Skip to content

Commit

Permalink
Improved the new translation feature in the selection tool.
Browse files Browse the repository at this point in the history
Drag entities along the XZ plane and hold ALT to translate along Y.
  • Loading branch information
fLindahl committed Mar 15, 2024
1 parent 5d3afe4 commit 206e021
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 28 deletions.
2 changes: 1 addition & 1 deletion toolkit/editor/editor/tools/camera.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Camera::Update()
this->freeCamUtil.SetMouseMovement({ -io.MouseDelta.x, -io.MouseDelta.y });
this->freeCamUtil.SetAccelerateButton(io.KeyShift);

this->freeCamUtil.SetRotateButton(io.MouseDown[Input::MouseButton::LeftButton]);
this->freeCamUtil.SetRotateButton(io.MouseDown[Input::MouseButton::RightButton]);
this->freeCamUtil.Update();

switch (this->cameraMode)
Expand Down
2 changes: 1 addition & 1 deletion toolkit/editor/editor/tools/camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class Camera
SizeT screenWidth = 0;
SizeT screenHeight = 0;

CameraMode cameraMode = CameraMode::ORBIT;
CameraMode cameraMode = CameraMode::FREECAM;
ProjectionMode projectionMode = ProjectionMode::PERSPECTIVE;
float zoomIn = 0.0f;
float zoomOut = 0.0f;
Expand Down
169 changes: 145 additions & 24 deletions toolkit/editor/editor/tools/selectiontool.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ SelectionTool::Update(Math::vec2 const& viewPortPosition, Math::vec2 const& view
Ptr<Input::Mouse> mouse = Input::InputServer::Instance()->GetDefaultMouse();
Ptr<Input::Keyboard> keyboard = Input::InputServer::Instance()->GetDefaultKeyboard();

bool performPicking = mouse->ButtonUp(Input::MouseButton::Code::LeftButton) && !state.translation.isTransforming;
bool performPicking = mouse->ButtonUp(Input::MouseButton::Code::LeftButton) && state.picking.pauseCounter == 0;
SelectionMode mode = keyboard->KeyPressed(Input::Key::LeftControl) ? SelectionMode::Append : SelectionMode::Replace;

if (performPicking)
Expand Down Expand Up @@ -166,10 +166,30 @@ SelectionTool::RenderGizmo(Math::vec2 const& viewPortPosition, Math::vec2 const&
Ptr<Input::Mouse> mouse = Input::InputServer::Instance()->GetDefaultMouse();
Ptr<Input::Keyboard> keyboard = Input::InputServer::Instance()->GetDefaultKeyboard();

// TODO: Only start translating if the mouse has moved more that a small distance
// Store mouse pos on button down, then check for > 4 pixel diff, then start transforming if button pressed.
state.translation.isTransforming = mouse->ButtonPressed(Input::MouseButton::Code::LeftButton);
if (mouse->ButtonDown(Input::MouseButton::Code::LeftButton))
{
state.translation.mousePosOnStart = mouse->GetScreenPosition();
}

if (!state.translation.dragTimer.Running() && state.translation.pickingPaused)
{
state.translation.pickingPaused = false;
state.picking.pauseCounter--;
}

// Only start translating if the mouse has moved more that a small distance
if (!state.translation.dragTimer.Running())
{
float mouseDistance = (mouse->GetScreenPosition() - state.translation.mousePosOnStart).length();
if (mouseDistance > 0.0015f && mouse->ButtonPressed(Input::MouseButton::Code::LeftButton))
{
state.translation.dragTimer.Reset();
state.translation.dragTimer.Start();
state.picking.pauseCounter++;
state.translation.pickingPaused = true;
}
}

Math::vec2 mousePos = mouse->GetScreenPosition();
mousePos -= viewPortPosition;
mousePos = {mousePos.x / viewPortSize.x, mousePos.y / viewPortSize.y};
Expand All @@ -179,32 +199,73 @@ SelectionTool::RenderGizmo(Math::vec2 const& viewPortPosition, Math::vec2 const&

Math::line ray = RenderUtil::MouseRayUtil::ComputeWorldMouseRay(mousePos, 10000, camTransform, invProj, 0.01f);

if (state.translation.isTransforming && !state.translation.isDirty)
if (state.translation.dragTimer.Running() && !state.translation.isDirty)
{
state.translation.isDirty = true;

// TODO: get the entity that is under the mouse and base the plane on it's center instead of the last selected.
Game::Position entityPos = Editor::state.editorWorld->GetComponent<Game::Position>(state.selection.Back());
state.translation.plane = Math::plane(entityPos, Math::vector(0, 1, 0));
Math::point startPos;
if (state.translation.plane.intersect(ray, startPos))
state.translation.originEntity = GetSelectedEntityUnderMouse(viewPortPosition, viewPortSize, camera);
if (state.translation.originEntity == Editor::Entity::Invalid())
{
state.translation.startPos = startPos.vec;
// failed to find entity. Cancel translation attempt
state.translation.dragTimer.Stop();
}
else
{
// failed to find plane. Cancel translation attempt
state.translation.isTransforming = false;
state.translation.isDirty = false;
Game::Position entityPos = Editor::state.editorWorld->GetComponent<Game::Position>(state.translation.originEntity);
state.translation.plane = Math::plane(entityPos, Math::vector(0, 1, 0));
Math::point startPos;
if (state.translation.plane.intersect(ray, startPos))
{
state.translation.startPos = startPos.vec;
state.translation.isDirty = true;
}
else
{
// failed to find plane. Cancel translation attempt
state.translation.dragTimer.Stop();
}
}
}

if (state.translation.isTransforming)
bool const applyTransform = mouse->ButtonUp(Input::MouseButton::Code::LeftButton) && state.translation.dragTimer.Running();
if (applyTransform)
{
state.translation.dragTimer.Stop();
}

if (state.translation.dragTimer.Running())
{
Math::point mousePosOnWorldPlane;
if (state.translation.plane.intersect(ray, mousePosOnWorldPlane))
bool xzAxis = !keyboard->KeyPressed(Input::Key::LeftMenu);

if (xzAxis)
{
Math::point mousePosOnWorldPlane;
if (state.translation.plane.intersect(ray, mousePosOnWorldPlane))
{
state.translation.delta = mousePosOnWorldPlane.vec - state.translation.startPos;
}
}
else // y axis translation
{
state.translation.delta = mousePosOnWorldPlane.vec - state.translation.startPos;
// find a good plane
Game::Position gameEntityPos = defaultWorld->GetComponent<Game::Position>(Editor::state.editables[state.translation.originEntity.index].gameEntity);

Math::vector xDir = Math::vector(1, 0, 0);
Math::vector zDir = Math::vector(0, 0, 1);
float px = Math::abs(Math::dot(ray.m, xDir));
float pz = Math::abs(Math::dot(ray.m, zDir));

Math::vector planeNormal;
if (px > pz)
planeNormal = xDir;
else
planeNormal = zDir;

Math::plane plane = Math::plane(gameEntityPos, planeNormal);
Math::point mousePosOnWorldPlane;
if (plane.intersect(ray, mousePosOnWorldPlane))
{
state.translation.delta.y = (mousePosOnWorldPlane.vec - state.translation.startPos).y;
state.translation.plane = Math::plane(state.translation.startPos + state.translation.delta, Math::vector(0, 1, 0));
}
}

for (IndexT i = 0; i < state.selection.Size(); i++)
Expand All @@ -219,8 +280,9 @@ SelectionTool::RenderGizmo(Math::vec2 const& viewPortPosition, Math::vec2 const&
}
else if (state.translation.isDirty)
{
// TODO: Only apply the translation if the translation has happened for more than ~200ms
if (state.translation.delta != Math::vec3(0))
// Only apply the translation if the translation has happened for more than some threshold duration
constexpr float minDragTime = 0.1f;
if (state.translation.delta != Math::vec3(0) && state.translation.dragTimer.GetTime() > minDragTime)
{
// User has release gizmo, we can set real transform and add to undo queue
Edit::CommandManager::BeginMacro("Translate entities", false);
Expand All @@ -231,9 +293,19 @@ SelectionTool::RenderGizmo(Math::vec2 const& viewPortPosition, Math::vec2 const&
Edit::SetComponent(state.selection[i], Game::GetComponentId<Game::Position>(), &pos);
}
Edit::CommandManager::EndMacro();
state.translation.isTransforming = false;
state.translation.isDirty = false;
}
else
{
// Reset game entity position to be same as editors
for (IndexT i = 0; i < state.selection.Size(); i++)
{
Game::Position pos = Editor::state.editorWorld->GetComponent<Game::Position>(state.selection[i]);
Game::Entity const gameEntity = Editor::state.editables[state.selection[i].index].gameEntity;
defaultWorld->SetComponent<Game::Position>(gameEntity, pos);
defaultWorld->MarkAsModified(gameEntity);
}
}
}

for (auto const editorEntity : state.selection)
Expand All @@ -256,7 +328,56 @@ SelectionTool::RenderGizmo(Math::vec2 const& viewPortPosition, Math::vec2 const&
bool
SelectionTool::IsTransforming()
{
return state.translation.isTransforming;
return state.translation.dragTimer.Running();
}

//------------------------------------------------------------------------------
/**
*/
Editor::Entity
SelectionTool::GetSelectedEntityUnderMouse(
Math::vec2 const& viewPortPosition, Math::vec2 const& viewPortSize, Editor::Camera const* camera
)
{
Game::World* defaultWorld = Game::GetWorld(WORLD_DEFAULT);

float closestDistance = 1e30f;
Editor::Entity closestEntity = Editor::Entity::Invalid();

Ptr<Input::Mouse> mouse = Input::InputServer::Instance()->GetDefaultMouse();
Math::vec2 mousePos = mouse->GetScreenPosition();

mousePos -= viewPortPosition;
mousePos = {mousePos.x / viewPortSize.x, mousePos.y / viewPortSize.y};

Math::mat4 const camTransform = Math::inverse(camera->GetViewTransform());
Math::mat4 const invProj = Math::inverse(camera->GetProjectionTransform());

Math::line ray = RenderUtil::MouseRayUtil::ComputeWorldMouseRay(mousePos, 10000, camTransform, invProj, 0.01f);

for (IndexT i = 0; i < state.selection.Size(); i++)
{
Editor::Entity editorEntity = state.selection[i];
Game::Entity const gameEntity = Editor::state.editables[editorEntity.index].gameEntity;
if (defaultWorld->HasComponent<GraphicsFeature::Model>(gameEntity))
{
Graphics::GraphicsEntityId const gfxEntity =
defaultWorld->GetComponent<GraphicsFeature::Model>(gameEntity).graphicsEntityId;
Math::bbox const bbox = Models::ModelContext::ComputeBoundingBox(gfxEntity);

float dist;
if (bbox.intersects(ray, dist))
{
if (dist < closestDistance)
{
closestDistance = dist;
closestEntity = editorEntity;
}
}
}
}

return closestEntity;
}

} // namespace Tools
17 changes: 15 additions & 2 deletions toolkit/editor/editor/tools/selectiontool.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ class SelectionTool
static void Update(Math::vec2 const& viewPortPosition, Math::vec2 const& viewPortSize, Editor::Camera const* camera);
static bool IsTransforming();

/// returns the selected entity that is directly under the mouse, or invalid if none is under.
static Editor::Entity GetSelectedEntityUnderMouse(
Math::vec2 const& viewPortPosition, Math::vec2 const& viewPortSize, Editor::Camera const* camera
);

private:
friend Edit::CMDSetSelection;

Expand All @@ -50,20 +55,28 @@ class SelectionTool
bool useGridIncrements = false;
/// used to check if any changes should be applied to the entities
bool isDirty = false;
/// will be true if the entities are being dragged around
bool isTransforming = false;
/// entity translation delta when dragging entities around. Applied and reset when mouse is released
Math::vec3 delta = Math::vec3(0);
/// start position of the the mouse in worldspace along the XZ plane that cuts the hovered objects origin when we start translating
Math::vec3 startPos;
/// the current translation plane
Math::plane plane;
/// position of mouse when starting translation
Math::vec2 mousePosOnStart;
/// timer that starts when the mouse starts dragging
Timing::Timer dragTimer;
/// the entity that is used to calculate translation planes.
Editor::Entity originEntity = Editor::Entity::Invalid();
/// prolongs picking pause for one frame after entity has been dragged
bool pickingPaused = false;
} translation;

struct
{
/// entities that are omitted from being selected when picking
Util::Array<Editor::Entity> omittedEntities;
/// increment to disallow picking temporarily. Remember to decrement when done.
int pauseCounter = 0;
} picking;
};

Expand Down

0 comments on commit 206e021

Please sign in to comment.