Step 5: Handle Selection in the Viewport
In the following procedure, make changes to your code so that you can enter Component Mode by double-clicking the component in the viewport.
In Component Mode, you can modify the dimensions of the Point Light component directly in the viewport.
To handle selection in the viewport
In a text editor, open the
EditorPointLightComponent.h
file.For the last parameter, add the
EditorComponentSelectionRequestsBus::Handler
.class EditorPointLightComponent : public EditorLightComponent , private AzToolsFramework::EditorComponentSelectionRequestsBus::Handler
To implement the
EditorComponentSelectionRequests
, you must override the following four functions:GetEditorSelectionBoundsViewport
- Returns an AABB encompassing the visible extents of your componentEditorSelectionIntersectRayViewport
- Where you implement selection for the componentSupportsEditorRayIntersect
- Override this function and returntrue
if you implementedEditorSelectionIntersectRayViewport
GetBoundingBoxDisplayType
- Used for debugging to ensure that the AABB is the correct fit. This example sets the function toNoBoundingBox
// EditorComponentSelectionRequests AZ::Aabb GetEditorSelectionBoundsViewport( const AzFramework::ViewportInfo& viewportInfo) override; bool EditorSelectionIntersectRayViewport( const AzFramework::ViewportInfo& viewportInfo, const AZ::Vector3& src, const AZ::Vector3& dir, AZ::VectorFloat& distance) override; bool SupportsEditorRayIntersect() override; AZ::u32 GetBoundingBoxDisplayType() override;
Save the file.
In a text editor, open the
EditorPointLightComponent.cpp
file.Connect and disconnect from the
EditorComponentSelectionRequestsBus
in theActivate
andDeactivate
functions of the component.void EditorPointLightComponent::Activate() { ... AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(GetEntityId()); ... } void EditorPointLightComponent::Deactivate() { ... AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusDisconnect(); ... }
Add the following changes to your code:
- Add an implementation of
SupportsEditorRayIntersect
to returntrue
. By default, this function returnsfalse
. - Add an implementation of
GetBoundingBoxDisplayType
to returnAzToolsFramework::EditorComponentSelectionRequests::BoundingBoxDisplay::NoBoundingBox
.
bool EditorPointLightComponent::SupportsEditorRayIntersect() { return true; } AZ::u32 EditorPointLightComponent::GetBoundingBoxDisplayType() { return AzToolsFramework::EditorComponentSelectionRequests::BoundingBoxDisplay::NoBoundingBox;}
Note:It’s possible to instead return theAzToolsFramework::EditorComponentSelectionRequests::BoundingBoxDisplay:BoundingBox
for debugging, but you shouldn’t leave it enabled.The next two functions show how to implement the picking and selection support.
- Add an implementation of
Add the implementation for the
GetEditorSelectionBoundsViewport
function.Create an AABB centered around the component covering its extents. In this case, get the position in world space of the entity and create an AABB with the radius of the point light. Because the point light is represented as a sphere, use the
GetPointMaxDistance
function. ExampleYour code should look like the following.
AZ::Aabb EditorPointLightComponent::GetEditorSelectionBoundsViewport( const AzFramework::ViewportInfo& viewportInfo) { AZ::Vector3 worldTranslation = AZ::Vector3::CreateZero(); AZ::TransformBus::EventResult( worldTranslation, GetEntityId(), &AZ::TransformInterface::GetWorldTranslation); return AZ::Aabb::CreateCenterRadius(worldTranslation, GetPointMaxDistance()); }
In the next step, make changes to the
EditorSelectionIntersectRayViewport
function. Example// top of file <AzToolsFramework/Picking/Manipulators/ManipulatorBounds.h> ... bool EditorPointLightComponent::EditorSelectionIntersectRayViewport( const AzFramework::ViewportInfo& viewportInfo, const AZ::Vector3& src, const AZ::Vector3& dir, AZ::VectorFloat& distance) { AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult( worldFromLocal, GetEntityId(), &AZ::TransformInterface::GetWorldTM); const float minorRadius = 0.1f; const float majorRadius = GetPointMaxDistance(); const AZ::Vector3 axes[] = { AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ() }; enum { AxisCount = 3 }; float distances[AxisCount] = { FLT_MAX, FLT_MAX, FLT_MAX }; bool intersection = false; for (size_t axisIndex = 0; axisIndex < AxisCount; ++axisIndex) { intersection = intersection || AzToolsFramework::Picking::IntersectHollowCylinder( src, dir, worldFromLocal.GetTranslation(), axes[axisIndex], minorRadius, majorRadius, distances[axisIndex]); } distance = AZ::GetMin(AZ::GetMin(distances[0], distances[1]), distances[2]); return intersection; }
Get the position of the entity in world space and approximate a torus or flat hollow cylinder to represent the rings of the Point Light component. The minor radius corresponds to the tube part of the torus, which is its thickness.
const float minorRadius = 0.1f; const float majorRadius = GetPointMaxDistance();
You want a radius that is a reasonable size so that you can easily select it in the viewport. The major radius is the distance from the center of the torus to the middle of the tube. Because you have a ring for each axis, check that each one is using the
IntersectHollowCylinder
function, which basically approximates a torus.Test a ring for each axis and store the intersection distances to find the closest intersection.
{ intersection = intersection || AzToolsFramework::Picking::IntersectHollowCylinder( src, dir, worldFromLocal.GetTranslation(), axes[axisIndex], minorRadius, majorRadius, distances[axisIndex]); }
If a successful intersection occurred, the shortest distance is returned.
Save the file.