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.
1061 lines
39 KiB
C++
1061 lines
39 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.
|
|
*
|
|
*/
|
|
#include "EditorDefs.h"
|
|
|
|
#include "RotateTool.h"
|
|
|
|
// AzToolsFramework
|
|
#include <AzToolsFramework/Entity/EditorEntityTransformBus.h>
|
|
|
|
// Editor
|
|
#include "Objects/SelectionGroup.h"
|
|
#include "NullEditTool.h"
|
|
#include "Viewport.h"
|
|
#include "Grid.h"
|
|
#include "ViewManager.h"
|
|
#include "Objects/BaseObject.h"
|
|
|
|
|
|
// This constant is used with GetScreenScaleFactor and was found experimentally.
|
|
static const float kViewDistanceScaleFactor = 0.06f;
|
|
|
|
const GUID& CRotateTool::GetClassID()
|
|
{
|
|
// {A50E5B95-05B9-41A3-8D8E-BDA3E930A396}
|
|
static const GUID guid = {
|
|
0xA50E5B95, 0x05B9, 0x41A3, { 0x8D, 0x8E, 0xBD, 0xA3, 0xE9, 0x30, 0xA3, 0x96 }
|
|
};
|
|
return guid;
|
|
}
|
|
|
|
//! This method returns the human readable name of the class.
|
|
//! This method returns Category of this class, Category is specifying where this tool class fits best in create panel.
|
|
void CRotateTool::RegisterTool(CRegistrationContext& rc)
|
|
{
|
|
rc.pClassFactory->RegisterClass(new CQtViewClass<CRotateTool>("EditTool.Rotate", "Select", ESYSTEM_CLASS_EDITTOOL));
|
|
}
|
|
|
|
CRotateTool::CRotateTool(CBaseObject* pObject, QWidget* parent /*= nullptr*/)
|
|
: CObjectMode(parent)
|
|
, m_initialViewAxisAngleRadians(0.f)
|
|
, m_angleToCursor(0.f)
|
|
, m_highlightAxis(AxisNone)
|
|
, m_draggingMouse(false)
|
|
, m_lastPosition(0, 0)
|
|
, m_rotationAngles(0, 0, 0)
|
|
, m_object(pObject)
|
|
, m_bTransformChanged(false)
|
|
, m_totalRotationAngle(0.f)
|
|
, m_basisAxisRadius(4.f)
|
|
, m_viewAxisRadius(5.f)
|
|
, m_arcRotationStepRadians(DEG2RAD(5.f))
|
|
{
|
|
m_axes[AxisX] = RotationDrawHelper::Axis(Col_Red, Col_Yellow);
|
|
m_axes[AxisY] = RotationDrawHelper::Axis(Col_Green, Col_Yellow);
|
|
m_axes[AxisZ] = RotationDrawHelper::Axis(Col_Blue, Col_Yellow);
|
|
m_axes[AxisView] = RotationDrawHelper::Axis(Col_White, Col_Yellow);
|
|
|
|
if (m_object)
|
|
{
|
|
m_object->AddEventListener(this);
|
|
}
|
|
|
|
GetIEditor()->GetObjectManager()->SetSelectCallback(this);
|
|
}
|
|
|
|
bool CRotateTool::OnSelectObject(CBaseObject* object)
|
|
{
|
|
m_object = object;
|
|
if (m_object)
|
|
{
|
|
m_object->AddEventListener(this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CRotateTool::CanSelectObject([[maybe_unused]] CBaseObject* object)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void CRotateTool::OnObjectEvent(CBaseObject* object, int event)
|
|
{
|
|
if (event == CBaseObject::ON_DELETE || event == CBaseObject::ON_UNSELECT)
|
|
{
|
|
if (m_object && m_object == object)
|
|
{
|
|
m_object->RemoveEventListener(this);
|
|
m_object = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CRotateTool::Display(DisplayContext& dc)
|
|
{
|
|
if (!m_object)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool visible =
|
|
!m_object->IsHidden()
|
|
&& !m_object->IsFrozen()
|
|
&& m_object->IsSelected();
|
|
|
|
if (!visible)
|
|
{
|
|
GetIEditor()->SetEditTool(new NullEditTool());
|
|
return;
|
|
}
|
|
|
|
RotationDrawHelper::DisplayContextScope displayContextScope(dc);
|
|
m_hc.camera = dc.camera;
|
|
m_hc.view = dc.view;
|
|
m_hc.b2DViewport = static_cast<CViewport*>(dc.view)->GetType() != ET_ViewportCamera;
|
|
dc.SetLineWidth(m_lineThickness);
|
|
|
|
// Calculate the screen space position from which we cast a ray (center of viewport).
|
|
int viewportWidth = 0;
|
|
int viewportHeight = 0;
|
|
dc.view->GetDimensions(&viewportWidth, &viewportHeight);
|
|
m_hc.point2d = QPoint(viewportWidth / 2, viewportHeight / 2);
|
|
|
|
// Calculate the ray from the camera position to the selection.
|
|
dc.view->ViewToWorldRay(m_hc.point2d, m_hc.raySrc, m_hc.rayDir);
|
|
|
|
Matrix34 objectTransform = GetTransform(GetIEditor()->GetReferenceCoordSys(), dc.view);
|
|
|
|
AffineParts ap;
|
|
ap.Decompose(objectTransform);
|
|
|
|
Vec3 position = ap.pos;
|
|
CSelectionGroup* selection = GetIEditor()->GetSelection();
|
|
if (selection->GetCount() > 1)
|
|
{
|
|
position = selection->GetCenter();
|
|
}
|
|
|
|
float screenScale = GetScreenScale(dc.view, dc.camera);
|
|
|
|
// X axis arc
|
|
Vec3 cameraViewDir = (m_hc.raySrc - position).GetNormalized();
|
|
float cameraAngle = atan2f(cameraViewDir.y, cameraViewDir.x);
|
|
m_axes[AxisX].Draw(dc, position, ap.rot.GetColumn0(), cameraAngle, m_arcRotationStepRadians, m_basisAxisRadius, m_highlightAxis == AxisX, m_object, screenScale);
|
|
|
|
// Y axis arc
|
|
cameraAngle = atan2f(-cameraViewDir.z, cameraViewDir.x);
|
|
m_axes[AxisY].Draw(dc, position, ap.rot.GetColumn1(), cameraAngle, m_arcRotationStepRadians, m_basisAxisRadius, m_highlightAxis == AxisY, m_object, screenScale);
|
|
|
|
// View direction axis
|
|
Vec3 cameraPos = dc.camera->GetPosition();
|
|
|
|
Vec3 axis = cameraPos - position;
|
|
axis.NormalizeSafe();
|
|
|
|
// Z axis arc
|
|
cameraAngle = atan2f(axis.y, axis.x);
|
|
m_axes[AxisZ].Draw(dc, position, objectTransform.GetColumn2().GetNormalized(), cameraAngle, m_arcRotationStepRadians, m_basisAxisRadius, m_highlightAxis == AxisZ, m_object, screenScale);
|
|
|
|
// FIXME: currently, rotating multiple selections using the view axis may result in severe rotation artifacts, it's necessary to make sure
|
|
// the calculated rotation angle is smooth.
|
|
if (!m_hc.b2DViewport && selection->GetCount() == 1 || m_object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
// Draw view direction axis
|
|
dc.SetColor(m_highlightAxis == AxisView ? Col_Yellow : Col_White);
|
|
|
|
cameraViewDir = m_hc.camera->GetViewdir().normalized();
|
|
dc.DrawArc(position, m_viewAxisRadius * GetScreenScale(dc.view, dc.camera), 0, 360.f, RAD2DEG(m_arcRotationStepRadians), cameraViewDir);
|
|
}
|
|
|
|
// Draw angle decorator
|
|
if (RotationControlConfiguration::Get().RotationControl_DrawDecorators)
|
|
{
|
|
DrawAngleDecorator(dc);
|
|
}
|
|
|
|
// Display total rotation angle in degrees.
|
|
if (!m_hc.b2DViewport && fabs(m_totalRotationAngle) > FLT_EPSILON)
|
|
{
|
|
QString label;
|
|
label = QString::number(RAD2DEG(m_totalRotationAngle), 'f', 2);
|
|
|
|
const float textScale = 1.5f;
|
|
const ColorF textBackground = ColorF(0.2f, 0.2f, 0.2f, 0.6f);
|
|
|
|
if (m_object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
dc.DrawTextLabel(ap.pos, textScale, label.toUtf8().data());
|
|
}
|
|
else
|
|
{
|
|
dc.DrawTextOn2DBox(ap.pos, label.toUtf8().data(), textScale, Col_White, textBackground);
|
|
}
|
|
}
|
|
|
|
// Draw debug diagnostics
|
|
if (RotationControlConfiguration::Get().RotationControl_DebugHitTesting)
|
|
{
|
|
DrawHitTestGeometry(dc, m_hc);
|
|
}
|
|
|
|
// Draw debug tracking of the view direction angle
|
|
if (RotationControlConfiguration::Get().RotationControl_AngleTracking)
|
|
{
|
|
DrawViewDirectionAngleTracking(dc, m_hc);
|
|
}
|
|
}
|
|
|
|
void CRotateTool::DrawAngleDecorator(DisplayContext& dc)
|
|
{
|
|
if (m_highlightAxis == AxisView)
|
|
{
|
|
//Vec3 cameraViewDir = dc.view->GetViewTM().GetColumn1().GetNormalized();
|
|
Vec3 cameraViewDir = dc.camera->GetViewMatrix().GetColumn1().GetNormalized(); //Get the viewDir from the camera instead of from the view
|
|
// FIXME: The angle and sweep calculation here is incorrect.
|
|
float cameraAngle = atan2f(cameraViewDir.y, -cameraViewDir.x);
|
|
float angle = m_initialViewAxisAngleRadians - cameraAngle - (g_PI / 2);
|
|
float angleDelta = (m_angleToCursor - g_PI2 * floor(m_initialViewAxisAngleRadians / g_PI2)) - (m_initialViewAxisAngleRadians - (cameraAngle - (g_PI / 2)));
|
|
|
|
RotationDrawHelper::AngleDecorator::Draw(dc, m_object->GetWorldPos(), cameraViewDir, m_initialViewAxisAngleRadians, angleDelta, m_arcRotationStepRadians, m_viewAxisRadius, GetScreenScale(dc.view, dc.camera));
|
|
}
|
|
else
|
|
{
|
|
if (fabs(m_totalRotationAngle) > FLT_EPSILON)
|
|
{
|
|
float screenScale = GetScreenScale(dc.view, dc.camera);
|
|
switch (m_highlightAxis)
|
|
{
|
|
case AxisX:
|
|
RotationDrawHelper::AngleDecorator::Draw(dc, m_object->GetWorldPos(), m_object->GetRotation().GetColumn0(), m_initialViewAxisAngleRadians, m_totalRotationAngle, m_arcRotationStepRadians, m_basisAxisRadius, screenScale);
|
|
break;
|
|
case AxisY:
|
|
RotationDrawHelper::AngleDecorator::Draw(dc, m_object->GetWorldPos(), m_object->GetRotation().GetColumn1(), m_initialViewAxisAngleRadians, m_totalRotationAngle, m_arcRotationStepRadians, m_basisAxisRadius, screenScale);
|
|
break;
|
|
case AxisZ:
|
|
RotationDrawHelper::AngleDecorator::Draw(dc, m_object->GetWorldPos(), m_object->GetRotation().GetColumn2(), m_initialViewAxisAngleRadians, m_totalRotationAngle, m_arcRotationStepRadians, m_basisAxisRadius, screenScale);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CRotateTool::HitTest(CBaseObject* object, HitContext& hc)
|
|
{
|
|
if (!m_object)
|
|
{
|
|
return CObjectMode::HitTest(object, hc);
|
|
}
|
|
m_hc = hc;
|
|
m_highlightAxis = AxisNone;
|
|
|
|
float screenScale = GetScreenScale(hc.view, hc.camera);
|
|
|
|
// Determine intersection with the axis view direction.
|
|
CSelectionGroup* selection = GetIEditor()->GetSelection();
|
|
if (!m_hc.b2DViewport && selection->GetCount() == 1 || m_object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
if (m_axes[AxisView].HitTest(object, hc, m_viewAxisRadius, m_arcRotationStepRadians, hc.camera ? hc.camera->GetViewMatrix().GetInverted().GetColumn1() : hc.view->GetViewTM().GetColumn1(), screenScale))
|
|
{
|
|
m_highlightAxis = AxisView;
|
|
GetIEditor()->SetAxisConstraints(AXIS_XYZ);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Determine any intersection with a major axis.
|
|
AffineParts ap;
|
|
ap.Decompose(GetTransform(GetIEditor()->GetReferenceCoordSys(), hc.view));
|
|
|
|
if (m_axes[AxisX].HitTest(object, hc, m_basisAxisRadius, m_arcRotationStepRadians, ap.rot.GetColumn0(), screenScale))
|
|
{
|
|
m_highlightAxis = AxisX;
|
|
GetIEditor()->SetAxisConstraints(AXIS_X);
|
|
return true;
|
|
}
|
|
|
|
if (m_axes[AxisY].HitTest(object, hc, m_basisAxisRadius, m_arcRotationStepRadians, ap.rot.GetColumn1(), screenScale))
|
|
{
|
|
m_highlightAxis = AxisY;
|
|
GetIEditor()->SetAxisConstraints(AXIS_Y);
|
|
return true;
|
|
}
|
|
|
|
if (m_axes[AxisZ].HitTest(object, hc, m_basisAxisRadius, m_arcRotationStepRadians, ap.rot.GetColumn2(), screenScale))
|
|
{
|
|
m_highlightAxis = AxisZ;
|
|
GetIEditor()->SetAxisConstraints(AXIS_Z);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CRotateTool::DeleteThis()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
bool CRotateTool::OnKeyDown([[maybe_unused]] CViewport* view, uint32 nChar, [[maybe_unused]] uint32 nRepCnt, [[maybe_unused]] uint32 nFlags)
|
|
{
|
|
if (nChar == VK_ESCAPE)
|
|
{
|
|
GetIEditor()->GetObjectManager()->ClearSelection();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Matrix34 CRotateTool::GetTransform(RefCoordSys referenceCoordinateSystem, IDisplayViewport* view)
|
|
{
|
|
Matrix34 objectTransform = Matrix34::CreateIdentity();
|
|
if (m_object)
|
|
{
|
|
switch (referenceCoordinateSystem)
|
|
{
|
|
case COORDS_VIEW:
|
|
if (view)
|
|
{
|
|
objectTransform = view->GetViewTM();
|
|
}
|
|
objectTransform.SetTranslation(m_object->GetWorldTM().GetTranslation());
|
|
break;
|
|
case COORDS_LOCAL:
|
|
objectTransform = m_object->GetWorldTM();
|
|
break;
|
|
case COORDS_PARENT:
|
|
if (m_object->GetParent())
|
|
{
|
|
Matrix34 parentTM = m_object->GetParent()->GetWorldTM();
|
|
parentTM.SetTranslation(m_object->GetWorldTM().GetTranslation());
|
|
objectTransform = parentTM;
|
|
}
|
|
else
|
|
{
|
|
objectTransform.SetTranslation(m_object->GetWorldTM().GetTranslation());
|
|
}
|
|
break;
|
|
case COORDS_WORLD:
|
|
objectTransform.SetTranslation(m_object->GetWorldTM().GetTranslation());
|
|
break;
|
|
}
|
|
}
|
|
return objectTransform;
|
|
}
|
|
|
|
float CRotateTool::CalculateOrientation(const QPoint& p1, const QPoint& p2, const QPoint& p3)
|
|
{
|
|
// Source: https://www.geeksforgeeks.org/orientation-3-ordered-points/
|
|
float c = (p2.y() - p1.y()) * (p3.x() - p2.x()) - (p3.y() - p2.y()) * (p2.x() - p1.x());
|
|
return c > 0 ? 1.0f : -1.0f;
|
|
}
|
|
|
|
CRotateTool::~CRotateTool()
|
|
{
|
|
if (m_object)
|
|
{
|
|
m_object->RemoveEventListener(this);
|
|
}
|
|
GetIEditor()->GetObjectManager()->SetSelectCallback(nullptr);
|
|
}
|
|
|
|
bool CRotateTool::OnLButtonDown(CViewport* view, int nFlags, const QPoint& p)
|
|
{
|
|
QPoint point = p;
|
|
m_hc.view = view;
|
|
m_hc.b2DViewport = view->GetType() != ET_ViewportCamera;
|
|
m_hc.point2d = point;
|
|
if (nFlags == OBJFLAG_IS_PARTICLE)
|
|
{
|
|
view->setHitcontext(point, m_hc.raySrc, m_hc.rayDir);
|
|
}
|
|
else
|
|
{
|
|
view->ViewToWorldRay(point, m_hc.raySrc, m_hc.rayDir);
|
|
}
|
|
|
|
if (m_hc.object && m_hc.object != m_object)
|
|
{
|
|
GetIEditor()->ClearSelection();
|
|
return CObjectMode::OnLButtonDown(view, nFlags, point);
|
|
}
|
|
|
|
if (m_highlightAxis != AxisNone)
|
|
{
|
|
view->BeginUndo();
|
|
view->CaptureMouse();
|
|
view->SetCurrentCursor(STD_CURSOR_ROTATE);
|
|
|
|
m_draggingMouse = true;
|
|
|
|
// Store the starting drag angle when we first click the mouse, we will need this to know
|
|
// how much of the rotation we need to apply.
|
|
if (m_highlightAxis == AxisView)
|
|
{
|
|
Vec3 cameraViewDir = m_hc.camera->GetViewdir().GetNormalized();
|
|
float cameraAngle = atan2f(cameraViewDir.y, -cameraViewDir.x);
|
|
m_initialViewAxisAngleRadians = m_angleToCursor - cameraAngle - (g_PI / 2);
|
|
m_initialViewAxisAngleRadians -= static_cast<float>(g_PI);
|
|
}
|
|
|
|
m_lastPosition = point;
|
|
m_rotationAngles = Ang3(0, 0, 0);
|
|
|
|
AzToolsFramework::EntityIdList selectedEntities;
|
|
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
|
|
selectedEntities,
|
|
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::GetSelectedEntities);
|
|
|
|
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
|
|
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanging,
|
|
selectedEntities);
|
|
|
|
return true;
|
|
}
|
|
|
|
return CObjectMode::OnLButtonDown(view, nFlags, point);
|
|
}
|
|
|
|
bool CRotateTool::OnLButtonUp(CViewport* view, int nFlags, const QPoint& p)
|
|
{
|
|
QPoint point = p;
|
|
if (nFlags == OBJFLAG_IS_PARTICLE)
|
|
{
|
|
view->setHitcontext(point, m_hc.raySrc, m_hc.rayDir);
|
|
}
|
|
else
|
|
{
|
|
view->ViewToWorldRay(point, m_hc.raySrc, m_hc.rayDir);
|
|
}
|
|
|
|
if (m_draggingMouse)
|
|
{
|
|
// We are no longer dragging the mouse, so we will release it and reset any state variables.
|
|
{
|
|
AzToolsFramework::ScopedUndoBatch undo("Rotate");
|
|
}
|
|
view->AcceptUndo("Rotate Selection");
|
|
view->ReleaseMouse();
|
|
view->SetCurrentCursor(STD_CURSOR_DEFAULT);
|
|
|
|
m_draggingMouse = false;
|
|
m_totalRotationAngle = 0.f;
|
|
m_initialViewAxisAngleRadians = 0.f;
|
|
m_angleToCursor = 0.f;
|
|
|
|
// Apply the transform changes to the selection.
|
|
if (m_bTransformChanged)
|
|
{
|
|
CSelectionGroup* pSelection = GetIEditor()->GetSelection();
|
|
if (pSelection)
|
|
{
|
|
pSelection->FinishChanges();
|
|
}
|
|
|
|
m_bTransformChanged = false;
|
|
|
|
view->ResetSelectionRegion();
|
|
// Reset selected rectangle.
|
|
view->SetSelectionRectangle(QRect());
|
|
view->SetAxisConstrain(GetIEditor()->GetAxisConstrains());
|
|
|
|
AzToolsFramework::EntityIdList selectedEntities;
|
|
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
|
|
selectedEntities,
|
|
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::GetSelectedEntities);
|
|
|
|
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
|
|
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
|
|
selectedEntities);
|
|
}
|
|
}
|
|
|
|
return CObjectMode::OnLButtonUp(view, nFlags, point);
|
|
}
|
|
|
|
bool CRotateTool::OnMouseMove(CViewport* view, int nFlags, const QPoint& p)
|
|
{
|
|
QPoint point = p;
|
|
if (!m_object)
|
|
{
|
|
return CObjectMode::OnMouseMove(view, nFlags, point);
|
|
}
|
|
|
|
// Prevent the opening of the context menu during a mouse move.
|
|
m_openContext = false;
|
|
|
|
// We calculate the mouse drag direction vector's angle from the object to the mouse position.
|
|
QPoint objectCenter;
|
|
if (nFlags != OBJFLAG_IS_PARTICLE)
|
|
{
|
|
objectCenter = view->WorldToView(GetIEditor()->GetSelection()->GetCenter());
|
|
}
|
|
else
|
|
if (parent() && parent()->isWidgetType())
|
|
{
|
|
QWidget *wParent = static_cast<QWidget*>(parent());
|
|
|
|
// HACK: This is only valid for the particle editor and needs refactored.
|
|
const QRect rect = wParent->contentsRect();
|
|
objectCenter = view->WorldToViewParticleEditor(m_object->GetWorldPos(), rect.width(), rect.height());
|
|
}
|
|
|
|
Vec2 dragDirection = Vec2(point.x() - objectCenter.x(), point.y() - objectCenter.y());
|
|
dragDirection.Normalize();
|
|
|
|
float angleToCursor = (atan2f(dragDirection.y, dragDirection.x));
|
|
m_angleToCursor = angleToCursor - g_PI2 * floor(angleToCursor / g_PI2);
|
|
|
|
if (m_draggingMouse)
|
|
{
|
|
GetIEditor()->RestoreUndo();
|
|
|
|
view->SetCurrentCursor(STD_CURSOR_ROTATE);
|
|
|
|
RefCoordSys referenceCoordSys = GetIEditor()->GetReferenceCoordSys();
|
|
|
|
if (m_highlightAxis == AxisView)
|
|
{
|
|
// Calculate the angular difference between the starting rotation angle, taking into account the camera's angle to ensure a smooth rotation.
|
|
Vec3 cameraViewDir = m_hc.camera->GetViewdir();
|
|
float cameraAngle = atan2f(cameraViewDir.y, cameraViewDir.x);
|
|
float angleDelta = (m_angleToCursor - g_PI2 * floor(m_initialViewAxisAngleRadians / g_PI2)) - (m_initialViewAxisAngleRadians - (cameraAngle - (g_PI / 2)));
|
|
|
|
// Snap the angle is necessary
|
|
angleDelta = view->GetViewManager()->GetGrid()->SnapAngle(RAD2DEG(angleDelta));
|
|
|
|
if (nFlags != OBJFLAG_IS_PARTICLE)
|
|
{
|
|
Matrix34 viewRotation = Matrix34::CreateRotationAA(DEG2RAD(angleDelta), cameraViewDir);
|
|
GetIEditor()->GetSelection()->Rotate(viewRotation, COORDS_WORLD);
|
|
}
|
|
else
|
|
{
|
|
Quat quatRotation = Quat::CreateRotationAA(DEG2RAD(angleDelta), cameraViewDir);
|
|
m_object->SetRotation(quatRotation);
|
|
}
|
|
|
|
m_bTransformChanged = true;
|
|
}
|
|
else
|
|
if (m_highlightAxis != AxisNone)
|
|
{
|
|
float distanceMoved = (point - m_lastPosition).manhattanLength(); // screen-space distance dragged
|
|
float distanceToCenter = (m_lastPosition - objectCenter).manhattanLength(); // screen-space distance to object center
|
|
float roationDelta = RAD2DEG(atan2f(distanceMoved, distanceToCenter)); // unsigned rotation angle
|
|
float orientation = CalculateOrientation(objectCenter, m_lastPosition, point); // Calculate if rotation dragging gizmo clockwise or counter-clockwise
|
|
|
|
m_lastPosition = point;
|
|
|
|
// Calculate orientation of the object's axis towards camera
|
|
Vec3 directionToObject = (GetIEditor()->GetSelection()->GetCenter() - m_hc.camera->GetMatrix().GetTranslation()).normalize();
|
|
|
|
float directionX = 1.0f;
|
|
float directionY = 1.0f;
|
|
float directionZ = 1.0f;
|
|
|
|
switch (referenceCoordSys)
|
|
{
|
|
case COORDS_LOCAL:
|
|
directionX = directionToObject.Dot(m_object->GetWorldTM().GetColumn0()) > 0 ? -1.0f : 1.0f;
|
|
directionY = directionToObject.Dot(m_object->GetWorldTM().GetColumn1()) > 0 ? -1.0f : 1.0f;
|
|
directionZ = directionToObject.Dot(m_object->GetWorldTM().GetColumn2()) > 0 ? -1.0f : 1.0f;
|
|
break;
|
|
case COORDS_PARENT:
|
|
if (m_object->GetParent())
|
|
{
|
|
directionX = directionToObject.Dot(m_object->GetParent()->GetWorldTM().GetColumn0()) > 0 ? -1.0f : 1.0f;
|
|
directionY = directionToObject.Dot(m_object->GetParent()->GetWorldTM().GetColumn1()) > 0 ? -1.0f : 1.0f;
|
|
directionZ = directionToObject.Dot(m_object->GetParent()->GetWorldTM().GetColumn2()) > 0 ? -1.0f : 1.0f;
|
|
}
|
|
else
|
|
{
|
|
directionX = directionToObject.Dot(m_object->GetWorldTM().GetColumn0()) > 0 ? -1.0f : 1.0f;
|
|
directionY = directionToObject.Dot(m_object->GetWorldTM().GetColumn1()) > 0 ? -1.0f : 1.0f;
|
|
directionZ = directionToObject.Dot(m_object->GetWorldTM().GetColumn2()) > 0 ? -1.0f : 1.0f;
|
|
}
|
|
break;
|
|
case COORDS_VIEW:
|
|
case COORDS_WORLD:
|
|
directionX = directionToObject.Dot(Vec3(1, 0, 0)) > 0 ? -1.0f : 1.0f;
|
|
directionY = directionToObject.Dot(Vec3(0, 1, 0)) > 0 ? -1.0f : 1.0f;
|
|
directionZ = directionToObject.Dot(Vec3(0, 0, 1)) > 0 ? -1.0f : 1.0f;
|
|
break;
|
|
}
|
|
|
|
switch (m_highlightAxis)
|
|
{
|
|
case AxisX:
|
|
m_rotationAngles.x += roationDelta * directionX * orientation;
|
|
break;
|
|
case AxisY:
|
|
m_rotationAngles.y += roationDelta * directionY * orientation;
|
|
break;
|
|
case AxisZ:
|
|
m_rotationAngles.z += roationDelta * directionZ * orientation;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Snap the angle if necessary
|
|
m_rotationAngles = view->GetViewManager()->GetGrid()->SnapAngle(m_rotationAngles);
|
|
|
|
// Compute the total amount rotated
|
|
Vec3 vDragValue = Vec3(m_rotationAngles);
|
|
m_totalRotationAngle = DEG2RAD(vDragValue.len());
|
|
|
|
// Apply the rotation
|
|
if (nFlags != OBJFLAG_IS_PARTICLE)
|
|
{
|
|
GetIEditor()->GetSelection()->Rotate(m_rotationAngles, referenceCoordSys);
|
|
}
|
|
else
|
|
{
|
|
Quat currentRotation = (m_object->GetRotation());
|
|
Quat rotateTM = currentRotation * Quat::CreateRotationXYZ(DEG2RAD(-m_rotationAngles / 50.0f));
|
|
m_object->SetRotation(rotateTM);
|
|
}
|
|
|
|
m_bTransformChanged = fabs(m_totalRotationAngle) > FLT_EPSILON;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are not yet dragging the mouse, do the hit testing to highlight the axis the mouse is over.
|
|
m_hc.view = view;
|
|
m_hc.b2DViewport = view->GetType() != ET_ViewportCamera;
|
|
m_hc.point2d = point;
|
|
|
|
if (nFlags != OBJFLAG_IS_PARTICLE)
|
|
{
|
|
view->ViewToWorldRay(point, m_hc.raySrc, m_hc.rayDir);
|
|
}
|
|
else
|
|
{
|
|
view->setHitcontext(point, m_hc.raySrc, m_hc.rayDir);
|
|
}
|
|
|
|
if (HitTest(m_object, m_hc))
|
|
{
|
|
// Display a cursor that makes it clear to the user that he is over an axis that can be rotated.
|
|
view->SetCurrentCursor(STD_CURSOR_ROTATE);
|
|
}
|
|
else
|
|
{
|
|
// Nothing has been hit, reset the cursor back to default in case it was changed previously.
|
|
view->SetCurrentCursor(STD_CURSOR_DEFAULT);
|
|
}
|
|
}
|
|
|
|
// We always consider the rotation tool's OnMove event handled
|
|
return true;
|
|
}
|
|
|
|
float CRotateTool::GetScreenScale(IDisplayViewport* view, CCamera* camera /*=nullptr*/)
|
|
{
|
|
Matrix34 objectTransform = GetTransform(GetIEditor()->GetReferenceCoordSys(), view);
|
|
|
|
AffineParts ap;
|
|
ap.Decompose(objectTransform);
|
|
|
|
if (m_object && m_object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
return view->GetScreenScaleFactor(*camera, ap.pos) * kViewDistanceScaleFactor;
|
|
}
|
|
|
|
return static_cast<CViewport*>(view)->GetScreenScaleFactor(ap.pos) * kViewDistanceScaleFactor;
|
|
}
|
|
|
|
void CRotateTool::DrawHitTestGeometry(DisplayContext& dc, HitContext& hc)
|
|
{
|
|
AffineParts ap;
|
|
ap.Decompose(GetTransform(GetIEditor()->GetReferenceCoordSys(), dc.view));
|
|
|
|
Vec3 position = ap.pos;
|
|
CSelectionGroup* selection = GetIEditor()->GetSelection();
|
|
if (selection->GetCount() > 1 && !m_object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
position = selection->GetCenter();
|
|
}
|
|
|
|
float screenScale = GetScreenScale(dc.view, dc.camera);
|
|
|
|
// Draw debug test surface for each axis.
|
|
m_axes[AxisX].DebugDrawHitTestSurface(dc, hc, position, m_basisAxisRadius, m_arcRotationStepRadians, ap.rot.GetColumn0(), screenScale);
|
|
m_axes[AxisY].DebugDrawHitTestSurface(dc, hc, position, m_basisAxisRadius, m_arcRotationStepRadians, ap.rot.GetColumn1(), screenScale);
|
|
m_axes[AxisZ].DebugDrawHitTestSurface(dc, hc, position, m_basisAxisRadius, m_arcRotationStepRadians, ap.rot.GetColumn2(), screenScale);
|
|
|
|
// We don't render the view axis rotation for multiple selection.
|
|
if (!hc.b2DViewport && selection->GetCount() == 1)
|
|
{
|
|
Vec3 cameraViewDir = hc.view->GetViewTM().GetColumn1().GetNormalized();
|
|
m_axes[AxisView].DebugDrawHitTestSurface(dc, hc, position, m_viewAxisRadius, m_arcRotationStepRadians, cameraViewDir, screenScale);
|
|
}
|
|
}
|
|
|
|
void CRotateTool::DrawViewDirectionAngleTracking(DisplayContext& dc, HitContext& hc)
|
|
{
|
|
Vec3 a;
|
|
Vec3 b;
|
|
|
|
// Calculate a basis for the camera view direction.
|
|
Vec3 cameraViewDir = hc.view->GetViewTM().GetColumn1().GetNormalized();
|
|
GetBasisVectors(cameraViewDir, a, b);
|
|
|
|
// Calculates the camera view direction angle.
|
|
float angle = m_angleToCursor;
|
|
float cameraAngle = atan2f(cameraViewDir.y, -cameraViewDir.x);
|
|
|
|
// Ensures the angle remains camera aligned.
|
|
angle -= cameraAngle - (g_PI / 2);
|
|
|
|
// The position will be either the object's center or the selection's center.
|
|
Vec3 position = GetTransform(GetIEditor()->GetReferenceCoordSys(), dc.view).GetTranslation();
|
|
CSelectionGroup* selection = GetIEditor()->GetSelection();
|
|
if (selection->GetCount() > 1 && !m_object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
position = selection->GetCenter();
|
|
}
|
|
|
|
float screenScale = GetScreenScale(dc.view, dc.camera);
|
|
|
|
const float cosAngle = cos(angle);
|
|
const float sinAngle = sin(angle);
|
|
|
|
// The resulting position will be in a circular orientation based on the resulting angle.
|
|
Vec3 p0;
|
|
p0.x = position.x + (cosAngle * a.x + sinAngle * b.x) * m_viewAxisRadius * screenScale;
|
|
p0.y = position.y + (cosAngle * a.y + sinAngle * b.y) * m_viewAxisRadius * screenScale;
|
|
p0.z = position.z + (cosAngle * a.z + sinAngle * b.z) * m_viewAxisRadius * screenScale;
|
|
|
|
const float ballRadius = 0.1f * screenScale;
|
|
dc.SetColor(Col_Magenta);
|
|
dc.DrawBall(p0, ballRadius);
|
|
}
|
|
|
|
namespace RotationDrawHelper
|
|
{
|
|
Axis::Axis(const ColorF& defaultColor, const ColorF& highlightColor)
|
|
{
|
|
m_colors[StateDefault] = defaultColor;
|
|
m_colors[StateHighlight] = highlightColor;
|
|
}
|
|
|
|
void Axis::Draw(DisplayContext& dc, const Vec3& position, const Vec3& axis, float angleRadians, float angleStepRadians, float radius, bool highlighted, CBaseObject* object, float screenScale)
|
|
{
|
|
if (static_cast<CViewport*>(dc.view)->GetType() != ET_ViewportCamera || object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
bool set = dc.SetDrawInFrontMode(true);
|
|
|
|
// Draw the front facing arc
|
|
dc.SetColor(!highlighted ? m_colors[StateDefault] : m_colors[StateHighlight]);
|
|
dc.DrawArc(position, radius * screenScale, 0.f, 360.f, RAD2DEG(angleStepRadians), axis);
|
|
|
|
dc.SetDrawInFrontMode(set);
|
|
}
|
|
else
|
|
{
|
|
// Draw the front facing arc
|
|
dc.SetColor(!highlighted ? m_colors[StateDefault] : m_colors[StateHighlight]);
|
|
dc.DrawArc(position, radius * screenScale, RAD2DEG(angleRadians) - 90.f, 180.f, RAD2DEG(angleStepRadians), axis);
|
|
|
|
// Draw the back side
|
|
dc.SetColor(!highlighted ? Col_Gray : m_colors[StateHighlight]);
|
|
dc.DrawArc(position, radius * screenScale, RAD2DEG(angleRadians) + 90.f, 180.f, RAD2DEG(angleStepRadians), axis);
|
|
}
|
|
|
|
static bool drawAxisMidPoint = false;
|
|
if (drawAxisMidPoint)
|
|
{
|
|
const float kBallRadius = 0.085f;
|
|
Vec3 a;
|
|
Vec3 b;
|
|
GetBasisVectors(axis, a, b);
|
|
|
|
float cosAngle = cos(angleRadians);
|
|
float sinAngle = sin(angleRadians);
|
|
|
|
Vec3 offset;
|
|
offset.x = position.x + (cosAngle * a.x + sinAngle * b.x) * screenScale * radius;
|
|
offset.y = position.y + (cosAngle * a.y + sinAngle * b.y) * screenScale * radius;
|
|
offset.z = position.z + (cosAngle * a.z + sinAngle * b.z) * screenScale * radius;
|
|
|
|
dc.SetColor(!highlighted ? m_colors[StateDefault] : m_colors[StateHighlight]);
|
|
dc.DrawBall(offset, kBallRadius * screenScale);
|
|
}
|
|
}
|
|
|
|
void Axis::GenerateHitTestGeometry([[maybe_unused]] HitContext& hc, const Vec3& position, float radius, float angleStepRadians, const Vec3& axis, float screenScale)
|
|
{
|
|
m_vertices.clear();
|
|
|
|
// The number of vertices relies on the angleStepRadians, the smaller the angle, the higher the vertex count.
|
|
int numVertices = static_cast<int>(std::ceil(g_PI2 / angleStepRadians));
|
|
|
|
Vec3 a;
|
|
Vec3 b;
|
|
GetBasisVectors(axis, a, b);
|
|
|
|
// The geometry is calculated by computing a circle aligned to the specified axis.
|
|
float angle = 0.f;
|
|
for (int i = 0; i < numVertices; ++i)
|
|
{
|
|
float cosAngle = cos(angle);
|
|
float sinAngle = sin(angle);
|
|
|
|
Vec3 p;
|
|
p.x = position.x + (cosAngle * a.x + sinAngle * b.x) * radius * screenScale;
|
|
p.y = position.y + (cosAngle * a.y + sinAngle * b.y) * radius * screenScale;
|
|
p.z = position.z + (cosAngle * a.z + sinAngle * b.z) * radius * screenScale;
|
|
m_vertices.push_back(p);
|
|
|
|
angle += angleStepRadians;
|
|
}
|
|
}
|
|
|
|
bool Axis::IntersectRayWithQuad(const Ray& ray, Vec3 quad[4], Vec3& contact)
|
|
{
|
|
contact = Vec3();
|
|
|
|
// Tests ray vs. two quads, the front facing quad and a back facing quad.
|
|
// will return true if an intersection occurs and the world space position of the contact.
|
|
return (Intersect::Ray_Triangle(ray, quad[0], quad[1], quad[2], contact) || Intersect::Ray_Triangle(ray, quad[0], quad[2], quad[3], contact) ||
|
|
Intersect::Ray_Triangle(ray, quad[0], quad[2], quad[1], contact) || Intersect::Ray_Triangle(ray, quad[0], quad[3], quad[2], contact));
|
|
}
|
|
|
|
bool Axis::HitTest(CBaseObject* object, HitContext& hc, float radius, float angleStepRadians, const Vec3& axis, float screenScale)
|
|
{
|
|
AffineParts ap;
|
|
ap.Decompose(object->GetWorldTM());
|
|
|
|
Vec3 position = ap.pos;
|
|
|
|
CSelectionGroup* selection = GetIEditor()->GetSelection();
|
|
if (selection->GetCount() > 1 && !object->CheckFlags(OBJFLAG_IS_PARTICLE))
|
|
{
|
|
position = selection->GetCenter();
|
|
}
|
|
|
|
// Generate intersection testing geometry
|
|
GenerateHitTestGeometry(hc, position, radius, angleStepRadians, axis, screenScale);
|
|
|
|
Ray ray;
|
|
ray.origin = hc.raySrc;
|
|
ray.direction = hc.rayDir;
|
|
|
|
// Calculate the face normal with the first two vertices in the intersection geometry.
|
|
Vec3 vdir0 = (m_vertices[0] - m_vertices[1]).GetNormalized();
|
|
Vec3 vdir1 = (m_vertices[2] - m_vertices[1]).GetNormalized();
|
|
|
|
Vec3 normal;
|
|
if (!hc.b2DViewport)
|
|
{
|
|
normal = hc.view->GetViewTM().GetColumn1();
|
|
}
|
|
else
|
|
{
|
|
normal = hc.view->GetConstructionPlane()->n;
|
|
}
|
|
|
|
float shortestDistance = std::numeric_limits<float>::max();
|
|
size_t numVertices = m_vertices.size();
|
|
for (size_t i = 0; i < numVertices; ++i)
|
|
{
|
|
const Vec3& v0 = m_vertices[i];
|
|
const Vec3& v1 = m_vertices[(i + 1) % numVertices];
|
|
Vec3 right = (v0 - v1).Cross(normal).GetNormalized() * screenScale * m_hitTestWidth;
|
|
|
|
// Calculates the quad vertices aligned to the face normal.
|
|
Vec3 quad[4];
|
|
quad[0] = v0 + right;
|
|
quad[1] = v1 + right;
|
|
quad[2] = v1 - right;
|
|
quad[3] = v0 - right;
|
|
|
|
Vec3 contact;
|
|
if (IntersectRayWithQuad(ray, quad, contact))
|
|
{
|
|
Vec3 intersectionPoint;
|
|
if (PointToLineDistance(v0, v1, contact, intersectionPoint))
|
|
{
|
|
// Ensure the intersection is within the quad's extents
|
|
float distanceToIntersection = intersectionPoint.GetDistance(contact);
|
|
if (distanceToIntersection < shortestDistance)
|
|
{
|
|
shortestDistance = distanceToIntersection;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if shortestDistance is less than the maximum possible distance, we have an intersection.
|
|
if (shortestDistance < std::numeric_limits<float>::max() - FLT_EPSILON)
|
|
{
|
|
hc.object = object;
|
|
hc.dist = shortestDistance;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Axis::DebugDrawHitTestSurface(DisplayContext& dc, HitContext& hc, const Vec3& position, float radius, float angleStepRadians, const Vec3& axis, float screenScale)
|
|
{
|
|
// Generate the geometry for rendering.
|
|
GenerateHitTestGeometry(hc, position, radius, angleStepRadians, axis, screenScale);
|
|
|
|
// Calculate the face normal with the first two vertices in the intersection geometry.
|
|
Vec3 vdir0 = (m_vertices[0] - m_vertices[1]).GetNormalized();
|
|
Vec3 vdir1 = (m_vertices[2] - m_vertices[1]).GetNormalized();
|
|
|
|
Vec3 normal;
|
|
if (!hc.b2DViewport)
|
|
{
|
|
normal = hc.view->GetViewTM().GetColumn1();
|
|
}
|
|
else
|
|
{
|
|
normal = hc.view->GetConstructionPlane()->n;
|
|
}
|
|
|
|
float shortestDistance = std::numeric_limits<float>::max();
|
|
|
|
Ray ray;
|
|
ray.origin = hc.raySrc;
|
|
ray.direction = hc.rayDir;
|
|
|
|
size_t numVertices = m_vertices.size();
|
|
for (size_t i = 0; i < numVertices; ++i)
|
|
{
|
|
const Vec3& v0 = m_vertices[i];
|
|
const Vec3& v1 = m_vertices[(i + 1) % numVertices];
|
|
Vec3 right = (v0 - v1).Cross(normal).GetNormalized() * screenScale * m_hitTestWidth;
|
|
|
|
// Calculates the quad vertices aligned to the face normal.
|
|
Vec3 quad[4];
|
|
quad[0] = v0 + right;
|
|
quad[1] = v1 + right;
|
|
quad[2] = v1 - right;
|
|
quad[3] = v0 - right;
|
|
|
|
// Draw double sided quad to ensure it is always visible regardless of camera orientation.
|
|
dc.DrawQuad(quad[0], quad[1], quad[2], quad[3]);
|
|
dc.DrawQuad(quad[3], quad[2], quad[1], quad[0]);
|
|
|
|
Vec3 contact;
|
|
if (IntersectRayWithQuad(ray, quad, contact))
|
|
{
|
|
Vec3 intersectionPoint;
|
|
if (PointToLineDistance(v0, v1, contact, intersectionPoint))
|
|
{
|
|
// Ensure the intersection is within the quad's extents
|
|
float distanceToIntersection = intersectionPoint.GetDistance(contact);
|
|
if (distanceToIntersection < shortestDistance)
|
|
{
|
|
shortestDistance = distanceToIntersection;
|
|
|
|
// Highlight the quad at which an intersection occurred.
|
|
auto c = dc.GetColor();
|
|
dc.SetColor(Col_Red);
|
|
dc.DrawQuad(quad[0], quad[1], quad[2], quad[3]);
|
|
dc.DrawQuad(quad[3], quad[2], quad[1], quad[0]);
|
|
dc.SetColor(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
namespace AngleDecorator
|
|
{
|
|
void Draw(DisplayContext& dc, const Vec3& position, const Vec3& axisToAlign, float startAngleRadians, float sweepAngleRadians, float stepAngleRadians, float radius, float screenScale)
|
|
{
|
|
float angle = startAngleRadians;
|
|
|
|
if (fabs(sweepAngleRadians) < FLT_EPSILON || sweepAngleRadians < stepAngleRadians)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (sweepAngleRadians > g_PI)
|
|
{
|
|
sweepAngleRadians = g_PI - (fabs(sweepAngleRadians - g_PI));
|
|
stepAngleRadians = -stepAngleRadians;
|
|
}
|
|
|
|
Vec3 a;
|
|
Vec3 b;
|
|
GetBasisVectors(axisToAlign, a, b);
|
|
|
|
float cosAngle = cos(angle);
|
|
float sinAngle = sin(angle);
|
|
|
|
// Pre-calculate the first vertex, this is useful for rendering the first handle ball.
|
|
Vec3 p0;
|
|
p0.x = position.x + (cosAngle * a.x + sinAngle * b.x) * radius * screenScale;
|
|
p0.y = position.y + (cosAngle * a.y + sinAngle * b.y) * radius * screenScale;
|
|
p0.z = position.z + (cosAngle * a.z + sinAngle * b.z) * radius * screenScale;
|
|
|
|
const float ballRadius = 0.1f * screenScale;
|
|
|
|
// TODO: colors should be configurable properties
|
|
dc.SetColor(0.f, 1.f, 0.f, 1.f);
|
|
dc.DrawBall(p0, ballRadius);
|
|
|
|
float alpha = 0.5f;
|
|
dc.SetColor(0.8f, 0.8f, 0.8f, 0.5f);
|
|
|
|
// Number of vertices is defined by stepAngleRadians, the smaller the step the higher vertex count.
|
|
int numVertices = static_cast<int>(fabs(sweepAngleRadians / stepAngleRadians));
|
|
if (numVertices >= 2)
|
|
{
|
|
Vec3 p1;
|
|
for (int i = 0; i < numVertices; ++i)
|
|
{
|
|
// We pre-calculated the first vertex, so we can advance the angle
|
|
angle += stepAngleRadians;
|
|
|
|
const float cosAngle2 = cos(angle);
|
|
const float sinAngle2 = sin(angle);
|
|
|
|
p1.x = position.x + (cosAngle2 * a.x + sinAngle2 * b.x) * radius * screenScale;
|
|
p1.y = position.y + (cosAngle2 * a.y + sinAngle2 * b.y) * radius * screenScale;
|
|
p1.z = position.z + (cosAngle2 * a.z + sinAngle2 * b.z) * radius * screenScale;
|
|
|
|
// Draws a triangle from the object's position to p0 and p1.
|
|
dc.SetColor(0.8f, 0.8f, 0.8f, alpha);
|
|
dc.DrawTri(position, p0, p1);
|
|
|
|
alpha += 0.5f * (i / numVertices);
|
|
p0 = p1;
|
|
}
|
|
|
|
// Draw the end handle ball.
|
|
dc.SetColor(1.f, 0.f, 0.f, 1.f);
|
|
dc.DrawBall(p1, ballRadius);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RotationControlConfiguration::RotationControlConfiguration()
|
|
{
|
|
DefineConstIntCVar(RotationControl_DrawDecorators, 0, VF_NULL, "Toggles the display of the angular decorator.");
|
|
DefineConstIntCVar(RotationControl_DebugHitTesting, 0, VF_NULL, "Renders the hit testing geometry used for mouse input control.");
|
|
DefineConstIntCVar(RotationControl_AngleTracking, 0, VF_NULL, "Displays a sphere aligned to the mouse cursor direction for debugging.");
|
|
}
|
|
|
|
#include <moc_RotateTool.cpp>
|