You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Sandbox/Plugins/EditorCommon/ManipScene.cpp

1741 lines
56 KiB
C++

/*
* 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 "EditorCommon_precompiled.h"
#include "ManipScene.h"
#include "Serialization.h"
#include "QViewportEvents.h"
#include <IRenderer.h>
#include <IRenderAuxGeom.h>
#include <IPhysics.h>
#include <IPhysicsDebugRenderer.h>
#include <IStatObj.h>
#include <I3DEngine.h>
#include "../EditorCommon/QViewport.h"
#include <Include/EditorCoreAPI.h>
// 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 <QSet>
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, [[maybe_unused]] 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([[maybe_unused]] 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();
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);
if (element.shape == SHAPE_BOX)
{
OBB obb;
obb.m33 = Matrix33(IDENTITY);
obb.c = element.placement.center;
obb.h = element.placement.size * 0.5f;
bool isHighlighted2 = highlightedItem == element.id;
ColorB color;
if (isHighlighted2)
{
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 || isHighlighted2)
{
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 isHighlighted2 = highlightedItem == element.id;
ColorB color;
if (isHighlighted2)
{
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 || isHighlighted2)
{
drawBB = true;
}
if (drawBB)
{
aux->DrawOBB(obb, transformM, false, edgeColor, eBBD_Faceted);
}
}
}
void CScene::OnViewportRender(const SRenderContext& rc)
{
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::TYPE_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<QKeySequence> 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([[maybe_unused]] 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, [[maybe_unused]] 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([[maybe_unused]] 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([[maybe_unused]] const SMouseEvent& ev) override
{
m_scene->SignalElementsChanged(m_layerBits);
}
};
template<class T>
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<int>(amin.x, amax.x).Intersects(SRange<int>(bmin.x, bmax.x)))
{
return false;
}
if (!SRange<int>(amin.y, amax.y).Intersects(SRange<int>(bmin.y, bmax.y)))
{
return false;
}
return true;
}
static bool OBBOverlapsSelectionFrustum(const OBB& box, Vec2i rectMin, Vec2i rectMax, [[maybe_unused]] 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<float> 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<float> 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<const SElement*>* out, Vec2i point1, Vec2i point2, QViewport* viewport,
const vector<SElement>& elements, [[maybe_unused]] 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& m2 = elements[j];
if (m2.hidden)
{
continue;
}
if (!scene->IsLayerVisible(m2.layer) && !scene->Selection().Contains(m2.id))
{
continue;
}
if (ElementInFrustum(m2, Vec2i(minX, minY), Vec2i(maxX, maxY), frustum, &view, scene))
{
out->push_back(&m2);
}
}
}
static void GetSelectionInRect(SSelectionSet* selection, Vec2i p1, Vec2i p2, QViewport* viewport, const vector<SElement>& elements, ISpaceProvider* spaceProvider, CScene* scene)
{
vector<const SElement*> 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, [[maybe_unused]] 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([[maybe_unused]] 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<int>(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::TYPE_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<int>(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::TYPE_RELEASE)
{
if (m_mouseDragHandler.get())
{
m_mouseDragHandler->End(ev);
m_mouseDragHandler.reset();
ev.viewport->ReleaseMouse();
}
}
else if (ev.type == SMouseEvent::TYPE_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", "<P");
}
Ang3 rotation(Ang3::GetAnglesXYZ(transform.q));
Ang3 rotationOld = rotation;
if ((caps & CAP_ROTATE))
{
ar(Serialization::RadiansAsDeg(rotation), "rotation", "<R");
}
Vec3 size = m_scene->GetSelectionSize();
Vec3 sizeOld = size;
if ((caps & CAP_SCALE))
{
ar(size, "size", "<S");
}
if (ar.IsInput())
{
bool transformChanged = false;
bool sizeChanged = false;
Vec3 oldPosition = transform.t;
if (position.IsValid() && oldPosition.IsValid() && position != oldPosition)
{
transform.SetTranslation(position);
transformChanged = true;
}
if (rotation.IsValid() && rotationOld.IsValid() && rotation != rotationOld)
{
transform = QuatT(Quat(rotation), transform.t);
transformChanged = true;
}
if (size.IsValid() && sizeOld.IsValid() && size != sizeOld)
{
sizeChanged = true;
}
if (transformChanged)
{
m_scene->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", "<Manipulator Space");
}
else
{
ar(SSpaceSelector(m_transformationSpace), "transformationSpace");
}
ar(m_showGizmo, "showGizmo", "Show Manipulation Gizmo");
if (!m_selection.IsEmpty())
{
STransformBox transform(this);
ar(transform, "transform", "Global Transform");
}
}
void CScene::GetSelectedElements(SElements* elements) const
{
elements->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<int>(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<SElement> 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<SElement> 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<SElement> 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<SElement> 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;
}
}