// Portions Copyright (c) 2008-2015 the Urho3D project. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved // Please see LICENSE.md in repository root for license information // https://github.com/AtomicGameEngine/AtomicGameEngine #include #include #include #include #include #include "Gizmo3D.h" namespace AtomicEditor { Gizmo3D::Gizmo3D(Context* context) : Object(context) { ResourceCache* cache = GetSubsystem(); gizmoNode_ = new Node(context_); gizmo_ = gizmoNode_->CreateComponent(); gizmo_->SetModel(cache->GetResource("AtomicEditor/Models/Axes.mdl")); gizmo_->SetEnabled(false); gizmo_->SetViewMask(0x80000000); // Editor raycasts use viewmask 0x7fffffff gizmo_->SetOccludee(false); axisMode_ = AXIS_LOCAL; gizmoAxisX_.lastSelected_ = false; gizmoAxisY_.lastSelected_ = false; gizmoAxisZ_.lastSelected_ = false; editMode_ = EDIT_MOVE; lastEditMode_ = EDIT_SELECT; } Gizmo3D::~Gizmo3D() { } void Gizmo3D::SetView(SceneView3D* view3D) { view3D_ = view3D; scene_ = view3D->GetScene(); camera_ = view3D->GetCameraNode()->GetComponent(); assert(camera_.NotNull()); } void Gizmo3D::Position() { Vector3 center(0, 0, 0); bool containsScene = false; for (unsigned i = 0; i < editNodes_->Size(); ++i) { // Scene's transform should not be edited, so hide gizmo if it is included if (editNodes_->At(i) == scene_) { containsScene = true; break; } center += editNodes_->At(i)->GetWorldPosition(); } if (editNodes_->Empty() || containsScene) { Hide(); return; } center /= editNodes_->Size(); gizmoNode_->SetPosition(center); if (axisMode_ == AXIS_WORLD || editNodes_->Size() > 1) gizmoNode_->SetRotation(Quaternion()); else gizmoNode_->SetRotation(editNodes_->At(0)->GetWorldRotation()); ResourceCache* cache = GetSubsystem(); if (editMode_ != lastEditMode_) { switch (editMode_) { case EDIT_MOVE: gizmo_->SetModel(cache->GetResource("AtomicEditor/Models/Axes.mdl")); break; case EDIT_ROTATE: gizmo_->SetModel(cache->GetResource("AtomicEditor/Models/RotateAxes.mdl")); break; case EDIT_SCALE: gizmo_->SetModel(cache->GetResource("AtomicEditor/Models/ScaleAxes.mdl")); break; default: break; } lastEditMode_ = editMode_; } bool orbiting = false; if ((editMode_ != EDIT_SELECT && !orbiting) && !gizmo_->IsEnabled()) Show(); else if ((editMode_ == EDIT_SELECT || orbiting) && gizmo_->IsEnabled()) Hide(); if (gizmo_->IsEnabled()) { float scale = 0.1f / camera_->GetZoom(); if (camera_->IsOrthographic()) scale *= camera_->GetOrthoSize(); else scale *= (camera_->GetView() * gizmoNode_->GetPosition()).z_; gizmoNode_->SetScale(Vector3(scale, scale, scale)); } } void Gizmo3D::Update(Vector &editNodes) { editNodes_ = &editNodes; Use(); Position(); } void Gizmo3D::CalculateGizmoAxes() { gizmoAxisX_.axisRay_ = Ray(gizmoNode_->GetPosition(), gizmoNode_->GetRotation() * Vector3(1, 0, 0)); gizmoAxisY_.axisRay_ = Ray(gizmoNode_->GetPosition(), gizmoNode_->GetRotation() * Vector3(0, 1, 0)); gizmoAxisZ_.axisRay_ = Ray(gizmoNode_->GetPosition(), gizmoNode_->GetRotation() * Vector3(0, 0, 1)); } void Gizmo3D::Use() { if (gizmo_.Null() || !gizmo_->IsEnabled() || editMode_ == EDIT_SELECT) { //StoreGizmoEditActions(); //previousGizmoDrag = false; return; } ResourceCache* cache = GetSubsystem(); Input* input = GetSubsystem(); Ray cameraRay = view3D_->GetCameraRay(); float scale = gizmoNode_->GetScale().x_; // Recalculate axes only when not left-dragging bool drag = input->GetMouseButtonDown(MOUSEB_LEFT);// && (Abs(input->GetMouseMoveX()) > 3 || Abs(input->GetMouseMoveY()) > 3); if (!drag) CalculateGizmoAxes(); gizmoAxisX_.Update(cameraRay, scale, drag, camera_->GetNode()); gizmoAxisY_.Update(cameraRay, scale, drag, camera_->GetNode()); gizmoAxisZ_.Update(cameraRay, scale, drag, camera_->GetNode()); if (!editNodes_->Size() || editNodes_->At(0) == scene_) { gizmoAxisX_.selected_ = gizmoAxisY_.selected_ = gizmoAxisZ_.selected_ = false; // this just forces an update gizmoAxisX_.lastSelected_ = gizmoAxisY_.lastSelected_ = gizmoAxisZ_.lastSelected_ = true; } if (gizmoAxisX_.selected_ != gizmoAxisX_.lastSelected_) { gizmo_->SetMaterial(0, cache->GetResource( gizmoAxisX_.selected_ ? "AtomicEditor/Materials/BrightRedUnlit.xml" : "AtomicEditor/Materials/RedUnlit.xml")); gizmoAxisX_.lastSelected_ = gizmoAxisX_.selected_; } if (gizmoAxisY_.selected_ != gizmoAxisY_.lastSelected_) { gizmo_->SetMaterial(1, cache->GetResource( gizmoAxisY_.selected_ ? "AtomicEditor/Materials/BrightGreenUnlit.xml" : "AtomicEditor/Materials/GreenUnlit.xml")); gizmoAxisY_.lastSelected_ = gizmoAxisY_.selected_; } if (gizmoAxisZ_.selected_ != gizmoAxisZ_.lastSelected_) { gizmo_->SetMaterial(2, cache->GetResource( gizmoAxisZ_.selected_ ? "AtomicEditor/Materials/BrightBlueUnlit.xml" : "AtomicEditor/Materials/BlueUnlit.xml")); gizmoAxisZ_.lastSelected_ = gizmoAxisZ_.selected_; } if (drag) Drag(); } bool Gizmo3D::MoveEditNodes(Vector3 adjust) { bool moved = false; if (adjust.Length() > M_EPSILON) { for (unsigned i = 0; i < editNodes_->Size(); ++i) { /* if (moveSnap) { float moveStepScaled = moveStep * snapScale; adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled; adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled; adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled; } */ Node* node = editNodes_->At(i); Vector3 nodeAdjust = adjust; if (axisMode_ == AXIS_LOCAL && editNodes_->Size() == 1) nodeAdjust = node->GetWorldRotation() * nodeAdjust; Vector3 worldPos = node->GetWorldPosition(); Vector3 oldPos = node->GetPosition(); worldPos += nodeAdjust; if (!node->GetParent()) node->SetPosition(worldPos); else node->SetPosition(node->GetParent()->WorldToLocal(worldPos)); if (node->GetPosition() != oldPos) moved = true; } } return moved; } bool Gizmo3D::RotateEditNodes(Vector3 adjust) { bool moved = false; /* if (rotateSnap) { float rotateStepScaled = rotateStep * snapScale; adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled; adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled; adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled; } */ if (adjust.Length() > M_EPSILON) { moved = true; for (unsigned i = 0; i < editNodes_->Size(); ++i) { Node* node = editNodes_->At(i); Quaternion rotQuat(adjust.x_, adjust.y_, adjust.z_); if (axisMode_ == AXIS_LOCAL && editNodes_->Size() == 1) node->SetRotation(node->GetRotation() * rotQuat); else { Vector3 offset = node->GetWorldPosition() - gizmoAxisX_.axisRay_.origin_; if (node->GetParent() && node->GetParent()->GetWorldRotation() != Quaternion(1, 0, 0, 0)) rotQuat = node->GetParent()->GetWorldRotation().Inverse() * rotQuat * node->GetParent()->GetWorldRotation(); node->SetRotation(rotQuat * node->GetRotation()); Vector3 newPosition = gizmoAxisX_.axisRay_.origin_ + rotQuat * offset; if (node->GetParent()) newPosition = node->GetParent()->WorldToLocal(newPosition); node->SetPosition(newPosition); } } } return moved; } bool Gizmo3D::ScaleEditNodes(Vector3 adjust) { bool moved = false; if (adjust.Length() > M_EPSILON) { for (unsigned i = 0; i < editNodes_->Size(); ++i) { Node* node = editNodes_->At(i); Vector3 scale = node->GetScale(); Vector3 oldScale = scale; if (true)//!scaleSnap) scale += adjust; else { /* float scaleStepScaled = scaleStep * snapScale; if (adjust.x != 0) { scale.x += adjust.x * scaleStepScaled; scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled; } if (adjust.y != 0) { scale.y += adjust.y * scaleStepScaled; scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled; } if (adjust.z != 0) { scale.z += adjust.z * scaleStepScaled; scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled; } */ } if (scale != oldScale) moved = true; node->SetScale(scale); } } return moved; } void Gizmo3D::Moved() { gizmoAxisX_.Moved(); gizmoAxisY_.Moved(); gizmoAxisZ_.Moved(); } void Gizmo3D::Drag() { bool moved = false; float scale = gizmoNode_->GetScale().x_; if (editMode_ == EDIT_MOVE) { Vector3 adjust(0, 0, 0); if (gizmoAxisX_.selected_) adjust += Vector3(1, 0, 0) * (gizmoAxisX_.t_ - gizmoAxisX_.lastT_); if (gizmoAxisY_.selected_) adjust += Vector3(0, 1, 0) * (gizmoAxisY_.t_ - gizmoAxisY_.lastT_); if (gizmoAxisZ_.selected_) adjust += Vector3(0, 0, 1) * (gizmoAxisZ_.t_ - gizmoAxisZ_.lastT_); moved = MoveEditNodes(adjust); } else if (editMode_ == EDIT_ROTATE) { const float rotSensitivity = 50.0; Vector3 adjust(0, 0, 0); if (gizmoAxisX_.selected_) adjust.x_ = (gizmoAxisX_.d_ - gizmoAxisX_.lastD_) * rotSensitivity / scale; if (gizmoAxisY_.selected_) adjust.y_ = -(gizmoAxisY_.d_ - gizmoAxisY_.lastD_) * rotSensitivity / scale; if (gizmoAxisZ_.selected_) adjust.z_ = (gizmoAxisZ_.d_ - gizmoAxisZ_.lastD_) * rotSensitivity / scale; moved = RotateEditNodes(adjust); } else if (editMode_ == EDIT_SCALE) { Vector3 adjust(0, 0, 0); if (gizmoAxisX_.selected_) adjust += Vector3(1, 0, 0) * (gizmoAxisX_.t_ - gizmoAxisX_.lastT_); if (gizmoAxisY_.selected_) adjust += Vector3(0, 1, 0) * (gizmoAxisY_.t_ - gizmoAxisY_.lastT_); if (gizmoAxisZ_.selected_) adjust += Vector3(0, 0, 1) * (gizmoAxisZ_.t_ - gizmoAxisZ_.lastT_); // Special handling for uniform scale: use the unmodified X-axis movement only if (editMode_ == EDIT_SCALE && gizmoAxisX_.selected_ && gizmoAxisY_.selected_ && gizmoAxisZ_.selected_) { float x = gizmoAxisX_.t_ - gizmoAxisX_.lastT_; adjust = Vector3(x, x, x); } moved = ScaleEditNodes(adjust); } if (moved) { Moved(); //UpdateNodeAttributes(); //needGizmoUndo = true; } } void Gizmo3D::SetEditMode(EditMode mode) { editMode_ = mode; } void Gizmo3D::Hide() { gizmo_->SetEnabled(false); } void Gizmo3D::Show() { if (scene_.Null()) return; gizmo_->SetEnabled(true); Octree* octree = scene_->GetComponent(); if (!octree) return; octree->AddManualDrawable(gizmo_); } }