/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "stdafx.h" #include "ManipScene.h" #ifdef AZ_PLATFORM_WINDOWS #pragma warning(disable: 4266) // base function is hidden #endif #include "Serialization.h" #include "QViewportEvents.h" #include #include #include #include #include #include #include #include "../EditorCommon/QViewport.h" #include // from Sandbox/Editor: #include "Include/IDisplayViewport.h" #include "Include/HitContext.h" #include "Objects/DisplayContext.h" #include "RenderHelpers/AxisHelper.h" #include "Util/EditorUtils.h" #include "Util/Math.h" // ^^^ #include "DisplayViewportAdapter.h" #include namespace Manip { static unsigned int GetLayerBits(const SElements& elements) { unsigned int result = 0; for (size_t i = 0; i < elements.size(); ++i) { result |= 1 << elements[i].layer; } return result; } static unsigned int GetLayerBits(const SElements& elements, const SSelectionSet& selection) { unsigned int result = 0; for (size_t i = 0; i < elements.size(); ++i) { if (selection.Contains(elements[i].id)) { result |= 1 << elements[i].layer; } } return result; } static QuatT GetGizmoOrientation(const QuatT& transform, const CCamera* camera, ETransformationSpace space) { if (space == SPACE_LOCAL) { return transform; } else { return QuatT(IDENTITY, transform.t); } } // --------------------------------------------------------------------------- SERIALIZATION_ENUM_BEGIN(ETransformationMode, "Transformation Mode") SERIALIZATION_ENUM(MODE_TRANSLATE, "translate", "Translate") SERIALIZATION_ENUM(MODE_ROTATE, "rotate", "Rotate") SERIALIZATION_ENUM(MODE_SCALE, "scale", "Scale") SERIALIZATION_ENUM_END() // --------------------------------------------------------------------------- CScene::CScene() : m_axisHelper(new CAxisHelper()) , m_showGizmo(true) , m_transformationMode(MODE_TRANSLATE) , m_transformationSpace(SPACE_LOCAL) , m_customDrawer(0) , m_customTracer(0) , m_spaceProvider(0) , m_temporaryLocalDelta(IDENTITY) , m_visibleLayerMask(0xffffffff) { m_axisHelper->SetMode(CAxisHelper::MOVE_MODE); } CScene::~CScene() { } static void DrawElement(const SRenderContext& rc, const SElement& element, ElementId highlightedItem, bool isSelected, bool xRay, IElementDrawer* customDrawer, CScene* spaceProvider, const SLookSettings& lookSettings) { if (element.hidden) { return; } IRenderer* renderer = GetIEditor()->GetRenderer(); #if ENABLE_CRY_PHYSICS IPhysicsDebugRenderer* physicsDebugRenderer = GetIEditor()->GetSystem()->GetIPhysicsDebugRenderer(); #endif IRenderAuxGeom* aux = renderer->GetIRenderAuxGeom(); SAuxGeomRenderFlags defaultFlags(e_Mode3D | e_AlphaBlended | e_DrawInFrontOff | e_FillModeSolid | e_CullModeNone | e_DepthWriteOn | e_DepthTestOn); SAuxGeomRenderFlags xRayFlags(e_Mode3D | e_AlphaBlended | e_DrawInFrontOn | e_FillModeSolid | e_CullModeNone | e_DepthWriteOff | e_DepthTestOff); aux->SetRenderFlags(xRay ? xRayFlags : defaultFlags); QuatT transform = spaceProvider->ElementToWorldSpace(element); Matrix34 transformM(transform); bool isHighlighted = element.id == highlightedItem; if (element.shape == SHAPE_BOX) { OBB obb; obb.m33 = Matrix33(IDENTITY); obb.c = element.placement.center; obb.h = element.placement.size * 0.5f; bool isHighlighted = highlightedItem == element.id; ColorB color; if (isHighlighted) { color = lookSettings.proxyHighlightColor; } else if (isSelected) { color = lookSettings.proxySelectionColor; } else if (element.colorGroup == ELEMENT_COLOR_CLOTH) { color = lookSettings.clothProxyColor; } else { color = lookSettings.proxyColor; } ColorB innerColor = color; ColorB edgeColor(innerColor.r, innerColor.g, innerColor.b, 255); bool drawBB = false; if (!customDrawer || !customDrawer->Draw(element)) { drawBB = true; } if (isSelected || isHighlighted) { drawBB = true; } if (drawBB) { aux->DrawOBB(obb, transformM, false, edgeColor, eBBD_Faceted); } } else if (element.shape == SHAPE_AXES) { OBB obb; obb.m33 = Matrix33(IDENTITY); obb.c = element.placement.center; obb.h = Vec3(0.01f, 0.01f, 0.01f); bool isHighlighted = highlightedItem == element.id; ColorB color; if (isHighlighted) { color = lookSettings.proxyHighlightColor; } else if (isSelected) { color = lookSettings.proxySelectionColor; } else { color = lookSettings.proxyColor; } ColorB innerColor = color; ColorB edgeColor(innerColor.r, innerColor.g, innerColor.b, 255); bool drawBB = false; if (!customDrawer || !customDrawer->Draw(element)) { drawBB = true; } if (isSelected || isHighlighted) { drawBB = true; } if (drawBB) { aux->DrawOBB(obb, transformM, false, edgeColor, eBBD_Faceted); } } } void CScene::OnViewportRender(const SRenderContext& rc) { IRenderer* renderer = GetIEditor()->GetRenderer(); IRenderAuxGeom* aux = renderer->GetIRenderAuxGeom(); #if ENABLE_CRY_PHYSICS IPhysicsDebugRenderer* physicsDebugRenderer = GetIEditor()->GetSystem()->GetIPhysicsDebugRenderer(); physicsDebugRenderer->UpdateCamera(*rc.viewport->Camera()); #endif SignalRenderElements(m_elements, rc); for (int xRayPass = 0; xRayPass < 2; ++xRayPass) { size_t numElements = m_elements.size(); for (size_t i = 0; i < numElements; ++i) { SElement& element = m_elements[i]; bool isSelected = m_selection.Contains(element.id); if (!isSelected && !IsLayerVisible(element.layer)) { continue; } bool xRay = isSelected || element.id == m_highlightedItem || element.alwaysXRay; if (int(xRay) != xRayPass) { continue; } DrawElement(rc, element, m_highlightedItem, m_selection.Contains(element.id), xRay, m_customDrawer, this, m_lookSettings); } } if (m_mouseDragHandler.get()) { m_mouseDragHandler->Render(rc); } bool hasSelection = !m_selection.IsEmpty(); if (m_showGizmo && hasSelection) { int selectionCaps = GetSelectionCaps(); Matrix34 m = Matrix34(GetGizmoOrientation(GetSelectionTransform(SPACE_WORLD), rc.viewport->Camera(), m_transformationSpace)); DisplayContext dc; CDisplayViewportAdapter view(rc.viewport); dc.SetView(&view); SGizmoParameters gizmoParameters; gizmoParameters.axisGizmoScale = 0.2f; if (m_axisHelper.get()) { switch (m_transformationMode) { case MODE_TRANSLATE: m_axisHelper->SetMode(CAxisHelper::MOVE_MODE); gizmoParameters.enabled = (selectionCaps & CAP_MOVE) != 0; break; case MODE_ROTATE: m_axisHelper->SetMode(CAxisHelper::ROTATE_MODE); gizmoParameters.enabled = (selectionCaps & CAP_ROTATE) != 0; break; case MODE_SCALE: m_axisHelper->SetMode(CAxisHelper::SCALE_MODE); gizmoParameters.enabled = (selectionCaps & CAP_SCALE) != 0; break; } m_axisHelper->DrawAxis(m, gizmoParameters, dc); } } } void CScene::SetTransformationMode(ETransformationMode mode) { m_transformationMode = mode; } void CScene::SetTransformationSpace(ETransformationSpace space) { m_transformationSpace = space; } ETransformationMode CScene::TransformationMode() const { return m_transformationMode; } void CScene::SetSelection(const SSelectionSet& selection) { if (m_selection != selection) { m_selection = selection; SignalSelectionChanged(); } } void CScene::AddToSelection(ElementId elementId) { m_selection.Add(elementId); SignalSelectionChanged(); } void CScene::ApplyToAll(EElementAction action) { size_t count = m_elements.size(); for (size_t i = 0; i < count; ++i) { SElement& m = m_elements[i]; if (m.hidden) { m.action = action; //m.hidden = false; } } SignalElementsChanged(GetLayerBits(m_elements)); } void CScene::ApplyToSelection(EElementAction action) { size_t count = m_elements.size(); for (size_t i = 0; i < count; ++i) { SElement& m = m_elements[i]; if (m_selection.Contains(m.id)) { m.action = action; //m.hidden = true; } } SignalElementsChanged(GetLayerBits(m_elements, m_selection)); } void CScene::OnViewportKey(const SKeyEvent& ev) { QKeySequence key(ev.key); if (ev.type == SKeyEvent::PRESS) { if (key == QKeySequence(Qt::Key_U) || key == QKeySequence(Qt::Key_Z | Qt::CTRL)) { SignalUndo(); } else if (key == QKeySequence(Qt::Key_Y | Qt::CTRL) || key == QKeySequence(Qt::Key_Z | Qt::CTRL | Qt::SHIFT)) { SignalRedo(); } else if (key == QKeySequence(Qt::ALT | Qt::Key_H) || key == QKeySequence(Qt::SHIFT | Qt::Key_H)) { ApplyToAll(ACTION_UNHIDE); } else if (key == QKeySequence(Qt::Key_H)) { ApplyToSelection(ACTION_HIDE); } else if (key == QKeySequence(Qt::Key_Delete)) { ApplyToSelection(ACTION_DELETE); } else if (key == QKeySequence(Qt::Key_1)) { m_transformationMode = MODE_TRANSLATE; SignalManipulationModeChanged(); SignalPropertiesChanged(); } else if (key == QKeySequence(Qt::Key_2)) { m_transformationMode = MODE_ROTATE; SignalManipulationModeChanged(); SignalPropertiesChanged(); } else if (key == QKeySequence(Qt::Key_3)) { m_transformationMode = MODE_SCALE; SignalManipulationModeChanged(); SignalPropertiesChanged(); } } } bool CScene::ProcessesViewportKey(const QKeySequence& key) { static QSet overriddenKeys = { QKeySequence(Qt::Key_U), QKeySequence(Qt::Key_Z | Qt::CTRL), QKeySequence(Qt::Key_Y | Qt::CTRL), QKeySequence(Qt::Key_Z | Qt::CTRL | Qt::SHIFT), QKeySequence(Qt::ALT | Qt::Key_H), QKeySequence(Qt::SHIFT | Qt::Key_H), QKeySequence(Qt::Key_H), QKeySequence(Qt::Key_Delete), QKeySequence(Qt::Key_1), QKeySequence(Qt::Key_2), QKeySequence(Qt::Key_3) }; // Check if the parameter key is one that we care about in OnViewportKey // If we don't, matching shortcuts attached to the widget will get processed // instead, and CScene::OnViewportKey will never get called return overriddenKeys.contains(key); } bool RayHitsElement(Vec3* intersectionPoint, const Ray& ray, const SElement& element, IElementTracer* customTracer, CScene* spaceProvider) { QuatT parentSpace(IDENTITY); if (spaceProvider) { parentSpace = spaceProvider->GetParentSpace(element); } OBB obb; if (element.shape == SHAPE_BOX) { obb.m33 = Matrix33((parentSpace * element.placement.transform).q); obb.c = element.placement.center; obb.h = element.placement.size * 0.5f; } else { obb.m33 = IDENTITY; obb.c = Vec3(ZERO); obb.h = element.placement.size * 0.5f; } if (Intersect::Ray_OBB(ray, (parentSpace * element.placement.transform).t, obb, *intersectionPoint) != 0) { if (customTracer) { return customTracer->HitRay(intersectionPoint, ray, element); } return true; } return false; } static SElement* HitElementWithRay(Vec3* hitPoint, SElements& elements, const Ray& ray, IElementTracer* customTracer, CScene* spaceProvider) { const float BIG_VALUE = 1e20f; float closestDistanceSquare = BIG_VALUE; int hitPickPriority = INT_MIN; SElement* closestElement = 0; size_t numElements = elements.size(); for (size_t i = 0; i < numElements; ++i) { SElement& element = elements[i]; if (element.hidden) { continue; } if (!spaceProvider->IsLayerVisible(element.layer) && !spaceProvider->Selection().Contains(element.id)) { continue; } Vec3 intersectionPoint; if (RayHitsElement(&intersectionPoint, ray, element, customTracer, spaceProvider)) { float distanceSquare = (intersectionPoint - ray.origin).GetLengthSquared(); if (distanceSquare < closestDistanceSquare || int(element.mousePickPriority) > hitPickPriority) { closestDistanceSquare = distanceSquare; closestElement = &element; hitPickPriority = element.mousePickPriority; *hitPoint = intersectionPoint; } } } return closestElement; } static ElementId HitSelectionWithRay(Vec3* hitPoint, SElements& elements, const Ray& ray, IElementTracer* customTracer, CScene* spaceProvider) { SElement* element = HitElementWithRay(hitPoint, elements, ray, customTracer, spaceProvider); if (element) { return element->id; } return 0; } struct STransformConstraint { enum EType { NONE, AXIS, PLANE }; EType type; Plane plane; Vec3 axis; Vec3 localAxis; Vec3 GetAxis(ETransformationSpace space) { switch (space) { case SPACE_LOCAL: return localAxis; default: return axis; } } STransformConstraint() : type(NONE) { } }; struct CScene::SMoveHandler : IMouseDragHandler { CScene* m_scene; Vec3 m_lastPoint; Vec3 m_startPoint; Vec2i m_startMousePosition; STransformConstraint m_constraint; SElements m_elements; SElements m_transformedElements; int m_layerBits; SMoveHandler(CScene* scene, const STransformConstraint& constraint) : m_scene(scene) , m_constraint(constraint) , m_layerBits(0) , m_lastPoint(ZERO) , m_startPoint(ZERO) { } bool Begin(const SMouseEvent& ev, Vec3 hitPoint) override { m_startMousePosition = Vec2i(ev.x, ev.y); m_startPoint = hitPoint; m_scene->GetSelectedElements(&m_elements); m_layerBits = GetLayerBits(m_elements); if (!ReprojectStartPoint(ev.viewport)) { return false; } return true; } bool ReprojectStartPoint(QViewport* viewport) { Ray ray; MAKE_SURE(viewport->ScreenToWorldRay(&ray, m_startMousePosition.x, m_startMousePosition.y), return false); if (m_constraint.type == STransformConstraint::PLANE) { Intersect::Ray_Plane(ray, m_constraint.plane, m_startPoint, false); } else if (m_constraint.type == STransformConstraint::AXIS) { Vec3 point; Vec3 axis = m_constraint.GetAxis(m_scene->TransformationSpace()); RayToLineDistance(ray.origin, ray.origin + ray.direction, m_startPoint - axis * 10000.0f, m_startPoint + axis * 10000.0f, point); m_startPoint = m_startPoint + axis * axis.dot(point - m_startPoint); } return true; } void Update(const SMouseEvent& ev) override { ReprojectStartPoint(ev.viewport); Ray ray; MAKE_SURE(ev.viewport->ScreenToWorldRay(&ray, ev.x, ev.y), return ); if (m_constraint.type == STransformConstraint::PLANE) { Vec3 point(0.0f, 0.0f, 0.0f); Intersect::Ray_Plane(ray, m_constraint.plane, point, false); Vec3 delta = point - m_startPoint; Move(delta); } else if (m_constraint.type == STransformConstraint::AXIS) { Vec3 point = m_startPoint; Vec3 axis = m_constraint.GetAxis(m_scene->TransformationSpace()); RayToLineDistance(ray.origin, ray.origin + ray.direction, m_startPoint - axis * 10000.0f, m_startPoint + axis * 10000.0f, point); Vec3 newPoint = m_startPoint + axis * axis.dot(point - m_startPoint); Vec3 delta = newPoint - m_startPoint; Move(delta); } } void Move(Vec3 delta) { m_transformedElements = m_elements; for (size_t i = 0; i < m_transformedElements.size(); ++i) { SElement& e = m_transformedElements[i]; QuatT parentSpace = m_scene->GetParentSpace(e) * QuatT(e.placement.transform.q, ZERO); QuatT localDelta = parentSpace.GetInverted() * (QuatT(IDENTITY, delta) * parentSpace); m_scene->m_temporaryLocalDelta = localDelta; e.placement.transform = e.placement.transform * localDelta; e.placement.transform.q.Normalize(); } m_scene->UpdateElements(m_transformedElements); m_scene->SignalElementContinousChange(m_layerBits); } void End(const SMouseEvent& ev) override { m_scene->m_temporaryLocalDelta = QuatT(IDENTITY); m_scene->SignalElementsChanged(m_layerBits); } }; struct CScene::SRotationHandler : public IMouseDragHandler { CScene* m_scene; STransformConstraint m_constraint; SElements m_elements; SElements m_transformedElements; int m_layerBits; Vec3 m_origin; Vec2i m_startPoint; SRotationHandler(CScene* scene, const STransformConstraint& constraint) : m_scene(scene) , m_constraint(constraint) , m_layerBits(0) , m_origin(ZERO) , m_startPoint(ZERO) { if (m_constraint.type == STransformConstraint::PLANE) { // TODO: Apart from having rotation around axis of gizmo (as Sandbox does) it // is possible here to rotate around viewer axis. This part can be considerably // improved by making sure that picked point on the proxy keeps touching the // mouse pointer. m_constraint.localAxis = m_constraint.plane.n; m_constraint.axis = Vec3(0.0f, 0.0f, 1.0f); m_constraint.type = STransformConstraint::AXIS; } m_scene->GetSelectedElements(&m_elements); m_layerBits = GetLayerBits(m_elements); m_origin = m_scene->GetSelectionTransform(SPACE_WORLD).t; } bool Begin(const SMouseEvent& ev, Vec3 hitPoint) override { m_startPoint = Vec2i(ev.x, ev.y); return true; } void Update(const SMouseEvent& ev) override { if (ev.y != m_startPoint.y) { float angle = ((ev.y - m_startPoint.y) * 0.01f) * 3.1415926f; QuatT rotation; float cos, sin; sin = sinf(angle); cos = cosf(angle); Vec3 axis = m_constraint.GetAxis(m_scene->TransformationSpace()); rotation.SetRotationAA(cos, sin, axis); m_transformedElements = m_elements; for (size_t i = 0; i < m_elements.size(); ++i) { SElement& e = m_transformedElements[i]; QuatT t = m_scene->ElementToWorldSpace(e); t.t -= m_origin; t = rotation * t; t.t += m_origin; m_scene->m_temporaryLocalDelta = m_scene->ElementToWorldSpace(m_elements[i]).GetInverted() * t; m_scene->WorldSpaceToElement(&e, t); } m_scene->UpdateElements(m_transformedElements); m_scene->SignalElementContinousChange(m_layerBits); } } void End(const SMouseEvent& ev) override { m_scene->m_temporaryLocalDelta = QuatT(IDENTITY); m_scene->SignalElementsChanged(m_layerBits); } }; static Vec3 ScaleAround(Vec3 p, Vec3 origin, float scale) { return (p - origin) * scale + origin; } struct CScene::SScalingHandler : public IMouseDragHandler { CScene* m_scene; STransformConstraint m_constraint; SElements m_elements; SElements m_transformedElements; Vec3 m_origin; Vec3 m_size; Vec3 m_hitPoint; Vec2i m_startPoint; unsigned int m_layerBits; SScalingHandler(CScene* scene, const STransformConstraint& constraint) : m_scene(scene) , m_constraint(constraint) , m_layerBits(0) , m_origin(ZERO) , m_size(ZERO) , m_hitPoint(ZERO) , m_startPoint(ZERO) { } bool Begin(const SMouseEvent& ev, Vec3 hitPoint) override { m_hitPoint = hitPoint; m_startPoint = Vec2i(ev.x, ev.y); m_scene->GetSelectedElements(&m_elements); m_layerBits = GetLayerBits(m_elements); m_origin = m_scene->GetSelectionTransform(SPACE_WORLD).t; m_size = m_scene->GetSelectionSize(); return true; } void Update(const SMouseEvent& ev) override { float screenScaleFactor = 0.0f; if (const CCamera* camera = ev.viewport->Camera()) { screenScaleFactor = camera->GetPosition().GetDistance(m_hitPoint); if (screenScaleFactor < camera->GetNearPlane()) { screenScaleFactor = camera->GetNearPlane(); } } m_transformedElements = m_elements; if (m_transformedElements.size() == 1) { Vec3& size = m_transformedElements[0].placement.size; float difference = -(ev.y - m_startPoint.y) * 0.01f * screenScaleFactor; Vec3 axis = m_constraint.GetAxis(m_scene->TransformationSpace()); size = (size + size.CompMul(axis * difference)).abs(); } else { float difference = -(ev.y - m_startPoint.y) * 0.01f * screenScaleFactor; float sizeDifference = max(0.01f, fabsf(1.0f + difference)); for (size_t i = 0; i < m_transformedElements.size(); ++i) { SElement& g = m_transformedElements[i]; g.placement.transform.t = ScaleAround(g.placement.transform.t, m_origin, sizeDifference); g.placement.size = g.placement.size * sizeDifference; } } m_scene->UpdateElements(m_transformedElements); m_scene->SignalElementContinousChange(m_layerBits); } void End(const SMouseEvent& ev) override { m_scene->SignalElementsChanged(m_layerBits); } }; template struct SRange { T low; T high; SRange(T low, T high) : low(low) , high(high) { } SRange() : low(T(0)) , high(T(0)) { } void Add(T value) { if (value < low) { low = value; } if (value > high) { high = value; } } bool Intersects(const SRange& r) const { if (high < r.low) { return false; } if (low > r.high) { return false; } return true; } }; static bool RectsIntersect(Vec2i amin, Vec2i amax, Vec2i bmin, Vec2i bmax) { if (!SRange(amin.x, amax.x).Intersects(SRange(bmin.x, bmax.x))) { return false; } if (!SRange(amin.y, amax.y).Intersects(SRange(bmin.y, bmax.y))) { return false; } return true; } static bool OBBOverlapsSelectionFrustum(const OBB& box, Vec2i rectMin, Vec2i rectMax, const Plane (&frustum)[4], IDisplayViewport* viewport) { Vec3 s = box.h; Vec3 points[8] = { box.m33 * Vec3(-s.x, -s.y, -s.z) + box.c, box.m33 * Vec3(s.x, -s.y, -s.z) + box.c, box.m33 * Vec3(s.x, s.y, -s.z) + box.c, box.m33 * Vec3(-s.x, s.y, -s.z) + box.c, box.m33 * Vec3(-s.x, -s.y, s.z) + box.c, box.m33 * Vec3(s.x, -s.y, s.z) + box.c, box.m33 * Vec3(s.x, s.y, s.z) + box.c, box.m33 * Vec3(-s.x, s.y, s.z) + box.c, }; Vec2i projectedPoints[8]; Vec2i boundMin(INT_MAX, INT_MAX); Vec2i boundMax(INT_MIN, INT_MIN); for (int i = 0; i < 8; ++i) { Vec2i& p = projectedPoints[i]; QPoint pt = viewport->WorldToView(points[i]); p.x = pt.x(); p.y = pt.y(); if (p.x < boundMin.x) { boundMin.x = p.x; } if (p.y < boundMin.y) { boundMin.y = p.y; } if (p.x > boundMax.x) { boundMax.x = p.x; } if (p.y > boundMax.y) { boundMax.y = p.y; } } if (boundMin.x > rectMin.x && boundMax.x < rectMax.x && boundMin.y > rectMin.y && boundMax.y < rectMax.y) { return true; } if (!RectsIntersect(boundMin, boundMax, rectMin, rectMax)) { return false; } struct Edge { Vec2 a; Vec2 b; }; Edge boxEdges[12] = { { projectedPoints[0], projectedPoints[1] }, { projectedPoints[1], projectedPoints[2] }, { projectedPoints[2], projectedPoints[3] }, { projectedPoints[3], projectedPoints[0] }, { projectedPoints[4], projectedPoints[5] }, { projectedPoints[5], projectedPoints[6] }, { projectedPoints[6], projectedPoints[7] }, { projectedPoints[7], projectedPoints[4] }, { projectedPoints[0], projectedPoints[4] }, { projectedPoints[1], projectedPoints[5] }, { projectedPoints[2], projectedPoints[6] }, { projectedPoints[3], projectedPoints[7] } }; Vec2 rectPoints[4] = { Vec2(float(rectMin.x), float(rectMin.y)), Vec2(float(rectMax.x), float(rectMin.y)), Vec2(float(rectMin.x), float(rectMax.y)), Vec2(float(rectMax.x), float(rectMax.y)) }; for (size_t i = 0; i < 12; ++i) { const Edge& e = boxEdges[i]; Vec2 dir = e.b - e.a; if (dir.GetLength2() < 1.0f) { continue; } dir.Normalize(); Vec2 ort(dir.y, -dir.x); SRange range1; range1.low = ort.Dot(projectedPoints[0]); range1.high = range1.low; for (size_t j = 1; j < 8; ++j) { float pos = ort.Dot(projectedPoints[j]); range1.Add(pos); } SRange range2; range2.low = ort.Dot(rectPoints[0]); range2.high = range2.low; range2.Add(ort.Dot(rectPoints[1])); range2.Add(ort.Dot(rectPoints[2])); range2.Add(ort.Dot(rectPoints[3])); if (!range1.Intersects(range2)) { return false; } } return true; } static bool ElementInFrustum(const SElement& element, Vec2i rectMin, Vec2i rectMax, const Plane (&frustum)[4], IDisplayViewport* viewport, CScene* scene) { OBB obb; QuatT parentSpace = scene->GetParentSpace(element); obb.m33 = Matrix33((parentSpace * element.placement.transform).q); obb.c = Vec3((parentSpace * element.placement.transform).t); obb.h = element.placement.size * 0.5f; return OBBOverlapsSelectionFrustum(obb, rectMin, rectMax, frustum, viewport); } static void FindElementsInRect(vector* out, Vec2i point1, Vec2i point2, QViewport* viewport, const vector& elements, ISpaceProvider* spaceProvider, CScene* scene) { int minX = min(point1.x, point2.x); int minY = min(point1.y, point2.y); int maxX = max(point1.x, point2.x); int maxY = max(point1.y, point2.y); Ray ray1; if (!viewport->ScreenToWorldRay(&ray1, minX, minY)) { return; } Ray ray2; if (!viewport->ScreenToWorldRay(&ray2, maxX, maxY)) { return; } Matrix34 m = viewport->Camera()->GetMatrix(); Vec3 xdir = m.GetColumn0().GetNormalized(); Vec3 zdir = m.GetColumn2().GetNormalized(); Vec3 pos = m.GetTranslation(); Vec3 normals[4] = { ray1.direction.Cross(xdir).GetNormalized(), (zdir).Cross(ray2.direction).GetNormalized(), ray2.direction.Cross(-xdir).GetNormalized(), (-zdir).Cross(ray1.direction).GetNormalized(), }; Plane frustum[4]; for (int i = 0; i < 4; ++i) { frustum[i].SetPlane(-normals[i], pos); } CDisplayViewportAdapter view(viewport); for (int j = 0; j < elements.size(); ++j) { const SElement& m = elements[j]; if (m.hidden) { continue; } if (!scene->IsLayerVisible(m.layer) && !scene->Selection().Contains(m.id)) { continue; } if (ElementInFrustum(m, Vec2i(minX, minY), Vec2i(maxX, maxY), frustum, &view, scene)) { out->push_back(&m); } } } static void GetSelectionInRect(SSelectionSet* selection, Vec2i p1, Vec2i p2, QViewport* viewport, const vector& elements, ISpaceProvider* spaceProvider, CScene* scene) { vector elementsIn; FindElementsInRect(&elementsIn, p1, p2, viewport, elements, spaceProvider, scene); for (size_t i = 0; i < elementsIn.size(); ++i) { selection->Add(elementsIn[i]->id); } } static void DrawLine2D(const SRenderContext& rc, Vec2i p1, Vec2i p2, ColorB color) { IRenderer* renderer = GetIEditor()->GetRenderer(); IRenderAuxGeom* aux = renderer->GetIRenderAuxGeom(); int w = rc.viewport->Width(); int h = rc.viewport->Height(); if (w == 0 || h == 0) { return; } int renderFlags = aux->GetRenderFlags().m_renderFlags; aux->SetRenderFlags((renderFlags | e_Mode2D) & ~e_Mode3D); Vec3 start(float(p1.x) / w, float(p1.y) / h, 0.0f); Vec3 end(float(p2.x) / w, float(p2.y) / h, 0.0f); aux->DrawLine(start, color, end, color); aux->SetRenderFlags(renderFlags); } struct CScene::SBlockSelectHandler : public IMouseDragHandler { CScene* m_scene; Vec2i m_startPoint; Vec2i m_endPoint; SSelectionSet m_lastSelection; SBlockSelectHandler(CScene* scene) : m_scene(scene) { } bool Begin(const SMouseEvent& ev, Vec3 hitPoint) override { m_startPoint = Vec2i(ev.x, ev.y); m_endPoint = m_startPoint; m_scene->SignalPushUndo("Selection change", 0); m_scene->SetSelection(SSelectionSet()); return true; } void Update(const SMouseEvent& ev) override { m_endPoint = Vec2i(ev.x, ev.y); SSelectionSet selection; GetSelectionInRect(&selection, m_startPoint, m_endPoint, ev.viewport, m_scene->Elements(), m_scene->SpaceProvider(), m_scene); if (selection != m_lastSelection || (selection.IsEmpty() && !m_scene->Selection().IsEmpty())) { m_scene->SetSelection(selection); } } void Render(const SRenderContext& rc) override { ColorB color(255, 255, 255, 255); Vec2i points[4] = { m_startPoint, Vec2i(m_startPoint.x, m_endPoint.y), m_endPoint, Vec2i(m_endPoint.x, m_startPoint.y) }; DrawLine2D(rc, points[0], points[1], color); DrawLine2D(rc, points[1], points[2], color); DrawLine2D(rc, points[2], points[3], color); DrawLine2D(rc, points[3], points[0], color); } void End(const SMouseEvent& ev) override { } }; void CScene::OnMouseMove(const SMouseEvent& ev) { SGizmoParameters gizmoParameters; gizmoParameters.axisGizmoScale = 0.2f; CDisplayViewportAdapter displayView(ev.viewport); QuatT selectionTransform = GetSelectionTransform(SPACE_WORLD); int selectionCaps = GetSelectionCaps(); QuatT axesTransform = GetGizmoOrientation(selectionTransform, ev.viewport->Camera(), m_transformationSpace); if (m_mouseDragHandler.get()) { m_mouseDragHandler->Update(ev); } else if (m_showGizmo) { bool gizmoEnabled = true; switch (m_transformationMode) { case MODE_TRANSLATE: gizmoEnabled = (selectionCaps & CAP_MOVE) != 0; break; case MODE_ROTATE: gizmoEnabled = (selectionCaps & CAP_ROTATE) != 0; break; case MODE_SCALE: gizmoEnabled = (selectionCaps & CAP_SCALE) != 0; break; } if (gizmoEnabled) { HitContext hc; hc.point2d = QPoint(ev.x, ev.y); hc.view = &displayView; m_axisHelper->HitTest(Matrix34(axesTransform), gizmoParameters, hc); m_axisHelper->SetHighlightAxis(hc.axis); } else { m_axisHelper->SetHighlightAxis(0); } Ray ray; if (ev.viewport->ScreenToWorldRay(&ray, ev.x, ev.y)) { Vec3 hitPoint; m_highlightedItem = aznumeric_cast(HitSelectionWithRay(&hitPoint, m_elements, ray, m_customTracer, this)); } } } void CScene::OnViewportMouse(const SMouseEvent& ev) { if (!ev.viewport) { return; } SGizmoParameters gizmoParameters; gizmoParameters.axisGizmoScale = 0.2f; CDisplayViewportAdapter displayView(ev.viewport); QuatT selectionTransform = GetSelectionTransform(SPACE_WORLD); int selectionCaps = GetSelectionCaps(); QuatT axesTransform = GetGizmoOrientation(selectionTransform, ev.viewport->Camera(), m_transformationSpace); if (ev.type == SMouseEvent::PRESS) { int button = ev.button; if (button == SMouseEvent::BUTTON_LEFT) { Ray ray; if (ev.viewport->ScreenToWorldRay(&ray, ev.x, ev.y)) { STransformConstraint constraint; Vec3 hitPoint = selectionTransform.t; if (m_showGizmo) { HitContext hc; hc.point2d = QPoint(ev.x, ev.y); hc.view = &displayView; if (!Selection().IsEmpty() && m_axisHelper->HitTest(Matrix34(axesTransform), gizmoParameters, hc)) { Quat localRot(selectionTransform.q); Quat planeRot(GetGizmoOrientation(QuatT(selectionTransform.q, ZERO), ev.viewport->Camera(), m_transformationSpace).q); switch (hc.axis) { case AXIS_X: constraint.type = STransformConstraint::AXIS; constraint.axis = Vec3(1.0f, 0.0f, 0.0f); constraint.localAxis = localRot * constraint.axis; break; case AXIS_Y: constraint.type = STransformConstraint::AXIS; constraint.axis = Vec3(0.0f, 1.0f, 0.0f); constraint.localAxis = localRot * constraint.axis; break; case AXIS_Z: constraint.type = STransformConstraint::AXIS; constraint.axis = Vec3(0.0f, 0.0f, 1.0f); constraint.localAxis = localRot * constraint.axis; break; case AXIS_XY: constraint.type = STransformConstraint::PLANE; constraint.plane.SetPlane(planeRot * Vec3(0.0f, 0.0f, 1.0f), hitPoint); constraint.axis = Vec3(1.0f, 1.0f, 0.0f); break; case AXIS_XZ: constraint.type = STransformConstraint::PLANE; constraint.plane.SetPlane(planeRot * Vec3(0.0f, 1.0f, 0.0f), hitPoint); constraint.axis = Vec3(1.0f, 0.0f, 1.0f); break; case AXIS_YZ: constraint.type = STransformConstraint::PLANE; constraint.plane.SetPlane(planeRot * Vec3(1.0f, 0.0f, 0.0f), hitPoint); constraint.axis = Vec3(0.0f, 1.0f, 1.0f); break; case AXIS_XYZ: constraint.type = STransformConstraint::AXIS; constraint.localAxis = Vec3(1.0f, 1.0f, 1.0f); constraint.axis = Vec3(1.0f, 1.0f, 1.0f); break; } } } if (constraint.type == STransformConstraint::NONE) { ElementId selected_id = HitSelectionWithRay(&hitPoint, m_elements, ray, m_customTracer, this); if (selected_id != 0) { if (!m_selection.Contains(aznumeric_cast(selected_id)) || m_selection.Size() > 1) { if (ev.control) { AddToSelection(selected_id); } else { SetSelection(SSelectionSet(selected_id)); } } if (m_transformationMode == MODE_SCALE) { constraint.type = STransformConstraint::AXIS; constraint.axis = Vec3(1.0f, 1.0f, 1.0f); constraint.localAxis = Vec3(1.0f, 1.0f, 1.0f); } else { Matrix34 m = ev.viewport->Camera()->GetMatrix(); Vec3 xdir = m.GetColumn0().GetNormalized(); Vec3 ydir = m.GetColumn1().GetNormalized(); Vec3 zdir = m.GetColumn2().GetNormalized(); Vec3 pos = m.GetTranslation(); Vec3 fromScreenToSelection = pos - hitPoint; float distance = ydir.Dot(fromScreenToSelection); Vec3 planeCenter = pos + -ydir * distance; constraint.type = STransformConstraint::PLANE; constraint.plane.SetPlane(-ydir, planeCenter); constraint.axis = Vec3(1.0f, 0.0f, 1.0f); } } else { m_mouseDragHandler.reset(new SBlockSelectHandler(this)); } } if (constraint.type != STransformConstraint::NONE) { switch (m_transformationMode) { case MODE_TRANSLATE: if (selectionCaps & CAP_MOVE) { m_mouseDragHandler.reset(new SMoveHandler(this, constraint)); } break; case MODE_ROTATE: if (selectionCaps & CAP_ROTATE) { m_mouseDragHandler.reset(new SRotationHandler(this, constraint)); } break; case MODE_SCALE: if (selectionCaps & CAP_SCALE) { m_mouseDragHandler.reset(new SScalingHandler(this, constraint)); } break; default: break; } } if (m_mouseDragHandler.get()) { if (!m_mouseDragHandler->Begin(ev, hitPoint)) { m_mouseDragHandler.reset(); } } } } } else if (ev.type == SMouseEvent::RELEASE) { if (m_mouseDragHandler.get()) { m_mouseDragHandler->End(ev); m_mouseDragHandler.reset(); ev.viewport->ReleaseMouse(); } } else if (ev.type == SMouseEvent::MOVE) { OnMouseMove(ev); } } struct CScene::STransformBox { CScene* m_scene; STransformBox(CScene* scene) : m_scene(scene) { } void Serialize(IArchive& ar) { QuatT transform = m_scene->GetSelectionTransform(SPACE_WORLD); int caps = m_scene->GetSelectionCaps(); Vec3 position = transform.t; if ((caps & CAP_MOVE)) { ar(position, "position", "GetSelectionSize(); Vec3 sizeOld = size; if ((caps & CAP_SCALE)) { ar(size, "size", "SetSelectionTransform(SPACE_WORLD, transform); } if (sizeChanged) { m_scene->SetSelectionSize(size); } } } }; struct SSpaceSelector { ETransformationSpace& space; SSpaceSelector(ETransformationSpace& space) : space(space) { } void Serialize(Serialization::IArchive& ar) { using Serialization::RadioButton; ar(RadioButton((int&)space, SPACE_WORLD), "transformWorld", "^Global"); ar(RadioButton((int&)space, SPACE_LOCAL), "transformLocal", "^Local"); } }; void CScene::Serialize(IArchive& ar) { ar(m_transformationMode, "transformationMode", 0); if (ar.IsEdit()) { ar(SSpaceSelector(m_transformationSpace), "transformationSpace", "clear(); size_t numElements = m_elements.size(); for (size_t i = 0; i < numElements; ++i) { const SElement& element = m_elements[i]; if (m_selection.Contains(element.id)) { elements->push_back(element); } } } void CScene::UpdateElements(const SElements& elements) { // TODO: remove quadratic complexity here size_t numElements = m_elements.size(); for (size_t i = 0; i < numElements; ++i) { for (size_t j = 0; j < elements.size(); ++j) { if (m_elements[i].id == elements[j].id) { m_elements[i].placement = elements[j].placement; m_elements[i].changed = true; } } } } void CScene::Clear() { m_elements.clear(); m_lastIdByLayer.clear(); } void CScene::ClearLayer(int layer) { auto newEnd = std::remove_if(m_elements.begin(), m_elements.end(), [=](const SElement& e){ return e.layer == layer; }); m_elements.erase(newEnd, m_elements.end()); if (layer < m_lastIdByLayer.size()) { m_lastIdByLayer[layer] = layer << 24; } } void CScene::AddElement(const SElement& element) { ElementId& lastId = m_lastIdByLayer[element.layer]; AddElement(element, lastId); ++lastId; } void CScene::AddElement(const SElement& element, ElementId id) { m_elements.push_back(element); if (element.layer >= m_lastIdByLayer.size()) { while (m_lastIdByLayer.size() <= element.layer) { m_lastIdByLayer.push_back(m_lastIdByLayer.size() << 24); } } m_elements.back().id = aznumeric_cast(id); ElementId& lastId = m_lastIdByLayer[element.layer]; lastId = max(lastId, id) + 1; } QuatT CScene::GetSelectionTransform(ETransformationSpace space) const { if (space == SPACE_WORLD) { QuatT r(IDENTITY); vector selectedElements; GetSelectedElements(&selectedElements); if (selectedElements.size() == 1) { r = ElementToWorldSpace(selectedElements[0]); } else if (selectedElements.size() > 1) { r = GetParentSpace(selectedElements[0]) * selectedElements[0].placement.transform; for (size_t i = 1; i < selectedElements.size(); ++i) { SElement& e = selectedElements[i]; QuatT parentSpace = GetParentSpace(e); r.t = r.t + ElementToWorldSpace(e).t; } r.SetTranslation(r.t / float(selectedElements.size())); } if (!_finite(r.t.x) || !_finite(r.t.y) || !_finite(r.t.z)) { Q_ASSERT(false); r.SetIdentity(); } return r; } else if (space == SPACE_LOCAL) { return m_temporaryLocalDelta; } return IDENTITY; } bool CScene::SelectionCanBeMoved() const { return (GetSelectionCaps() & CAP_MOVE) != 0; } bool CScene::SelectionCanBeRotated() const { return (GetSelectionCaps() & CAP_ROTATE) != 0; } int CScene::GetSelectionCaps() const { int caps = 0; vector selectedElements; GetSelectedElements(&selectedElements); for (size_t i = 0; i < selectedElements.size(); ++i) { if (selectedElements[i].caps & CAP_MOVE && (caps & CAP_MOVE) != 0) { // enable rotation and scale of two or more positions caps |= CAP_ROTATE | CAP_SCALE; } caps |= selectedElements[i].caps; } ; return caps; } bool CScene::SetSelectionTransform(ETransformationSpace space, const QuatT& newTransform) { QuatT transform = GetSelectionTransform(SPACE_WORLD); QuatT deltaWorld; if (space == SPACE_WORLD) { deltaWorld = transform.GetInverted() * newTransform; } else { deltaWorld = newTransform; } bool hasElementsChanged = false; size_t numElements = m_elements.size(); for (size_t i = 0; i < numElements; ++i) { SElement& element = m_elements[i]; if (m_selection.Contains(element.id)) { QuatT parentSpace = GetParentSpace(element); QuatT newWorldTransform = parentSpace * element.placement.transform * deltaWorld; element.placement.transform = parentSpace.GetInverted() * newWorldTransform; element.changed = true; hasElementsChanged = true; } } return hasElementsChanged; } Vec3 CScene::GetSelectionSize() const { Vec3 size(1.0f, 1.0f, 1.0f); vector selectedElements; GetSelectedElements(&selectedElements); if (selectedElements.size() == 1) { size = selectedElements[0].placement.size; } else if (selectedElements.size() > 1) { AABB combinedSize(AABB::RESET); for (size_t i = 0; i < selectedElements.size(); ++i) { const SElement& element = m_elements[i]; AABB box(element.placement.size * -0.5f, element.placement.size * 0.5f); AABB transformedBox = AABB::CreateTransformedAABB(Matrix34(element.placement.transform), box); combinedSize.Add(transformedBox); } size = combinedSize.IsReset() ? Vec3(1.0f, 1.0f, 1.0f) : combinedSize.GetSize(); } return size; } bool CScene::SetSelectionSize(const Vec3& size) { bool hasElementsChanged = false; vector selectedElements; GetSelectedElements(&selectedElements); if (selectedElements.size() == 1) { selectedElements[0].placement.size = size; selectedElements[0].changed = true; hasElementsChanged = true; } return hasElementsChanged; } void CScene::SetSpaceProvider(ISpaceProvider* provider) { m_spaceProvider = provider; } QuatT CScene::GetParentSpace(const SElement& e) const { if (!m_spaceProvider) { return QuatT(IDENTITY); } QuatT result = m_spaceProvider->GetTransform(e.parentSpaceIndex); return result; } QuatT CScene::ElementToWorldSpace(const SElement& e) const { if (!m_spaceProvider) { return e.placement.transform; } QuatT parent = m_spaceProvider->GetTransform(e.parentSpaceIndex); if (e.parentOrientationSpaceIndex.m_attachmentCRC32 == -1 && e.parentOrientationSpaceIndex.m_jointCRC32 == -1) { return parent * e.placement.transform; } QuatT parentOrientation = m_spaceProvider->GetTransform(e.parentOrientationSpaceIndex); QuatT result = parent * e.placement.transform; result.q = parentOrientation.q * e.placement.transform.q; return result; } void CScene::WorldSpaceToElement(SElement* e, const QuatT& worldSpaceTransform) { if (!e) { return; } if (!m_spaceProvider) { e->placement.transform = worldSpaceTransform; return; } QuatT parent = m_spaceProvider->GetTransform(e->parentSpaceIndex); if (e->parentOrientationSpaceIndex.m_attachmentCRC32 == -1 && e->parentOrientationSpaceIndex.m_jointCRC32 == -1) { e->placement.transform = parent.GetInverted() * worldSpaceTransform; return; } QuatT parentOrientation = m_spaceProvider->GetTransform(e->parentOrientationSpaceIndex); e->placement.transform = parent.GetInverted() * worldSpaceTransform; e->placement.transform.q = parentOrientation.GetInverted().q * worldSpaceTransform.q; } bool CScene::IsLayerVisible(int layer) const { return (m_visibleLayerMask & (1 << layer)) != 0; } void CScene::SetVisibleLayerMask(unsigned int layerMask) { m_visibleLayerMask = layerMask; } }