Files
leocad/common/lc_view.cpp
T
Leonardo Zide baae697960 Fix in-place submodel editing context and navigation
In-place editing could use the tab model's step and focus, lose its active
state after switching tabs, or resolve a nested instance through the wrong
parent. Entering before the final animation key also moved geometry when
drilling into a child.

Use the edited model for step controls, step edits, visibility, and focus
coordinates. Restore that editing target when returning to its tab, include
animation keys and finite hide steps in the last step, and retain the
selected instance path and transforms during nested editing.

Show the editing path only during in-place edits, with links to ancestors
and a close control to return to the tab model. Stack Find/Replace beneath
it and preserve full names in accessible text and tooltips. Add translated
source entries without changing existing translations.

Add a regression test for final-key tracking through time and key edits.
Validated with the macOS Qt 6 application build, all 8 Qt Test cases, and
51 focused checks against the compiled application for steps, tab switching,
focus status, breadcrumbs, and Find/Replace layout. Visually checked the
viewport overlay and its placement during nested editing.
2026-10-24 12:39:25 -07:00

3549 lines
101 KiB
C++

#include "lc_global.h"
#include "lc_view.h"
#include "lc_viewwidget.h"
#include <stdlib.h>
#include "lc_mainwindow.h"
#include "light.h"
#include "camera.h"
#include "texfont.h"
#include "lc_texture.h"
#include "piece.h"
#include "lc_piecedata.h"
#include "lc_traintrack.h"
#include "lc_scene.h"
#include "lc_context.h"
#include "lc_viewmanipulator.h"
#include "lc_viewsphere.h"
#include "lc_findreplacewidget.h"
#include "lc_library.h"
#include "lc_qutils.h"
#include "lc_partselectionpopup.h"
lcFindReplaceParams lcView::mFindReplaceParams;
QPointer<lcFindReplaceWidget> lcView::mFindWidget;
lcView* lcView::mLastFocusedView;
std::vector<lcView*> lcView::mViews;
lcView::lcView(lcViewType ViewType, lcModel* Model)
: mViewType(ViewType), mScene(new lcScene()), mModel(Model)
{
mContext = new lcContext();
mViews.push_back(this);
mViewManipulator = std::unique_ptr<lcViewManipulator>(new lcViewManipulator(this));
mViewSphere = std::unique_ptr<lcViewSphere>(new lcViewSphere(this));
memset(mGridSettings, 0, sizeof(mGridSettings));
mDragState = lcDragState::None;
mTrackToolFromOverlay = false;
lcView* ActiveView = gMainWindow ? gMainWindow->GetActiveView() : nullptr;
if (ActiveView)
SetCamera(ActiveView->mCamera, false);
else
{
mCamera = new lcCamera(true);
mCamera->SetViewpoint(lcViewpoint::Home);
}
}
lcView::~lcView()
{
DestroyOutlineFramebuffers();
mContext->DestroyVertexBuffer(mGridBuffer);
if (gMainWindow && mViewType == lcViewType::View)
gMainWindow->RemoveView(this);
if (mCamera && mCamera->IsSimple())
delete mCamera;
mViews.erase(std::find(mViews.begin(), mViews.end(), this));
if (mLastFocusedView == this)
mLastFocusedView = nullptr;
delete mContext;
}
std::vector<lcView*> lcView::GetModelViews(const lcModel* Model)
{
std::vector<lcView*> Views;
for (lcView* View : mViews)
if (View->GetModel() == Model)
Views.push_back(View);
return Views;
}
void lcView::UpdateProjectViews(const Project* Project)
{
for (lcView* View : mViews)
{
const lcModel* ViewModel = View->GetActiveModel();
if (ViewModel && ViewModel->GetProject() == Project)
View->Redraw();
}
}
void lcView::UpdateAllViews()
{
for (lcView* View : mViews)
View->Redraw();
}
void lcView::MakeCurrent()
{
mContext->MakeCurrent();
}
void lcView::Redraw()
{
if (mWidget)
{
mWidget->UpdateModelEditingPath();
mWidget->update();
}
}
void lcView::SetOffscreenContext()
{
mContext->SetOffscreenContext();
}
void lcView::SetFocus(bool Focus)
{
if (Focus)
{
if (mLastFocusedView != this)
{
delete mFindWidget;
mFindWidget = nullptr;
mLastFocusedView = this;
}
emit FocusReceived();
}
}
void lcView::SetMousePosition(int MouseX, int MouseY)
{
mMouseX = MouseX;
mMouseY = MouseY;
}
void lcView::SetMouseModifiers(Qt::KeyboardModifiers MouseModifiers)
{
mMouseModifiers = MouseModifiers;
}
lcModel* lcView::GetActiveModel() const
{
return !mActiveSubmodelInstance ? mModel : mActiveSubmodelInstance->mPieceData->GetModel();
}
std::vector<lcModel*> lcView::GetModelEditingPath() const
{
std::vector<lcModel*> Models = { mModel };
for (const std::pair<lcPiece*, lcMatrix44>& Entry : mSubmodelEditingPath)
Models.push_back(Entry.first->mPieceData->GetModel());
return Models;
}
void lcView::SetSubmodelActive(size_t Depth)
{
if (Depth > mSubmodelEditingPath.size())
return;
StopTracking(true);
lcModel* ActiveModel = GetActiveModel();
if (mActiveSubmodelInstance)
{
ActiveModel->SetActive(false);
std::vector<lcModel*> UpdatedModels;
ActiveModel->UpdatePieceData(UpdatedModels);
}
mSubmodelEditingPath.resize(Depth);
mActiveSubmodelInstance = nullptr;
if (!mSubmodelEditingPath.empty())
{
mActiveSubmodelInstance = mSubmodelEditingPath.back().first;
mActiveSubmodelTransform = mSubmodelEditingPath.back().second;
}
GetActiveModel()->SetActive(true);
GetActiveModel()->UpdateInterface();
}
void lcView::SetTopSubmodelActive()
{
SetSubmodelActive(0);
}
void lcView::SetSelectedSubmodelActive()
{
lcModel* ActiveModel = GetActiveModel();
lcObject* Object = ActiveModel->GetFocusObject();
if (!Object || !Object->IsPiece())
return;
lcPiece* Piece = (lcPiece*)Object;
if (!Piece->mPieceData->IsModel())
return;
// Preserve the selected instance path and capture its transform before SetActive(false) changes the step.
lcMatrix44 SubmodelTransform = Piece->mModelWorld;
if (mActiveSubmodelInstance)
SubmodelTransform = lcMul(SubmodelTransform, mActiveSubmodelTransform);
if (mActiveSubmodelInstance)
ActiveModel->SetActive(false);
mSubmodelEditingPath.emplace_back(Piece, SubmodelTransform);
mActiveSubmodelTransform = SubmodelTransform;
mActiveSubmodelInstance = Piece;
ActiveModel = mActiveSubmodelInstance->mPieceData->GetModel();
ActiveModel->SetTemporaryStep(ActiveModel->GetLastStep());
ActiveModel->SetActive(true);
RemoveCamera();
GetActiveModel()->UpdateInterface();
}
void lcView::CreateResources(lcContext* Context)
{
Q_UNUSED(Context);
gGridTexture = new lcTexture;
gGridTexture->CreateGridTexture();
}
void lcView::DestroyResources(lcContext* Context)
{
Q_UNUSED(Context);
delete gGridTexture;
gGridTexture = nullptr;
}
void lcView::RemoveCamera()
{
if (mCamera && mCamera->IsSimple())
return;
lcCamera* Camera = mCamera;
mCamera = new lcCamera(true);
if (Camera)
mCamera->CopyPosition(Camera);
else
mCamera->SetViewpoint(lcViewpoint::Home);
Redraw();
}
lcVector3 lcView::ProjectPoint(const lcVector3& Point) const
{
int Viewport[4] = { 0, 0, mWidth, mHeight };
return lcProjectPoint(Point, mCamera->mWorldView, GetProjectionMatrix(), Viewport);
}
lcVector3 lcView::UnprojectPoint(const lcVector3& Point) const
{
int Viewport[4] = { 0, 0, mWidth, mHeight };
return lcUnprojectPoint(Point, mCamera->mWorldView, GetProjectionMatrix(), Viewport);
}
void lcView::UnprojectPoints(lcVector3* Points, int NumPoints) const
{
int Viewport[4] = { 0, 0, mWidth, mHeight };
lcUnprojectPoints(Points, NumPoints, mCamera->mWorldView, GetProjectionMatrix(), Viewport);
}
lcMatrix44 lcView::GetProjectionMatrix() const
{
float AspectRatio = (float)mWidth / (float)mHeight;
if (mCamera->GetProjection() == lcCameraProjection::Orthographic)
{
float OrthoHeight = mCamera->GetOrthoHeight() / 2.0f;
float OrthoWidth = OrthoHeight * AspectRatio;
return lcMatrix44Ortho(-OrthoWidth, OrthoWidth, -OrthoHeight, OrthoHeight, mCamera->m_zNear, mCamera->m_zFar * 4);
}
else
return lcMatrix44Perspective(mCamera->m_fovy, AspectRatio, mCamera->m_zNear, mCamera->m_zFar);
}
lcMatrix44 lcView::GetTileProjectionMatrix(int CurrentRow, int CurrentColumn, int CurrentTileWidth, int CurrentTileHeight) const
{
int ImageWidth = mRenderImage.width();
int ImageHeight = mRenderImage.height();
double ImageLeft, ImageRight, ImageBottom, ImageTop, Near, Far;
double AspectRatio = (double)ImageWidth / (double)ImageHeight;
if (mCamera->GetProjection() == lcCameraProjection::Orthographic)
{
float OrthoHeight = mCamera->GetOrthoHeight() / 2.0f;
float OrthoWidth = OrthoHeight * AspectRatio;
ImageLeft = -OrthoWidth;
ImageRight = OrthoWidth;
ImageBottom = -OrthoHeight;
ImageTop = OrthoHeight;
Near = mCamera->m_zNear;
Far = mCamera->m_zFar * 4;
}
else
{
double xmin, xmax, ymin, ymax;
ymax = mCamera->m_zNear * tan(mCamera->m_fovy * 3.14159265 / 360.0);
ymin = -ymax;
xmin = ymin * AspectRatio;
xmax = ymax * AspectRatio;
ImageLeft = xmin;
ImageRight = xmax;
ImageBottom = ymin;
ImageTop = ymax;
Near = mCamera->m_zNear;
Far = mCamera->m_zFar;
}
double Left = ImageLeft + (ImageRight - ImageLeft) * (CurrentColumn * mWidth) / ImageWidth;
double Right = Left + (ImageRight - ImageLeft) * CurrentTileWidth / ImageWidth;
double Bottom = ImageBottom + (ImageTop - ImageBottom) * (CurrentRow * mHeight) / ImageHeight;
double Top = Bottom + (ImageTop - ImageBottom) * CurrentTileHeight / ImageHeight;
if (mCamera->GetProjection() == lcCameraProjection::Orthographic)
return lcMatrix44Ortho(Left, Right, Bottom, Top, Near, Far);
else
return lcMatrix44Frustum(Left, Right, Bottom, Top, Near, Far);
}
void lcView::ShowTrainTrackPopup()
{
if (mViewType != lcViewType::View)
return;
lcModel* ActiveModel = GetActiveModel();
lcObject* Focus = ActiveModel->GetFocusObject();
if (!Focus || !Focus->IsPiece())
return;
lcPiece* Piece = (lcPiece*)Focus;
const lcTrainTrackInfo* TrainTrackInfo = Piece->mPieceData->GetTrainTrackInfo();
if (!TrainTrackInfo)
return;
if (mTrackToolSection < LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST)
return;
int ConnectionIndex = mTrackToolSection - LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST;
const lcTrainTrackConnectionType& ConnectionType = TrainTrackInfo->GetConnections()[ConnectionIndex].Type;
std::vector<lcPieceData*> TrainTrackParts = lcGetPiecesLibrary()->GetVisibleTrainTrackParts(ConnectionType);
auto PartSortFunc = [](const lcPieceData* a, const lcPieceData* b)
{
if (!a)
return true;
if (!b)
return false;
return strcmp(a->m_strDescription, b->m_strDescription) < 0;
};
std::sort(TrainTrackParts.begin(), TrainTrackParts.end(), PartSortFunc);
std::vector<std::pair<lcPieceData*, std::string>> Parts;
Parts.reserve(TrainTrackParts.size());
for (lcPieceData* PieceData : TrainTrackParts)
Parts.emplace_back(PieceData, std::string());
std::optional<lcPieceData*> Result = lcShowPartSelectionPopup(nullptr, Parts, gMainWindow->mColorIndex, mWidget, QCursor::pos());
lcPieceData* PieceData = Result.has_value() ? Result.value() : nullptr;
if (PieceData)
{
Piece = ActiveModel->AddPiece(PieceData, mTrackToolSection);
if (Piece)
ActiveModel->UpdateTrainTrackConnections(Piece, false);
}
mWidget->UpdateMousePosition();
}
void lcView::ShowContextMenu() const
{
if (mViewType != lcViewType::View)
return;
QAction** Actions = gMainWindow->mActions;
QMenu* Popup = new QMenu();
Popup->addAction(Actions[LC_EDIT_CUT]);
Popup->addAction(Actions[LC_EDIT_COPY]);
Popup->addAction(Actions[LC_EDIT_PASTE]);
Popup->addAction(Actions[LC_EDIT_PASTE_STEPS]);
Popup->addAction(Actions[LC_PIECE_DUPLICATE]);
Popup->addSeparator();
Popup->addAction(Actions[LC_PIECE_CONTROL_POINT_INSERT]);
Popup->addAction(Actions[LC_PIECE_CONTROL_POINT_REMOVE]);
Popup->addSeparator();
Popup->addAction(Actions[LC_PIECE_EDIT_SELECTED_SUBMODEL]);
Popup->addAction(Actions[LC_PIECE_EDIT_END_SUBMODEL]);
Popup->addAction(Actions[LC_PIECE_VIEW_SELECTED_MODEL]);
Popup->addAction(Actions[LC_PIECE_INLINE_SELECTED_MODELS]);
Popup->addAction(Actions[LC_PIECE_MOVE_SELECTION_TO_MODEL]);
Popup->addSeparator();
Popup->addMenu(gMainWindow->GetToolsMenu());
Popup->addMenu(gMainWindow->GetViewpointMenu());
Popup->addMenu(gMainWindow->GetCameraMenu());
Popup->addMenu(gMainWindow->GetProjectionMenu());
Popup->addMenu(gMainWindow->GetShadingMenu());
Popup->addSeparator();
Popup->addAction(Actions[LC_VIEW_SPLIT_HORIZONTAL]);
Popup->addAction(Actions[LC_VIEW_SPLIT_VERTICAL]);
Popup->addAction(Actions[LC_VIEW_REMOVE_VIEW]);
Popup->addAction(Actions[LC_VIEW_RESET_VIEWS]);
Popup->exec(QCursor::pos());
delete Popup;
}
bool lcView::CloseFindReplaceDialog()
{
if (mFindWidget && (mWidget->hasFocus() || mFindWidget->focusWidget()))
{
delete mFindWidget;
mFindWidget = nullptr;
return true;
}
return false;
}
void lcView::ShowFindReplaceWidget(bool Replace)
{
delete mFindWidget;
mFindReplaceParams = lcFindReplaceParams();
mFindWidget = new lcFindReplaceWidget(mWidget, GetActiveModel(), Replace);
mWidget->SetFindReplaceWidget(mFindWidget);
}
lcVector3 lcView::GetMoveDirection(const lcVector3& Direction) const
{
if (lcGetPreferences().mFixedAxes)
return Direction;
// Match the axes used by MoveSelectedObjects/RotateSelectedObjects before
// projecting them, so relative transforms do not rotate the mapping again.
lcMatrix33 LocalToWorld = GetActiveModel()->GetRelativeRotation();
// Separate transforms always use each piece's axes. Use the focused piece
// as the reference for the shared keyboard direction.
if (gMainWindow->GetSeparateTransform())
{
lcObject* Focus = GetActiveModel()->GetFocusObject();
if (Focus && Focus->IsPiece())
LocalToWorld = static_cast<lcPiece*>(Focus)->GetRelativeRotation();
}
if (mActiveSubmodelInstance)
LocalToWorld = lcMul(LocalToWorld, lcMatrix33(mActiveSubmodelTransform));
const lcMatrix33 LocalToView = lcMul(LocalToWorld, lcMatrix33(mCamera->mWorldView));
// Choose distinct X/Y axes for screen right and up. Page Up/Down keep
// their Z direction regardless of the camera, including bottom views.
float BestScore = -std::numeric_limits<float>::infinity();
float BestAway = -std::numeric_limits<float>::infinity();
lcVector3 BestRight(1.0f, 0.0f, 0.0f);
lcVector3 BestUp(0.0f, 1.0f, 0.0f);
for (int RightAxis = 0; RightAxis < 2; RightAxis++)
{
for (int UpAxis = 0; UpAxis < 2; UpAxis++)
{
if (RightAxis == UpAxis)
continue;
for (int RightSign : { 1, -1 })
{
for (int UpSign : { 1, -1 })
{
lcVector3 Right(0.0f, 0.0f, 0.0f);
lcVector3 Up(0.0f, 0.0f, 0.0f);
Right[RightAxis] = float(RightSign);
Up[UpAxis] = float(UpSign);
const lcVector3 ViewUp = lcMul(Up, LocalToView);
const float Score = lcMul(Right, LocalToView).x + ViewUp.y;
const float Away = -ViewUp.z;
// In edge-on views, Up moves into the scene along the remaining
// horizontal axis. Retain the first frame on diagonal ties.
if (Score > BestScore + LC_EPSILON || (fabsf(Score - BestScore) <= LC_EPSILON && Away > BestAway + LC_EPSILON))
{
BestScore = Score;
BestAway = Away;
BestRight = Right;
BestUp = Up;
}
}
}
}
}
// Default bindings are +X = Down, +Y = Right, +Z = Page Up.
return BestRight * Direction.y - BestUp * Direction.x + lcVector3(0.0f, 0.0f, Direction.z);
}
void lcView::UpdatePiecePreview()
{
const lcPieceDataRef PreviewPieceData(gMainWindow->GetInsertPieceData());
std::vector<lcInsertPieceInfo> InsertPieceInfo;
bool IsConnection;
if (PreviewPieceData)
{
std::tie(InsertPieceInfo, IsConnection) = GetMouseInsertPieceInfo(false, true, PreviewPieceData, nullptr);
}
GetActiveModel()->SetPreviewInsertPieceInfo(std::move(InsertPieceInfo));
}
std::pair<std::vector<lcInsertPieceInfo>, bool> lcView::GetMouseInsertPieceInfo(bool IgnoreSelected, bool AllowNewPieces, const lcPieceDataRef& PieceData, lcPiece* MovingPiece) const
{
lcModel* ActiveModel = GetActiveModel();
lcGetPiecesLibrary()->EnsurePieceDataAssetsReady(PieceData);
if (MovingPiece && PieceData->GetSynthInfo())
lcGetPiecesLibrary()->WaitForSynthMeshes({ MovingPiece });
lcObjectRayTest ObjectRayTest = RayTest(true, IgnoreSelected);
// Settling a hit's geometry can reveal another pending piece behind it, so pick again until the hit is stable.
while (ObjectRayTest.ObjectSection.Object)
{
lcPiece* MousePiece = reinterpret_cast<lcPiece*>(ObjectRayTest.ObjectSection.Object);
lcPieceData* MousePieceData = MousePiece->mPieceData.Get();
const bool Retest = MousePieceData->mState != lcAssetState::Loaded || MousePieceData->IsModel() || MousePieceData->IsProject() || MousePiece->IsGeneratedMeshPending();
lcGetPiecesLibrary()->EnsurePieceDataAssetsReady(MousePiece->mPieceData);
if (MousePieceData->GetSynthInfo())
lcGetPiecesLibrary()->WaitForSynthMeshes({ MousePiece });
if (!Retest)
break;
lcObjectRayTest ReadyRayTest = RayTest(true, IgnoreSelected);
const bool SamePiece = ReadyRayTest.ObjectSection.Object == ObjectRayTest.ObjectSection.Object;
ObjectRayTest = ReadyRayTest;
if (SamePiece)
break;
}
const lcBoundingBox& BoundingBox = MovingPiece ? MovingPiece->GetBoundingBox() : PieceData->GetBoundingBox();
if (ObjectRayTest.ObjectSection.Object)
{
lcPiece* MousePiece = reinterpret_cast<lcPiece*>(ObjectRayTest.ObjectSection.Object);
lcTrainTrackInfo* RayTrackInfo = MousePiece->mPieceData->GetTrainTrackInfo();
lcTrainTrackInfo* InfoTrackInfo = PieceData->GetTrainTrackInfo();
if (RayTrackInfo && InfoTrackInfo)
{
ActiveModel->UpdateTrainTrackConnections(MousePiece, IgnoreSelected);
lcVector3 ClosestPoint = ObjectRayTest.Start + lcNormalize(ObjectRayTest.End - ObjectRayTest.Start) * ObjectRayTest.Distance;
quint32 FocusSection = MousePiece->GetFocusSection();
std::vector<lcInsertPieceInfo> TrainTracks = lcTrainTrackInfo::GetInsertPieceInfo(MousePiece, PieceData.Get(), MovingPiece, gMainWindow->mColorIndex, FocusSection, AllowNewPieces, ClosestPoint);
if (!TrainTracks.empty())
return { TrainTracks, true };
}
lcVector3 Position = ObjectRayTest.PieceRayTest.Plane;
if (Position.x > 0.0f)
Position.x += fabsf(BoundingBox.Min.x);
else if (Position.x < 0.0f)
Position.x -= fabsf(BoundingBox.Max.x);
else if (Position.y > 0.0f)
Position.y += fabsf(BoundingBox.Min.y);
else if (Position.y < 0.0f)
Position.y -= fabsf(BoundingBox.Max.y);
else if (Position.z > 0.0f)
Position.z += fabsf(BoundingBox.Min.z);
else if (Position.z < 0.0f)
Position.z -= fabsf(BoundingBox.Max.z);
if (gMainWindow->GetRelativeTransform())
Position = lcMul31(ActiveModel->SnapPosition(Position), ObjectRayTest.PieceRayTest.Transform);
else
Position = ActiveModel->SnapPosition(lcMul31(Position, ObjectRayTest.PieceRayTest.Transform));
lcMatrix44 WorldMatrix = ObjectRayTest.PieceRayTest.Transform;
WorldMatrix.SetTranslation(Position);
return { { lcInsertPieceInfo{ PieceData.Get(), WorldMatrix, gMainWindow->mColorIndex } }, false };
}
std::array<lcVector3, 2> ClickPoints = {{ lcVector3((float)mMouseX, (float)mMouseY, 0.0f), lcVector3((float)mMouseX, (float)mMouseY, 1.0f) }};
UnprojectPoints(ClickPoints.data(), 2);
if (ActiveModel != mModel)
{
lcMatrix44 InverseMatrix = lcMatrix44AffineInverse(mActiveSubmodelTransform);
for (lcVector3& Point : ClickPoints)
Point = lcMul31(Point, InverseMatrix);
}
lcVector3 Intersection;
if (lcLineSegmentPlaneIntersection(&Intersection, ClickPoints[0], ClickPoints[1], lcVector4(0, 0, 1, BoundingBox.Min.z)))
{
Intersection = ActiveModel->SnapPosition(Intersection);
return { { lcInsertPieceInfo{ PieceData.Get(), lcMatrix44Translation(Intersection), gMainWindow->mColorIndex } }, false };
}
lcVector3 Position;
if (!ActiveModel->GetFocusPosition(Position))
Position = ActiveModel->GetSelectionOrModelCenter();
lcVector3 FrontVector(mCamera->mTargetPosition - mCamera->mPosition);
if (lcLineSegmentPlaneIntersection(&Intersection, ClickPoints[0], ClickPoints[1], lcVector4(FrontVector, -lcDot(FrontVector, Position))))
{
Intersection = ActiveModel->SnapPosition(Intersection);
return { { lcInsertPieceInfo{ PieceData.Get(), lcMatrix44Translation(Intersection), gMainWindow->mColorIndex } }, false };
}
return { { lcInsertPieceInfo{ PieceData.Get(), lcMatrix44Translation(UnprojectPoint(lcVector3((float)mMouseX, (float)mMouseY, 0.9f))), gMainWindow->mColorIndex } }, false };
}
lcVector3 lcView::GetCameraLightInsertPosition() const
{
lcModel* ActiveModel = GetActiveModel();
std::array<lcVector3, 2> ClickPoints = { { lcVector3((float)mMouseX, (float)mMouseY, 0.0f), lcVector3((float)mMouseX, (float)mMouseY, 1.0f) } };
UnprojectPoints(ClickPoints.data(), 2);
if (ActiveModel != mModel)
{
lcMatrix44 InverseMatrix = lcMatrix44AffineInverse(mActiveSubmodelTransform);
for (lcVector3& Point : ClickPoints)
Point = lcMul31(Point, InverseMatrix);
}
lcVector3 Min, Max;
lcVector3 Center;
if (ActiveModel->GetVisiblePiecesBoundingBox(Min, Max))
Center = (Min + Max) / 2.0f;
else
Center = lcVector3(0.0f, 0.0f, 0.0f);
return lcRayPointClosestPoint(Center, ClickPoints[0], ClickPoints[1]);
}
void lcView::GetRayUnderPointer(lcVector3& Start, lcVector3& End) const
{
lcVector3 StartEnd[2] =
{
lcVector3((float)mMouseX, (float)mMouseY, 0.0f),
lcVector3((float)mMouseX, (float)mMouseY, 1.0f)
};
UnprojectPoints(StartEnd, 2);
Start = StartEnd[0];
End = StartEnd[1];
}
lcObjectRayTest lcView::RayTest(bool PiecesOnly, bool IgnoreSelected) const
{
lcVector3 StartEnd[2] =
{
lcVector3((float)mMouseX, (float)mMouseY, 0.0f),
lcVector3((float)mMouseX, (float)mMouseY, 1.0f)
};
UnprojectPoints(StartEnd, 2);
lcObjectRayTest ObjectRayTest;
ObjectRayTest.PiecesOnly = PiecesOnly;
ObjectRayTest.IgnoreSelected = IgnoreSelected;
ObjectRayTest.ViewCamera = mCamera;
ObjectRayTest.Start = StartEnd[0];
ObjectRayTest.End = StartEnd[1];
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel != mModel)
{
lcMatrix44 InverseMatrix = lcMatrix44AffineInverse(mActiveSubmodelTransform);
ObjectRayTest.Start = lcMul31(ObjectRayTest.Start, InverseMatrix);
ObjectRayTest.End = lcMul31(ObjectRayTest.End, InverseMatrix);
}
ActiveModel->RayTest(ObjectRayTest);
return ObjectRayTest;
}
lcObjectSection lcView::FindObjectUnderPointer(bool PiecesOnly, bool IgnoreSelected) const
{
return RayTest(PiecesOnly, IgnoreSelected).ObjectSection;
}
std::vector<lcObject*> lcView::FindObjectsInBox(float x1, float y1, float x2, float y2) const
{
float Left, Top, Bottom, Right;
if (x1 < x2)
{
Left = x1;
Right = x2;
}
else
{
Left = x2;
Right = x1;
}
if (y1 > y2)
{
Top = y1;
Bottom = y2;
}
else
{
Top = y2;
Bottom = y1;
}
std::array<lcVector3, 6> Corners =
{{
lcVector3(Left, Top, 0),
lcVector3(Left, Bottom, 0),
lcVector3(Right, Bottom, 0),
lcVector3(Right, Top, 0),
lcVector3(Left, Top, 1),
lcVector3(Right, Bottom, 1)
}};
UnprojectPoints(Corners.data(), (int)Corners.size());
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel != mModel)
{
lcMatrix44 InverseMatrix = lcMatrix44AffineInverse(mActiveSubmodelTransform);
for (lcVector3& Point : Corners)
Point = lcMul31(Point, InverseMatrix);
}
lcVector3 PlaneNormals[6];
PlaneNormals[0] = lcNormalize(lcCross(Corners[4] - Corners[0], Corners[1] - Corners[0])); // Left
PlaneNormals[1] = lcNormalize(lcCross(Corners[5] - Corners[2], Corners[3] - Corners[2])); // Right
PlaneNormals[2] = lcNormalize(lcCross(Corners[3] - Corners[0], Corners[4] - Corners[0])); // Top
PlaneNormals[3] = lcNormalize(lcCross(Corners[1] - Corners[2], Corners[5] - Corners[2])); // Bottom
PlaneNormals[4] = lcNormalize(lcCross(Corners[1] - Corners[0], Corners[3] - Corners[0])); // Front
PlaneNormals[5] = lcNormalize(lcCross(Corners[1] - Corners[2], Corners[3] - Corners[2])); // Back
lcObjectBoxTest ObjectBoxTest;
ObjectBoxTest.ViewCamera = mCamera;
ObjectBoxTest.Planes[0] = lcVector4(PlaneNormals[0], -lcDot(PlaneNormals[0], Corners[0]));
ObjectBoxTest.Planes[1] = lcVector4(PlaneNormals[1], -lcDot(PlaneNormals[1], Corners[5]));
ObjectBoxTest.Planes[2] = lcVector4(PlaneNormals[2], -lcDot(PlaneNormals[2], Corners[0]));
ObjectBoxTest.Planes[3] = lcVector4(PlaneNormals[3], -lcDot(PlaneNormals[3], Corners[5]));
ObjectBoxTest.Planes[4] = lcVector4(PlaneNormals[4], -lcDot(PlaneNormals[4], Corners[0]));
ObjectBoxTest.Planes[5] = lcVector4(PlaneNormals[5], -lcDot(PlaneNormals[5], Corners[5]));
ActiveModel->BoxTest(ObjectBoxTest);
return ObjectBoxTest.Objects;
}
std::vector<QImage> lcView::GetStepImages(lcStep Start, lcStep End)
{
std::vector<QImage> Images;
mModel->WaitForAssets();
if (!BeginRenderToImage(mWidth, mHeight))
{
if (gMainWindow)
QMessageBox::warning(gMainWindow, tr("Create Step Images"), tr("Error creating images."));
return Images;
}
const lcStep CurrentStep = mModel->GetCurrentStep();
bool RenderFailed = false;
for (lcStep Step = Start; Step <= End; Step++)
{
mModel->SetTemporaryStep(Step);
OnDraw();
QImage Image = GetRenderImage();
if (Image.isNull())
{
RenderFailed = true;
Images.clear();
break;
}
Images.emplace_back(std::move(Image));
}
EndRenderToImage();
mModel->SetTemporaryStep(CurrentStep);
if (!mModel->IsActive())
mModel->CalculateStep(LC_STEP_MAX);
if (RenderFailed && gMainWindow)
QMessageBox::warning(gMainWindow, tr("Create Step Images"), tr("Error creating images."));
return Images;
}
lcResult<void> lcView::SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function<void(const QString&)> ProgressCallback)
{
std::vector<QImage> Images = GetStepImages(Start, End);
if (Images.size() != static_cast<size_t>(End - Start + 1))
return lcUnexpected(tr("Error creating images."));
for (lcStep Step = Start; Step <= End; Step++)
{
QString FileName;
if (AddStepSuffix)
FileName = BaseName.arg(Step, 2, 10, QLatin1Char('0'));
else
FileName = BaseName;
QImageWriter Writer(FileName);
if (Writer.format().isEmpty())
Writer.setFormat("png");
if (!Writer.write(Images[Step - Start]))
return lcUnexpected(tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString()));
if (ProgressCallback)
ProgressCallback(FileName);
}
return lcResult<void>();
}
bool lcView::BeginRenderToImage(int Width, int Height)
{
lcContext* Context = lcContext::GetGlobalOffscreenContext();
int MaxTexture = qMin(Context->GetMaxTextureSize(), 2048);
const int Samples = QSurfaceFormat::defaultFormat().samples();
if (Samples > 1)
MaxTexture /= Samples;
int TileWidth = qMin(Width, MaxTexture);
int TileHeight = qMin(Height, MaxTexture);
mWidth = TileWidth;
mHeight = TileHeight;
mRenderImage = QImage(Width, Height, QImage::Format_ARGB32);
QOpenGLFramebufferObjectFormat Format;
Format.setAttachment(QOpenGLFramebufferObject::Depth);
if (QSurfaceFormat::defaultFormat().samples() > 1)
Format.setSamples(QSurfaceFormat::defaultFormat().samples());
mRenderFramebuffer = std::unique_ptr<QOpenGLFramebufferObject>(new QOpenGLFramebufferObject(QSize(TileWidth, TileHeight), Format));
if (!mRenderFramebuffer->bind())
{
mRenderFramebuffer.reset();
mRenderImage = QImage();
return false;
}
return true;
}
void lcView::EndRenderToImage()
{
mRenderFramebuffer.reset();
}
QImage lcView::GetRenderImage() const
{
return mRenderImage;
}
void lcView::DestroyOutlineFramebuffers()
{
mOutlineFullFramebuffer.reset();
mOutlineVisibleFramebuffer.reset();
mOutlineAttemptSize = QSize();
}
bool lcView::PrepareOutlineFramebuffers()
{
QOpenGLContext* GLContext = mContext->GetGLContext();
// Framebuffer objects are context-local under core/ARB OpenGL.
if (mOutlineGLContext != GLContext)
{
DestroyOutlineFramebuffers();
mOutlineGLContext = GLContext;
}
if (!GLContext || !lcContext::SupportsSelectionOutline() || mWidth <= 0 || mHeight <= 0)
return false;
const QSize Size(mWidth, mHeight);
if (mOutlineAttemptSize == Size)
return mOutlineFullFramebuffer && mOutlineVisibleFramebuffer;
lcScopedGLContextState GLState;
DestroyOutlineFramebuffers();
mOutlineAttemptSize = Size;
mOutlineFullFramebuffer = mContext->CreateFramebuffer(mWidth, mHeight);
mOutlineVisibleFramebuffer = mContext->CreateFramebuffer(mWidth, mHeight);
if (!mOutlineFullFramebuffer || !mOutlineVisibleFramebuffer)
{
mOutlineFullFramebuffer.reset();
mOutlineVisibleFramebuffer.reset();
qWarning("Selection outline framebuffer creation failed; using face tint and colored edges.");
return false;
}
return true;
}
bool lcView::GenerateOutlineMasks()
{
if (mOutlineFullFramebuffer && mOutlineVisibleFramebuffer &&
mContext->GenerateOutlineMasks(*mScene, *mOutlineFullFramebuffer, *mOutlineVisibleFramebuffer))
return true;
mOutlineFullFramebuffer.reset();
mOutlineVisibleFramebuffer.reset();
qWarning("Selection outline pass failed; using face tint and colored edges.");
return false;
}
void lcView::BindRenderFramebuffer()
{
mRenderFramebuffer->bind();
}
void lcView::UnbindRenderFramebuffer()
{
mRenderFramebuffer->release();
}
QImage lcView::GetRenderFramebufferImage() const
{
return mRenderFramebuffer->toImage();
}
void lcView::OnDraw()
{
if (!mModel)
return;
//#define LC_PROFILE_DRAW
#ifdef LC_PROFILE_DRAW
static std::array<GLuint64, 30> QueryResults;
static std::array<qint64, 30> TimerResults;
static int FrameNumber;
QElapsedTimer Timer;
QOpenGLTimerQuery Query;
if (!Query.isCreated())
Query.create();
Query.begin();
Timer.start();
#endif
const lcPreferences& Preferences = lcGetPreferences();
const bool DrawOverlays = mWidget != nullptr;
const bool DrawInterface = mWidget != nullptr && mViewType == lcViewType::View;
lcShadingMode ShadingMode = Preferences.mShadingMode;
if (ShadingMode == lcShadingMode::Wireframe && !mWidget)
ShadingMode = lcShadingMode::Flat;
mScene->SetShadingMode(ShadingMode);
mScene->SetAllowLOD(Preferences.mAllowLOD && mWidget != nullptr);
mScene->SetRequireCompleteAssets(static_cast<bool>(mRenderFramebuffer));
mScene->SetRequireGeneratedMeshes(mRenderFramebuffer && mViewType != lcViewType::PartsList);
mScene->SetLODDistance(Preferences.mMeshLODDistance);
mScene->Begin(mCamera->mWorldView);
mScene->SetActiveSubmodelInstance(mActiveSubmodelInstance, mActiveSubmodelTransform);
mScene->SetDrawInterface(DrawInterface);
bool DrawInsertPreview = DrawInterface && std::any_of(mViews.begin(), mViews.end(), [](lcView* View){ return View->mTrackTool == lcTrackTool::Insert; });
mScene->SetDrawInsertPreview(DrawInsertPreview);
mModel->GetScene(mScene.get(), mCamera, Preferences.mHighlightNewParts, Preferences.mFadeSteps && mViewType == lcViewType::View);
mScene->End();
int TotalTileRows = 1;
int TotalTileColumns = 1;
if (!mRenderImage.isNull())
{
int ImageWidth = mRenderImage.width();
int ImageHeight = mRenderImage.height();
if (ImageWidth > mWidth || ImageHeight > mHeight)
{
TotalTileColumns = (mWidth + ImageWidth - 1) / mWidth;
TotalTileRows = (mHeight + ImageHeight - 1) / mHeight;
}
}
for (int CurrentTileRow = 0; CurrentTileRow < TotalTileRows; CurrentTileRow++)
{
for (int CurrentTileColumn = 0; CurrentTileColumn < TotalTileColumns; CurrentTileColumn++)
{
mContext->SetDefaultState();
mContext->SetViewport(0, 0, mWidth, mHeight);
int CurrentTileWidth, CurrentTileHeight;
if (!mRenderImage.isNull() && (TotalTileRows > 1 || TotalTileColumns > 1))
{
if (CurrentTileRow < TotalTileRows - 1)
CurrentTileHeight = mHeight;
else
CurrentTileHeight = mRenderImage.height() - (TotalTileRows - 1) * (mHeight);
if (CurrentTileColumn < TotalTileColumns - 1)
CurrentTileWidth = mWidth;
else
CurrentTileWidth = mRenderImage.width() - (TotalTileColumns - 1) * (mWidth);
DrawBackground(CurrentTileRow, TotalTileRows, CurrentTileHeight);
mContext->SetViewport(0, 0, CurrentTileWidth, CurrentTileHeight);
mContext->SetProjectionMatrix(GetTileProjectionMatrix(CurrentTileRow, CurrentTileColumn, CurrentTileWidth, CurrentTileHeight));
}
else
{
CurrentTileWidth = mWidth;
CurrentTileHeight = mHeight;
DrawBackground(CurrentTileRow, TotalTileRows, CurrentTileHeight);
mContext->SetProjectionMatrix(GetProjectionMatrix());
}
mContext->SetLineWidth(Preferences.mLineWidth);
const bool OutlineEnabled = DrawInterface && !mRenderFramebuffer && mRenderImage.isNull() &&
ShadingMode != lcShadingMode::Wireframe && Preferences.mHighlightRenderMode == lcHighlightRenderMode::Outline &&
(mScene->HasSelectedMeshes() || mScene->HasHighlightedMeshes()) && PrepareOutlineFramebuffers();
bool OutlineMasksReady = false;
mScene->SetHighlightRenderMode(OutlineEnabled ? lcHighlightRenderMode::Outline : lcHighlightRenderMode::Tint);
mScene->Draw(mContext, [this, OutlineEnabled, DrawInterface, &OutlineMasksReady]()
{
if (OutlineEnabled)
OutlineMasksReady = GenerateOutlineMasks();
if (DrawInterface)
DrawGrid();
});
if (OutlineEnabled && OutlineMasksReady)
mContext->DrawOutline(*mOutlineFullFramebuffer, *mOutlineVisibleFramebuffer, mWidget->GetDeviceScale());
if (OutlineEnabled && !OutlineMasksReady)
{
mScene->SetHighlightRenderMode(lcHighlightRenderMode::Tint);
DrawBackground(CurrentTileRow, TotalTileRows, CurrentTileHeight);
mContext->SetProjectionMatrix(GetProjectionMatrix());
mScene->Draw(mContext, [this]() { DrawGrid(); });
}
if (!mRenderImage.isNull())
{
UnbindRenderFramebuffer();
QImage TileImage = GetRenderFramebufferImage();
BindRenderFramebuffer();
if (TileImage.isNull())
{
mRenderImage = QImage();
return;
}
const quint8* Buffer = TileImage.constBits();
uchar* ImageBuffer = mRenderImage.bits();
if (!ImageBuffer)
{
mRenderImage = QImage();
return;
}
quint32 TileY = 0, SrcY = 0;
if (CurrentTileRow != TotalTileRows - 1)
TileY = (TotalTileRows - CurrentTileRow - 1) * mHeight - ((mHeight - mRenderImage.height() % mHeight) % mHeight);
else if (TotalTileRows > 1)
SrcY = (mHeight - mRenderImage.height() % mHeight) % mHeight;
quint32 TileStart = ((CurrentTileColumn * mWidth) + (TileY * mRenderImage.width())) * 4;
for (int y = 0; y < CurrentTileHeight; y++)
{
const quint8* src = Buffer + (SrcY + y) * mWidth * 4;
quint8* dst = ImageBuffer + TileStart + y * mRenderImage.width() * 4;
memcpy(dst, src, CurrentTileWidth * 4);
}
}
}
}
if (DrawInterface)
mScene->DrawInterfaceObjects(mContext);
if (DrawOverlays)
DrawAxes();
if (DrawInterface && mTrackTool != lcTrackTool::Insert)
{
lcTool Tool = gMainWindow->GetTool();
lcModel* ActiveModel = GetActiveModel();
if ((Tool == lcTool::Select || Tool == lcTool::Move) && mTrackButton == lcTrackButton::None && ActiveModel->AnyObjectsSelected())
mViewManipulator->DrawSelectMove(mTrackButton, mTrackTool, mTrackToolSection);
else if (GetCurrentTool() == lcTool::Move && mTrackButton != lcTrackButton::None)
mViewManipulator->DrawSelectMove(mTrackButton, mTrackTool, mTrackToolSection);
else if ((Tool == lcTool::Rotate || (Tool == lcTool::Select && mTrackButton != lcTrackButton::None && mTrackTool >= lcTrackTool::RotateX && mTrackTool <= lcTrackTool::RotateCamera)) && ActiveModel->CanRotateSelection())
mViewManipulator->DrawRotate(mTrackButton, mTrackTool);
else if ((mTrackTool == lcTrackTool::Select || mTrackTool == lcTrackTool::ZoomRegion) && mTrackButton != lcTrackButton::None)
DrawSelectZoomRegionOverlay();
else if (Tool == lcTool::RotateView && mTrackButton == lcTrackButton::None)
DrawRotateViewOverlay();
}
if (DrawOverlays)
{
mViewSphere->Draw();
DrawViewport();
}
#ifdef LC_PROFILE_DRAW
qint64 TimerElapsed = Timer.nsecsElapsed();
Query.end();
GLuint64 QueryElapsed = Query.waitForResult();
QueryResults[FrameNumber % QueryResults.size()] = QueryElapsed;
TimerResults[FrameNumber % TimerResults.size()] = TimerElapsed;
FrameNumber++;
GLuint64 QueryAverage = 0;
for (GLuint64 Result : QueryResults)
QueryAverage += Result;
QueryAverage /= QueryResults.size();
GLuint64 TimerAverage = 0;
for (GLuint64 Result : TimerResults)
TimerAverage += Result;
TimerAverage /= TimerResults.size();
if (gTexFont.IsLoaded())
{
mContext->SetWorldMatrix(lcMatrix44Identity());
mContext->SetViewMatrix(lcMatrix44Translation(lcVector3(0.375, 0.375, 0.0)));
mContext->SetProjectionMatrix(lcMatrix44Ortho(0.0f, mWidth, 0.0f, mHeight, -1.0f, 1.0f));
QString Line = QString("GPU: %1 CPU: %2").arg(QString::number(QueryAverage / 1000000.0, 'f', 2), QString::number(TimerAverage / 1000000.0, 'f', 2));
mContext->SetMaterial(lcMaterialType::TextSDF);
mContext->SetColor(lcVector4FromColor(lcGetPreferences().mTextColor));
mContext->SetTextHaloColor(lcVector4FromColor(lcGetPreferences().mTextHaloColor));
mContext->BindTexture2D(gTexFont.GetTexture());
mContext->EnableDepthTest(false);
mContext->EnableColorBlend(true);
gTexFont.PrintText(mContext, 3.0f, (float)mHeight - 1.0f - 6.0f, 0.0f, Line.toLatin1().constData());
mContext->EnableColorBlend(false);
mContext->EnableDepthTest(true);
}
Redraw();
#endif
mContext->ClearResources();
}
bool lcView::HasMissingAssets() const
{
return mScene->HasMissingAssets();
}
void lcView::DrawBackground(int CurrentTileRow, int TotalTileRows, int CurrentTileHeight) const
{
if (mOverrideBackgroundColor)
{
lcVector4 BackgroundColor(lcVector4FromColor(mBackgroundColor));
mContext->ClearColorAndDepth(BackgroundColor);
return;
}
const lcPreferences& Preferences = lcGetPreferences();
float Alpha = mContext->IsOffscreen() ? 0.0f : 1.0f;
if (!Preferences.mBackgroundGradient)
{
lcVector4 BackgroundColor(lcVector3FromColor(Preferences.mBackgroundSolidColor), Alpha);
mContext->ClearColorAndDepth(BackgroundColor);
return;
}
mContext->ClearDepth();
mContext->SetDepthWrite(false);
mContext->EnableDepthTest(false);
float ViewWidth = (float)mWidth;
float ViewHeight = (float)mHeight;
mContext->SetWorldMatrix(lcMatrix44Identity());
mContext->SetViewMatrix(lcMatrix44Identity());
mContext->SetProjectionMatrix(lcMatrix44Ortho(0.0f, ViewWidth, 0.0f, ViewHeight, -1.0f, 1.0f));
const int TotalHeight = TotalTileRows == 1 ? mHeight : mRenderImage.height();
const quint32 TopY = CurrentTileRow * mHeight + CurrentTileHeight;
const double t1 = 1.0 - (double)TopY / (double)TotalHeight;
const double t2 = 1.0 - (double)(TopY - CurrentTileHeight) / (double)TotalHeight;
const quint32 ColorTop = Preferences.mBackgroundGradientColorTop;
const quint32 ColorBottom = Preferences.mBackgroundGradientColorBottom;
double TopRed = LC_RGBA_RED(ColorTop);
double TopGreen = LC_RGBA_GREEN(ColorTop);
double TopBlue = LC_RGBA_BLUE(ColorTop);
double BottomRed = LC_RGBA_RED(ColorBottom);
double BottomGreen = LC_RGBA_GREEN(ColorBottom);
double BottomBlue = LC_RGBA_BLUE(ColorBottom);
const double DeltaRed = BottomRed - TopRed;
const double DeltaGreen = BottomGreen - TopGreen;
const double DeltaBlue = BottomBlue - TopBlue;
BottomRed = TopRed + DeltaRed * t2;
BottomGreen = TopGreen + DeltaGreen * t2;
BottomBlue = TopBlue + DeltaBlue * t2;
TopRed = TopRed + DeltaRed * t1;
TopGreen = TopGreen + DeltaGreen * t1;
TopBlue = TopBlue + DeltaBlue * t1;
const quint32 Color1 = LC_RGBA(TopRed, TopGreen, TopBlue, Alpha);
const quint32 Color2 = LC_RGBA(BottomRed, BottomGreen, BottomBlue, Alpha);
struct lcBackgroundVertex
{
float x, y;
quint32 Color;
};
const lcBackgroundVertex Verts[4] =
{
{ ViewWidth, ViewHeight, Color1 }, { 0.0f, ViewHeight, Color1 }, { 0.0f, 0.0f, Color2 }, { ViewWidth, 0.0f, Color2 }
};
mContext->SetMaterial(lcMaterialType::UnlitVertexColor);
mContext->SetVertexBufferPointer(Verts);
mContext->SetVertexFormat(0, 2, 0, 0, 4, false);
mContext->DrawPrimitives(GL_TRIANGLE_FAN, 0, 4);
mContext->EnableDepthTest(true);
mContext->SetDepthWrite(true);
}
void lcView::DrawViewport() const
{
float Scale = GetUIScale();
float Width = mWidth / Scale;
float Height = mHeight / Scale;
mContext->SetWorldMatrix(lcMatrix44Identity());
mContext->SetViewMatrix(lcMatrix44Translation(lcVector3(0.375, 0.375, 0.0)));
mContext->SetProjectionMatrix(lcMatrix44Ortho(0.0f, Width, 0.0f, Height, -1.0f, 1.0f));
mContext->SetLineWidth(1.0f);
mContext->SetDepthWrite(false);
mContext->EnableDepthTest(false);
mContext->SetMaterial(lcMaterialType::UnlitColor);
if (mLastFocusedView == this)
mContext->SetColor(lcVector4FromColor(lcGetPreferences().mActiveViewColor));
else
mContext->SetColor(lcVector4FromColor(lcGetPreferences().mInactiveViewColor));
float Verts[8] = { 0.0f, 0.0f, Width - 1.0f, 0.0f, Width - 1.0f, Height - 1.0f, 0.0f, Height - 1.0f };
mContext->SetVertexBufferPointer(Verts);
mContext->SetVertexFormatPosition(2);
mContext->DrawPrimitives(GL_LINE_LOOP, 0, 4);
QString CameraName = mCamera->GetName();
if (!CameraName.isEmpty() && gTexFont.IsLoaded())
{
mContext->SetMaterial(lcMaterialType::TextSDF);
mContext->SetColor(lcVector4FromColor(lcGetPreferences().mTextColor));
mContext->SetTextHaloColor(lcVector4FromColor(lcGetPreferences().mTextHaloColor));
mContext->BindTexture2D(gTexFont.GetTexture());
mContext->EnableColorBlend(true);
// TODO: Make TexFont support Unicode camera names instead of converting them to Latin-1.
gTexFont.PrintText(mContext, 3.0f, (float)Height - 1.0f - 6.0f, 0.0f, CameraName.toLatin1().constData());
mContext->EnableColorBlend(false);
}
mContext->SetDepthWrite(true);
mContext->EnableDepthTest(true);
}
void lcView::DrawAxes() const
{
const lcPreferences& Preferences = lcGetPreferences();
switch (mViewType)
{
case lcViewType::View:
if (!Preferences.mDrawAxes)
return;
break;
case lcViewType::Preview:
if (!Preferences.mDrawPreviewAxis)
return;
break;
case lcViewType::Minifig:
case lcViewType::PartsList:
case lcViewType::Count:
return;
}
// mContext->ClearDepth();
struct lcAxisVertex
{
float x, y, z;
quint32 Color;
};
const quint32 Red = LC_RGBA(204, 0, 0, 255);
const quint32 Green = LC_RGBA(0, 204, 0, 255);
const quint32 Blue = LC_RGBA(0, 0, 204, 255);
const lcAxisVertex Verts[30] =
{
{ 0.00f, 0.00f, 0.00f, Red }, { 20.00f, 0.00f, 0.00f, Red }, { 12.00f, 3.00f, 0.00f, Red }, { 12.00f, 2.12f, 2.12f, Red }, { 12.00f, 0.00f, 3.00f, Red },
{ 12.00f, -2.12f, 2.12f, Red }, { 12.00f, -3.00f, 0.00f, Red }, { 12.00f, -2.12f, -2.12f, Red }, { 12.00f, 0.00f, -3.00f, Red }, { 12.00f, 2.12f, -2.12f, Red },
{ 0.00f, 0.00f, 0.00f, Green }, { 0.00f, 20.00f, 0.00f, Green }, { 3.00f, 12.00f, 0.00f, Green }, { 2.12f, 12.00f, 2.12f, Green }, { 0.00f, 12.00f, 3.00f, Green },
{ -2.12f, 12.00f, 2.12f, Green }, { -3.00f, 12.00f, 0.00f, Green }, { -2.12f, 12.00f, -2.12f, Green }, { 0.00f, 12.00f, -3.00f, Green }, { 2.12f, 12.00f, -2.12f, Green },
{ 0.00f, 0.00f, 0.00f, Blue }, { 0.00f, 0.00f, 20.00f, Blue }, { 0.00f, 3.00f, 12.00f, Blue }, { 2.12f, 2.12f, 12.00f, Blue }, { 3.00f, 0.00f, 12.00f, Blue },
{ 2.12f, -2.12f, 12.00f, Blue }, { 0.00f, -3.00f, 12.00f, Blue }, { -2.12f, -2.12f, 12.00f, Blue }, { -3.00f, 0.00f, 12.00f, Blue }, { -2.12f, 2.12f, 12.00f, Blue }
};
const GLushort Indices[78] =
{
0, 1, 10, 11, 20, 21,
1, 2, 3, 1, 3, 4, 1, 4, 5, 1, 5, 6, 1, 6, 7, 1, 7, 8, 1, 8, 9, 1, 9, 2,
11, 12, 13, 11, 13, 14, 11, 14, 15, 11, 15, 16, 11, 16, 17, 11, 17, 18, 11, 18, 19, 11, 19, 12,
21, 22, 23, 21, 23, 24, 21, 24, 25, 21, 25, 26, 21, 26, 27, 21, 27, 28, 21, 28, 29, 21, 29, 22
};
lcMatrix44 TranslationMatrix;
float Scale = GetUIScale();
switch (Preferences.mAxisIconLocation)
{
default:
case lcAxisIconLocation::BottomLeft:
TranslationMatrix = lcMatrix44Translation(lcVector3(32, 32, 0.0f) / Scale);
break;
case lcAxisIconLocation::BottomRight:
TranslationMatrix = lcMatrix44Translation(lcVector3(mWidth - 36, 32, 0.0f) / Scale);
break;
case lcAxisIconLocation::TopLeft:
TranslationMatrix = lcMatrix44Translation(lcVector3(32, mHeight - 36, 0.0f) / Scale);
break;
case lcAxisIconLocation::TopRight:
TranslationMatrix = lcMatrix44Translation(lcVector3(mWidth - 36, mHeight - 36, 0.0f) / Scale);
break;
}
lcMatrix44 WorldViewMatrix = mCamera->mWorldView;
WorldViewMatrix.SetTranslation(lcVector3(0, 0, 0));
mContext->SetLineWidth(1.0f);
mContext->SetMaterial(lcMaterialType::UnlitVertexColor);
mContext->SetWorldMatrix(lcMatrix44Identity());
mContext->SetViewMatrix(lcMul(WorldViewMatrix, TranslationMatrix));
mContext->SetProjectionMatrix(lcMatrix44Ortho(0, mWidth / Scale, 0, mHeight / Scale, -50, 50));
mContext->SetVertexBufferPointer(Verts);
mContext->SetVertexFormat(0, 3, 0, 0, 4, false);
mContext->SetIndexBufferPointer(Indices);
mContext->DrawIndexedPrimitives(GL_LINES, 6, GL_UNSIGNED_SHORT, 0);
mContext->DrawIndexedPrimitives(GL_TRIANGLES, 72, GL_UNSIGNED_SHORT, 6 * 2);
if (!gTexFont.IsLoaded())
return;
mContext->SetMaterial(lcMaterialType::TextSDF);
mContext->SetViewMatrix(TranslationMatrix);
mContext->BindTexture2D(gTexFont.GetTexture());
mContext->EnableColorBlend(true);
float TextBuffer[6 * 5 * 3];
lcVector3 PosX = lcMul30(lcVector3(25.0f, 0.0f, 0.0f), WorldViewMatrix);
gTexFont.GetGlyphTriangles(PosX.x, PosX.y, PosX.z, 'X', TextBuffer);
lcVector3 PosY = lcMul30(lcVector3(0.0f, 25.0f, 0.0f), WorldViewMatrix);
gTexFont.GetGlyphTriangles(PosY.x, PosY.y, PosY.z, 'Y', TextBuffer + 5 * 6);
lcVector3 PosZ = lcMul30(lcVector3(0.0f, 0.0f, 25.0f), WorldViewMatrix);
gTexFont.GetGlyphTriangles(PosZ.x, PosZ.y, PosZ.z, 'Z', TextBuffer + 5 * 6 * 2);
mContext->SetVertexBufferPointer(TextBuffer);
mContext->SetVertexFormat(0, 3, 0, 2, 0, false);
mContext->SetColor(lcVector4FromColor(lcGetPreferences().mTextColor));
mContext->SetTextHaloColor(lcVector4FromColor(lcGetPreferences().mTextHaloColor));
mContext->DrawPrimitives(GL_TRIANGLES, 0, 6 * 3);
mContext->EnableColorBlend(false);
}
void lcView::DrawSelectZoomRegionOverlay()
{
mContext->SetMaterial(lcMaterialType::UnlitColor);
mContext->SetWorldMatrix(lcMatrix44Identity());
mContext->SetViewMatrix(lcMatrix44Translation(lcVector3(0.375, 0.375, 0.0)));
mContext->SetProjectionMatrix(lcMatrix44Ortho(0.0f, mWidth, 0.0f, mHeight, -1.0f, 1.0f));
mContext->SetLineWidth(1.0f);
mContext->EnableDepthTest(false);
float pt1x = (float)mMouseDownX;
float pt1y = (float)mMouseDownY;
float pt2x = (float)mMouseX;
float pt2y = (float)mMouseY;
float Left, Right, Bottom, Top;
if (pt1x < pt2x)
{
Left = pt1x;
Right = pt2x;
}
else
{
Left = pt2x;
Right = pt1x;
}
if (pt1y < pt2y)
{
Bottom = pt1y;
Top = pt2y;
}
else
{
Bottom = pt2y;
Top = pt1y;
}
Left = lcMax(Left, 0.0f);
Right = lcMin(Right, mWidth - 1.0f);
Bottom = lcMax(Bottom, 0.0f);
Top = lcMin(Top, mHeight - 1.0f);
float BorderX = lcMin(2.0f, Right - Left);
float BorderY = lcMin(2.0f, Top - Bottom);
float Verts[14][2] =
{
{ Left, Bottom },
{ Left + BorderX, Bottom + BorderY },
{ Right, Bottom },
{ Right - BorderX, Bottom + BorderY },
{ Right, Top },
{ Right - BorderX, Top - BorderY },
{ Left, Top },
{ Left + BorderX, Top - BorderY },
{ Left, Bottom },
{ Left + BorderX, Bottom + BorderY },
{ Left + BorderX, Bottom + BorderY },
{ Right - BorderX, Bottom + BorderY },
{ Left + BorderX, Top - BorderY },
{ Right - BorderX, Top - BorderY },
};
mContext->SetVertexBufferPointer(Verts);
mContext->SetVertexFormatPosition(2);
const lcPreferences& Preferences = lcGetPreferences();
mContext->SetColor(lcVector4FromColor(Preferences.mMarqueeBorderColor));
mContext->DrawPrimitives(GL_TRIANGLE_STRIP, 0, 10);
if (LC_RGBA_ALPHA(Preferences.mMarqueeFillColor))
{
mContext->EnableColorBlend(true);
mContext->SetColor(lcVector4FromColor(Preferences.mMarqueeFillColor));
mContext->DrawPrimitives(GL_TRIANGLE_STRIP, 10, 4);
mContext->EnableColorBlend(false);
}
mContext->EnableDepthTest(true);
}
void lcView::DrawRotateViewOverlay()
{
int x, y, w, h;
x = 0;
y = 0;
w = mWidth;
h = mHeight;
mContext->SetMaterial(lcMaterialType::UnlitColor);
mContext->SetWorldMatrix(lcMatrix44Identity());
mContext->SetViewMatrix(lcMatrix44Translation(lcVector3(0.375, 0.375, 0.0)));
mContext->SetProjectionMatrix(lcMatrix44Ortho(0, w, 0, h, -1, 1));
mContext->SetLineWidth(1.0f);
mContext->EnableDepthTest(false);
mContext->SetColor(lcVector4FromColor(lcGetPreferences().mOverlayColor));
float Verts[32 * 16 * 2];
float* CurVert = Verts;
float r = lcMin(w, h) * 0.35f;
float cx = x + w / 2.0f;
float cy = y + h / 2.0f;
for (int i = 0; i < 32; i++)
{
*CurVert++ = cosf((float)i / 32.0f * (2.0f * LC_PI)) * r + cx;
*CurVert++ = sinf((float)i / 32.0f * (2.0f * LC_PI)) * r + cy;
}
const float OverlayCameraSquareSize = lcMax(8.0f, (w + h) / 200.0f);
*CurVert++ = cx + OverlayCameraSquareSize; *CurVert++ = cy + r + OverlayCameraSquareSize;
*CurVert++ = cx - OverlayCameraSquareSize; *CurVert++ = cy + r + OverlayCameraSquareSize;
*CurVert++ = cx - OverlayCameraSquareSize; *CurVert++ = cy + r - OverlayCameraSquareSize;
*CurVert++ = cx + OverlayCameraSquareSize; *CurVert++ = cy + r - OverlayCameraSquareSize;
*CurVert++ = cx + OverlayCameraSquareSize; *CurVert++ = cy - r + OverlayCameraSquareSize;
*CurVert++ = cx - OverlayCameraSquareSize; *CurVert++ = cy - r + OverlayCameraSquareSize;
*CurVert++ = cx - OverlayCameraSquareSize; *CurVert++ = cy - r - OverlayCameraSquareSize;
*CurVert++ = cx + OverlayCameraSquareSize; *CurVert++ = cy - r - OverlayCameraSquareSize;
*CurVert++ = cx + r + OverlayCameraSquareSize; *CurVert++ = cy + OverlayCameraSquareSize;
*CurVert++ = cx + r - OverlayCameraSquareSize; *CurVert++ = cy + OverlayCameraSquareSize;
*CurVert++ = cx + r - OverlayCameraSquareSize; *CurVert++ = cy - OverlayCameraSquareSize;
*CurVert++ = cx + r + OverlayCameraSquareSize; *CurVert++ = cy - OverlayCameraSquareSize;
*CurVert++ = cx - r + OverlayCameraSquareSize; *CurVert++ = cy + OverlayCameraSquareSize;
*CurVert++ = cx - r - OverlayCameraSquareSize; *CurVert++ = cy + OverlayCameraSquareSize;
*CurVert++ = cx - r - OverlayCameraSquareSize; *CurVert++ = cy - OverlayCameraSquareSize;
*CurVert++ = cx - r + OverlayCameraSquareSize; *CurVert++ = cy - OverlayCameraSquareSize;
mContext->SetVertexBufferPointer(Verts);
mContext->SetVertexFormatPosition(2);
GLushort Indices[64 + 32] =
{
0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 0,
32, 33, 33, 34, 34, 35, 35, 32, 36, 37, 37, 38, 38, 39, 39, 36,
40, 41, 41, 42, 42, 43, 43, 40, 44, 45, 45, 46, 46, 47, 47, 44
};
mContext->SetIndexBufferPointer(Indices);
mContext->DrawIndexedPrimitives(GL_LINES, 96, GL_UNSIGNED_SHORT, 0);
mContext->EnableDepthTest(true);
}
void lcView::DrawGrid()
{
const lcPreferences& Preferences = lcGetPreferences();
if (!Preferences.mDrawGridStuds && !Preferences.mDrawGridLines && !Preferences.mDrawGridOrigin)
return;
if (!Preferences.mGridEnabled)
return;
const int Spacing = lcMax(Preferences.mGridLineSpacing, 1);
int MinX, MaxX, MinY, MaxY;
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
bool GridSizeValid = mModel->GetVisiblePiecesBoundingBox(Min, Max);
bool DrawInsertPreview = std::any_of(mViews.begin(), mViews.end(), [](lcView* View){ return View->mTrackTool == lcTrackTool::Insert; });
if (DrawInsertPreview)
{
const lcModel* PreviewModel = GetActiveModel();
for (const lcPreviewInsertPieceInfo& PreviewInsertPieceInfo : PreviewModel->GetPreviewInsertPieceInfo())
{
lcVector3 Points[8];
lcGetBoxCorners(PreviewInsertPieceInfo.PieceData->GetBoundingBox(), Points);
lcMatrix44 PreviewTransform = PreviewInsertPieceInfo.Transform;
if (PreviewModel != mModel)
PreviewTransform = lcMul(PreviewTransform, mActiveSubmodelTransform);
for (int i = 0; i < 8; i++)
{
lcVector3 Point = lcMul31(Points[i], PreviewTransform);
Min = lcMin(Point, Min);
Max = lcMax(Point, Max);
}
GridSizeValid = true;
}
}
if (GridSizeValid && std::isfinite(Min[0]) && std::isfinite(Min[1]) && std::isfinite(Max[0]) && std::isfinite(Max[1]))
{
// Bound grid coordinates before converting to integers, keeping the vertex buffer below 1 MB.
constexpr float MaxGridExtent = 10000.0f;
const float GridSpacing = 20.0f * Spacing;
MinX = static_cast<int>(floorf(lcClamp(Min[0] / GridSpacing, -MaxGridExtent + 1.0f, MaxGridExtent - 1.0f))) - 1;
MinY = static_cast<int>(floorf(lcClamp(Min[1] / GridSpacing, -MaxGridExtent + 1.0f, MaxGridExtent - 1.0f))) - 1;
MaxX = static_cast<int>(ceilf(lcClamp(Max[0] / GridSpacing, -MaxGridExtent + 1.0f, MaxGridExtent - 1.0f))) + 1;
MaxY = static_cast<int>(ceilf(lcClamp(Max[1] / GridSpacing, -MaxGridExtent + 1.0f, MaxGridExtent - 1.0f))) + 1;
MinX = lcMin(MinX, -2);
MinY = lcMin(MinY, -2);
MaxX = lcMax(MaxX, 2);
MaxY = lcMax(MaxY, 2);
}
else
{
MinX = -2;
MinY = -2;
MaxX = 2;
MaxY = 2;
}
if (!mGridBuffer.IsValid() || MinX != mGridSettings[0] || MinY != mGridSettings[1] || MaxX != mGridSettings[2] || MaxY != mGridSettings[3] ||
Spacing != mGridSettings[4] || (Preferences.mDrawGridStuds ? 1 : 0) != mGridSettings[5] || (Preferences.mDrawGridLines ? 1 : 0) != mGridSettings[6])
{
int VertexBufferSize = 0;
if (Preferences.mDrawGridStuds)
VertexBufferSize += 4 * 5 * sizeof(float);
if (Preferences.mDrawGridLines)
VertexBufferSize += 2 * (MaxX - MinX + MaxY - MinY + 2) * 3 * sizeof(float);
float* Verts = nullptr;
if (VertexBufferSize)
{
Verts = static_cast<float*>(malloc(VertexBufferSize));
if (!Verts)
return;
float* CurVert = Verts;
if (Preferences.mDrawGridStuds)
{
float Left = MinX * 20.0f * Spacing;
float Right = MaxX * 20.0f * Spacing;
float Top = MinY * 20.0f * Spacing;
float Bottom = MaxY * 20.0f * Spacing;
float Z = 0;
float U = (MaxX - MinX) * static_cast<float>(Spacing);
float V = (MaxY - MinY) * static_cast<float>(Spacing);
*CurVert++ = Left;
*CurVert++ = Top;
*CurVert++ = Z;
*CurVert++ = 0.0f;
*CurVert++ = V;
*CurVert++ = Right;
*CurVert++ = Top;
*CurVert++ = Z;
*CurVert++ = U;
*CurVert++ = V;
*CurVert++ = Left;
*CurVert++ = Bottom;
*CurVert++ = Z;
*CurVert++ = 0.0f;
*CurVert++ = 0.0f;
*CurVert++ = Right;
*CurVert++ = Bottom;
*CurVert++ = Z;
*CurVert++ = U;
*CurVert++ = 0.0f;
}
if (Preferences.mDrawGridLines)
{
float LineSpacing = Spacing * 20.0f;
for (int Step = MinX; Step < MaxX + 1; Step++)
{
*CurVert++ = Step * LineSpacing;
*CurVert++ = MinY * LineSpacing;
*CurVert++ = 0.0f;
*CurVert++ = Step * LineSpacing;
*CurVert++ = MaxY * LineSpacing;
*CurVert++ = 0.0f;
}
for (int Step = MinY; Step < MaxY + 1; Step++)
{
*CurVert++ = MinX * LineSpacing;
*CurVert++ = Step * LineSpacing;
*CurVert++ = 0.0f;
*CurVert++ = MaxX * LineSpacing;
*CurVert++ = Step * LineSpacing;
*CurVert++ = 0.0f;
}
}
}
mGridSettings[0] = MinX;
mGridSettings[1] = MinY;
mGridSettings[2] = MaxX;
mGridSettings[3] = MaxY;
mGridSettings[4] = Spacing;
mGridSettings[5] = (Preferences.mDrawGridStuds ? 1 : 0);
mGridSettings[6] = (Preferences.mDrawGridLines ? 1 : 0);
mContext->DestroyVertexBuffer(mGridBuffer);
mGridBuffer = mContext->CreateVertexBuffer(VertexBufferSize, Verts);
free(Verts);
}
int BufferOffset = 0;
mContext->SetVertexBuffer(mGridBuffer);
mContext->SetWorldMatrix(lcMatrix44Identity());
if (Preferences.mDrawGridStuds)
{
mContext->BindTexture2D(gGridTexture);
mContext->SetDepthWrite(false);
mContext->EnableColorBlend(true);
mContext->SetMaterial(lcMaterialType::UnlitTextureModulate);
mContext->SetColor(lcVector4FromColor(Preferences.mGridStudColor));
mContext->SetVertexFormat(0, 3, 0, 2, 0, false);
mContext->DrawPrimitives(GL_TRIANGLE_STRIP, 0, 4);
mContext->EnableColorBlend(false);
mContext->SetDepthWrite(true);
BufferOffset = 4 * 5 * sizeof(float);
}
if (Preferences.mDrawGridLines)
{
mContext->SetLineWidth(1.0f);
mContext->SetMaterial(lcMaterialType::UnlitColor);
mContext->SetColor(lcVector4FromColor(Preferences.mGridLineColor));
int NumVerts = 2 * (MaxX - MinX + MaxY - MinY + 2);
mContext->SetVertexFormat(BufferOffset, 3, 0, 0, 0, false);
mContext->DrawPrimitives(GL_LINES, 0, NumVerts);
}
if (Preferences.mDrawGridOrigin)
{
struct lcGridVertex
{
float x, y;
quint32 Color;
};
const quint32 Red = LC_RGBA(204, 0, 0, 255);
const quint32 Green = LC_RGBA(0, 204, 0, 255);
const float Scale = 20.0f * Spacing;
const lcGridVertex Verts[4] =
{
{ 0.0f, MinY * Scale, Green }, { 0.0f, MaxY * Scale, Green }, { MinX * Scale, 0.0f, Red }, { MaxX * Scale, 0.0f, Red }
};
mContext->SetMaterial(lcMaterialType::UnlitVertexColor);
mContext->SetVertexBufferPointer(Verts);
mContext->SetVertexFormat(0, 2, 0, 0, 4, false);
mContext->DrawPrimitives(GL_LINES, 0, 4);
}
}
lcTrackTool lcView::GetOverrideTrackTool(Qt::MouseButton Button) const
{
if (mTrackToolFromOverlay)
return lcTrackTool::None;
lcTool OverrideTool = gMouseShortcuts.GetTool(Button, mMouseModifiers);
if (OverrideTool == lcTool::Count)
return lcTrackTool::None;
constexpr lcTrackTool TrackToolFromTool[] =
{
lcTrackTool::Insert, // lcTool::Insert
lcTrackTool::PointLight, // lcTool::PointLight
lcTrackTool::SpotLight, // lcTool::SpotLight
lcTrackTool::DirectionalLight, // lcTool::DirectionalLight
lcTrackTool::AreaLight, // lcTool::AreaLight
lcTrackTool::Camera, // lcTool::Camera
lcTrackTool::Select, // lcTool::Select
lcTrackTool::MoveXYZ, // lcTool::Move
lcTrackTool::RotateTrackBall, // lcTool::Rotate
lcTrackTool::Eraser, // lcTool::Eraser
lcTrackTool::Paint, // lcTool::Paint
lcTrackTool::ColorPicker, // lcTool::ColorPicker
lcTrackTool::Zoom, // lcTool::Zoom
lcTrackTool::Pan, // lcTool::Pan
lcTrackTool::OrbitXY, // lcTool::RotateView
lcTrackTool::Roll, // lcTool::Roll
lcTrackTool::ZoomRegion // lcTool::ZoomRegion
};
LC_ARRAY_SIZE_CHECK(TrackToolFromTool, lcTool::Count);
return TrackToolFromTool[static_cast<int>(OverrideTool)];
}
float lcView::GetUIScale() const
{
return mWidget ? mWidget->GetDeviceScale() : 1.0f;
}
float lcView::GetOverlayScale() const
{
lcVector3 OverlayCenter;
lcMatrix33 RelativeRotation;
lcModel* ActiveModel = GetActiveModel();
ActiveModel->GetMoveRotateTransform(OverlayCenter, RelativeRotation);
lcMatrix44 WorldMatrix = lcMatrix44(RelativeRotation, OverlayCenter);
if (ActiveModel != mModel)
WorldMatrix = lcMul(WorldMatrix, mActiveSubmodelTransform);
lcVector3 ScreenPos = ProjectPoint(WorldMatrix.GetTranslation());
ScreenPos[0] += 10.0f;
lcVector3 Point = UnprojectPoint(ScreenPos);
lcVector3 Dist(Point - WorldMatrix.GetTranslation());
return Dist.Length() * 5.0f * GetUIScale();
}
void lcView::BeginDrag(lcDragState DragState)
{
mDragState = DragState;
UpdateTrackTool();
}
void lcView::EndDrag(bool Accept)
{
lcModel* ActiveModel = GetActiveModel();
if (Accept)
{
switch (mDragState)
{
case lcDragState::None:
break;
case lcDragState::Piece:
ActiveModel->InsertPieceToolClicked();
break;
case lcDragState::Color:
ActiveModel->PaintToolClicked(FindObjectUnderPointer(true, false).Object);
break;
}
}
mDragState = lcDragState::None;
UpdateTrackTool();
ActiveModel->SetPreviewInsertPieceInfo(std::vector<lcInsertPieceInfo>());
ActiveModel->UpdateAllViews();
}
void lcView::SetViewpoint(lcViewpoint Viewpoint)
{
if (!mCamera || !mCamera->IsSimple())
{
lcCamera* OldCamera = mCamera;
mCamera = new lcCamera(true);
if (OldCamera)
mCamera->CopySettings(OldCamera);
}
mCamera->SetViewpoint(Viewpoint);
ZoomExtents(lcGeometryBoundsMode::IncludeFallback, false);
Redraw();
}
void lcView::SetViewpoint(const lcVector3& Position)
{
if (!mCamera || !mCamera->IsSimple())
{
lcCamera* OldCamera = mCamera;
mCamera = new lcCamera(true);
if (OldCamera)
mCamera->CopySettings(OldCamera);
}
mCamera->SetViewpoint(Position);
ZoomExtents(lcGeometryBoundsMode::IncludeFallback, false);
Redraw();
}
void lcView::SetViewpoint(const lcVector3& Position, const lcVector3& Target, const lcVector3& Up)
{
if (!mCamera || !mCamera->IsSimple())
{
lcCamera* OldCamera = mCamera;
mCamera = new lcCamera(true);
if (OldCamera)
mCamera->CopySettings(OldCamera);
}
mCamera->SetViewpoint(Position, Target, Up);
Redraw();
}
void lcView::SetCameraAngles(float Latitude, float Longitude)
{
if (!mCamera || !mCamera->IsSimple())
{
lcCamera* OldCamera = mCamera;
mCamera = new lcCamera(true);
if (OldCamera)
mCamera->CopySettings(OldCamera);
}
mCamera->SetAngles(Latitude, Longitude, 1.0f);
ZoomExtents(lcGeometryBoundsMode::IncludeFallback, false);
Redraw();
}
void lcView::SetDefaultCamera()
{
if (!mCamera || !mCamera->IsSimple())
mCamera = new lcCamera(true);
mCamera->SetViewpoint(lcViewpoint::Home);
ZoomExtents(lcGeometryBoundsMode::IncludeFallback, false);
Redraw();
}
void lcView::SetCamera(lcCamera* Camera, bool ForceCopy)
{
if (Camera->IsSimple() || ForceCopy)
{
if (!mCamera || !mCamera->IsSimple())
mCamera = new lcCamera(true);
mCamera->CopyPosition(Camera);
}
else
{
if (mCamera && mCamera->IsSimple())
delete mCamera;
mCamera = Camera;
}
}
void lcView::SetCamera(const QString& CameraName)
{
const std::vector<std::unique_ptr<lcCamera>>& Cameras = mModel->GetCameras();
for (size_t CameraIndex = 0; CameraIndex < Cameras.size(); CameraIndex++)
{
if (CameraName.compare(Cameras[CameraIndex]->GetName(), Qt::CaseInsensitive) == 0)
{
SetCameraIndex(CameraIndex);
return;
}
}
}
void lcView::SetCameraIndex(size_t CameraIndex)
{
const std::vector<std::unique_ptr<lcCamera>>& Cameras = mModel->GetCameras();
if (CameraIndex >= Cameras.size())
return;
lcCamera* Camera = Cameras[CameraIndex].get();
SetCamera(Camera, false);
Redraw();
}
void lcView::SetCameraProjection(lcCameraProjection CameraProjection)
{
if (mCamera->IsSimple())
{
mCamera->SetProjection(CameraProjection);
Redraw();
}
else
{
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel)
ActiveModel->SetCameraProjection(mCamera, CameraProjection);
}
}
void lcView::LookAt()
{
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel)
ActiveModel->LookAt(mCamera);
}
void lcView::MoveCamera(const lcVector3& Direction)
{
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel)
ActiveModel->MoveCamera(mCamera, Direction);
}
void lcView::Zoom(float Amount)
{
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel)
ActiveModel->Zoom(mCamera, Amount);
}
void lcView::ZoomExtents(lcGeometryBoundsMode Mode, bool AdjustFarPlane)
{
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel)
{
const lcMatrix44 WorldMatrix = mActiveSubmodelInstance ? mActiveSubmodelTransform : lcMatrix44Identity();
ActiveModel->ZoomExtents(mCamera, (float)mWidth / (float)mHeight, WorldMatrix, Mode, AdjustFarPlane);
}
}
void lcView::ZoomExtentsAtStep(lcStep Step, lcGeometryBoundsMode Mode, bool AdjustFarPlane)
{
lcModel* ActiveModel = GetActiveModel();
if (!ActiveModel)
return;
const lcStep CurrentStep = ActiveModel->GetCurrentStep();
ActiveModel->SetTemporaryStep(Step);
ZoomExtents(Mode, AdjustFarPlane);
ActiveModel->SetTemporaryStep(CurrentStep);
if (!ActiveModel->IsActive())
ActiveModel->CalculateStep(LC_STEP_MAX);
}
lcCursor lcView::GetCursor() const
{
const lcTool CurrentTool = GetCurrentTool();
const bool IsMoveTool = mTrackTool >= lcTrackTool::MoveX && mTrackTool <= lcTrackTool::MoveXYZ;
if (mTrackButton != lcTrackButton::None && mTrackTool != lcTrackTool::Paint && !IsMoveTool && CurrentTool != lcTool::Rotate)
return lcCursor::Hidden;
if (mTrackTool == lcTrackTool::Select)
{
if (mMouseModifiers & Qt::ControlModifier)
return lcCursor::SelectAdd;
if (mMouseModifiers & Qt::ShiftModifier)
return lcCursor::SelectRemove;
}
constexpr lcCursor CursorFromTrackTool[] =
{
lcCursor::Select, // lcTrackTool::None
lcCursor::Brick, // lcTrackTool::Insert
lcCursor::PointLight, // lcTrackTool::PointLight
lcCursor::SpotLight, // lcTrackTool::SpotLight
lcCursor::DirectionalLight, // lcTrackTool::DirectionalLight
lcCursor::AreaLight, // lcTrackTool::AreaLight
lcCursor::Camera, // lcTrackTool::Camera
lcCursor::Select, // lcTrackTool::Select
lcCursor::Default, // lcTrackTool::MoveX
lcCursor::Default, // lcTrackTool::MoveY
lcCursor::Default, // lcTrackTool::MoveZ
lcCursor::Default, // lcTrackTool::MoveXY
lcCursor::Default, // lcTrackTool::MoveXZ
lcCursor::Default, // lcTrackTool::MoveYZ
lcCursor::Default, // lcTrackTool::MoveXYZ
lcCursor::Default, // lcTrackTool::RotateX
lcCursor::Default, // lcTrackTool::RotateY
lcCursor::Default, // lcTrackTool::RotateZ
lcCursor::Default, // lcTrackTool::RotateTrackBall
lcCursor::Default, // lcTrackTool::RotateCamera
lcCursor::Select, // lcTrackTool::RotateTrainTrackRight
lcCursor::Select, // lcTrackTool::RotateTrainTrackLeft
lcCursor::Select, // lcTrackTool::InsertTrainTrack
lcCursor::Select, // lcTrackTool::SelectTrainTrack
lcCursor::Move, // lcTrackTool::ScalePlus
lcCursor::Move, // lcTrackTool::ScaleMinus
lcCursor::Delete, // lcTrackTool::Eraser
lcCursor::Paint, // lcTrackTool::Paint
lcCursor::ColorPicker, // lcTrackTool::ColorPicker
lcCursor::Zoom, // lcTrackTool::Zoom
lcCursor::Pan, // lcTrackTool::Pan
lcCursor::RotateX, // lcTrackTool::OrbitX
lcCursor::RotateY, // lcTrackTool::OrbitY
lcCursor::RotateView, // lcTrackTool::OrbitXY
lcCursor::Roll, // lcTrackTool::Roll
lcCursor::ZoomRegion // lcTrackTool::ZoomRegion
};
LC_ARRAY_SIZE_CHECK(CursorFromTrackTool, lcTrackTool::Count);
if (mTrackTool >= lcTrackTool::None && mTrackTool < lcTrackTool::Count)
return CursorFromTrackTool[static_cast<int>(mTrackTool)];
return lcCursor::Select;
}
void lcView::SetCursor(lcCursor CursorType)
{
if (mCursor == CursorType)
return;
struct lcCursorInfo
{
int x, y;
const char* Name;
};
constexpr lcCursorInfo Cursors[] =
{
{ 0, 0, "" }, // lcCursor::Hidden
{ 0, 0, "" }, // lcCursor::Default
{ 8, 3, ":/resources/cursor_insert" }, // lcCursor::Brick
{ 15, 15, ":/resources/cursor_light" }, // lcCursor::PointLight
{ 7, 10, ":/resources/cursor_spotlight" }, // lcCursor::SpotLight
{ 15, 15, ":/resources/cursor_sunlight" }, // lcCursor::DirectionalLight
{ 15, 15, ":/resources/cursor_arealight" }, // lcCursor::AreaLight
{ 15, 9, ":/resources/cursor_camera" }, // lcCursor::Camera
{ 0, 2, ":/resources/cursor_select" }, // lcCursor::Select
{ 0, 2, ":/resources/cursor_select_add" }, // lcCursor::SelectAdd
{ 0, 2, ":/resources/cursor_select_remove" }, // lcCursor::SelectRemove
{ 15, 15, ":/resources/cursor_move" }, // lcCursor::Move
{ 15, 15, ":/resources/cursor_rotate" }, // lcCursor::Rotate
{ 15, 15, ":/resources/cursor_rotatex" }, // lcCursor::RotateX
{ 15, 15, ":/resources/cursor_rotatey" }, // lcCursor::RotateY
{ 0, 10, ":/resources/cursor_delete" }, // lcCursor::Delete
{ 14, 14, ":/resources/cursor_paint" }, // lcCursor::Paint
{ 1, 13, ":/resources/cursor_color_picker" }, // lcCursor::ColorPicker
{ 15, 15, ":/resources/cursor_zoom" }, // lcCursor::Zoom
{ 9, 9, ":/resources/cursor_zoom_region" }, // lcCursor::ZoomRegion
{ 15, 15, ":/resources/cursor_pan" }, // lcCursor::Pan
{ 15, 15, ":/resources/cursor_roll" }, // lcCursor::Roll
{ 15, 15, ":/resources/cursor_rotate_view" }, // lcCursor::RotateView
};
LC_ARRAY_SIZE_CHECK(Cursors, lcCursor::Count);
if (CursorType == lcCursor::Hidden)
{
mWidget->setCursor(Qt::BlankCursor);
mCursor = CursorType;
}
else if (CursorType == lcCursor::Default)
{
mWidget->unsetCursor();
mCursor = CursorType;
}
else if (CursorType >= lcCursor::First && CursorType < lcCursor::Count)
{
const lcCursorInfo& Cursor = Cursors[static_cast<int>(CursorType)];
mWidget->setCursor(QCursor(QPixmap(Cursor.Name), Cursor.x, Cursor.y));
mCursor = CursorType;
}
else
{
mWidget->unsetCursor();
mCursor = lcCursor::Default;
}
}
void lcView::UpdateCursor()
{
SetCursor(GetCursor());
}
lcTool lcView::GetCurrentTool() const
{
constexpr lcTool ToolFromTrackTool[] =
{
lcTool::Select, // lcTrackTool::None
lcTool::Insert, // lcTrackTool::Insert
lcTool::PointLight, // lcTrackTool::PointLight
lcTool::SpotLight, // lcTrackTool::SpotLight
lcTool::DirectionalLight, // lcTrackTool::DirectionalLight
lcTool::AreaLight, // lcTrackTool::AreaLight
lcTool::Camera, // lcTrackTool::Camera
lcTool::Select, // lcTrackTool::Select
lcTool::Move, // lcTrackTool::MoveX
lcTool::Move, // lcTrackTool::MoveY
lcTool::Move, // lcTrackTool::MoveZ
lcTool::Move, // lcTrackTool::MoveXY
lcTool::Move, // lcTrackTool::MoveXZ
lcTool::Move, // lcTrackTool::MoveYZ
lcTool::Move, // lcTrackTool::MoveXYZ
lcTool::Rotate, // lcTrackTool::RotateX
lcTool::Rotate, // lcTrackTool::RotateY
lcTool::Rotate, // lcTrackTool::RotateZ
lcTool::Rotate, // lcTrackTool::RotateTrackBall
lcTool::Rotate, // lcTrackTool::RotateCamera
lcTool::Rotate, // lcTrackTool::RotateTrainTrackRight
lcTool::Rotate, // lcTrackTool::RotateTrainTrackLeft
lcTool::Insert, // lcTrackTool::InsertTrainTrack
lcTool::Select, // lcTrackTool::SelectTrainTrack
lcTool::Move, // lcTrackTool::ScalePlus
lcTool::Move, // lcTrackTool::ScaleMinus
lcTool::Eraser, // lcTrackTool::Eraser
lcTool::Paint, // lcTrackTool::Paint
lcTool::ColorPicker, // lcTrackTool::ColorPicker
lcTool::Zoom, // lcTrackTool::Zoom
lcTool::Pan, // lcTrackTool::Pan
lcTool::RotateView, // lcTrackTool::OrbitX
lcTool::RotateView, // lcTrackTool::OrbitY
lcTool::RotateView, // lcTrackTool::OrbitXY
lcTool::Roll, // lcTrackTool::Roll
lcTool::ZoomRegion // lcTrackTool::ZoomRegion
};
LC_ARRAY_SIZE_CHECK(ToolFromTrackTool, lcTrackTool::Count);
if (mTrackTool >= lcTrackTool::None && mTrackTool < lcTrackTool::Count)
return ToolFromTrackTool[static_cast<int>(mTrackTool)];
return lcTool::Select;
}
void lcView::UpdateTrackTool()
{
if (mViewType != lcViewType::View)
{
mTrackTool = lcTrackTool::None;
UpdateCursor();
return;
}
lcTool CurrentTool = gMainWindow->GetTool();
lcTrackTool NewTrackTool = mTrackTool;
quint32 NewTrackSection = ~0U;
int x = mMouseX;
int y = mMouseY;
bool Redraw = false;
mMouseDownPiece = nullptr;
mTrackToolFromOverlay = false;
lcModel* ActiveModel = GetActiveModel();
switch (CurrentTool)
{
case lcTool::Insert:
NewTrackTool = lcTrackTool::Insert;
break;
case lcTool::PointLight:
NewTrackTool = lcTrackTool::PointLight;
break;
case lcTool::SpotLight:
NewTrackTool = lcTrackTool::SpotLight;
break;
case lcTool::DirectionalLight:
NewTrackTool = lcTrackTool::DirectionalLight;
break;
case lcTool::AreaLight:
NewTrackTool = lcTrackTool::AreaLight;
break;
case lcTool::Camera:
NewTrackTool = lcTrackTool::Camera;
break;
case lcTool::Select:
case lcTool::Move:
{
std::tie(NewTrackTool, NewTrackSection) = mViewManipulator->UpdateSelectMove(mTrackButton);
mTrackToolFromOverlay = NewTrackTool != lcTrackTool::MoveXYZ && NewTrackTool != lcTrackTool::Select;
Redraw = NewTrackTool != mTrackTool || NewTrackSection != mTrackToolSection;
if (CurrentTool == lcTool::Select && NewTrackTool == lcTrackTool::Select && mMouseModifiers == Qt::NoModifier)
{
lcObjectSection ObjectSection = FindObjectUnderPointer(false, false);
lcObject* Object = ObjectSection.Object;
if (Object && Object->IsPiece() && ObjectSection.Section == LC_PIECE_SECTION_POSITION && Object->IsSelected())
{
lcPiece* Piece = (lcPiece*)Object;
mMouseDownTransform = Piece->mModelWorld;
mMouseDownPiece = Piece;
NewTrackTool = lcTrackTool::MoveXYZ;
}
}
}
break;
case lcTool::Rotate:
{
NewTrackTool = mViewManipulator->UpdateRotate();
mTrackToolFromOverlay = NewTrackTool != lcTrackTool::RotateTrackBall;
Redraw = NewTrackTool != mTrackTool;
}
break;
case lcTool::Eraser:
NewTrackTool = lcTrackTool::Eraser;
break;
case lcTool::Paint:
NewTrackTool = lcTrackTool::Paint;
break;
case lcTool::ColorPicker:
NewTrackTool = lcTrackTool::ColorPicker;
break;
case lcTool::Zoom:
NewTrackTool = lcTrackTool::Zoom;
break;
case lcTool::Pan:
NewTrackTool = lcTrackTool::Pan;
break;
case lcTool::RotateView:
{
int vx, vy, vw, vh;
vx = 0;
vy = 0;
vw = mWidth;
vh = mHeight;
int cx = vx + vw / 2;
int cy = vy + vh / 2;
float d = sqrtf((float)((cx - x) * (cx - x) + (cy - y) * (cy - y)));
float r = lcMin(vw, vh) * 0.35f;
const float SquareSize = lcMax(8.0f, (vw + vh) / 200.0f);
if ((d < r + SquareSize) && (d > r - SquareSize))
{
if ((cx - x < SquareSize) && (cx - x > -SquareSize))
{
NewTrackTool = lcTrackTool::OrbitY;
mTrackToolFromOverlay = true;
}
if ((cy - y < SquareSize) && (cy - y > -SquareSize))
{
NewTrackTool = lcTrackTool::OrbitX;
mTrackToolFromOverlay = true;
}
}
else
{
if (d < r)
NewTrackTool = lcTrackTool::OrbitXY;
else
NewTrackTool = lcTrackTool::Roll;
}
}
break;
case lcTool::Roll:
NewTrackTool = lcTrackTool::Roll;
break;
case lcTool::ZoomRegion:
NewTrackTool = lcTrackTool::ZoomRegion;
break;
case lcTool::Count:
break;
}
switch (mDragState)
{
case lcDragState::None:
break;
case lcDragState::Piece:
NewTrackTool = lcTrackTool::Insert;
Redraw = true;
break;
case lcDragState::Color:
NewTrackTool = lcTrackTool::Paint;
break;
}
mTrackTool = NewTrackTool;
mTrackToolSection = NewTrackSection;
UpdateCursor();
if (Redraw)
ActiveModel->UpdateAllViews();
}
void lcView::StartOrbitTracking()
{
mTrackTool = lcTrackTool::OrbitXY;
UpdateCursor();
OnButtonDown(lcTrackButton::Left);
}
void lcView::StartPanGesture()
{
lcModel* ActiveModel = GetActiveModel();
StartPan(mWidth / 2, mHeight / 2);
ActiveModel->BeginMouseTool(lcTool::Pan, this);
}
void lcView::UpdatePanGesture(int dx, int dy)
{
UpdatePan(mPanX + dx, mPanY + dy);
}
void lcView::StartPan(int x, int y)
{
mPanX = x;
mPanY = y;
}
void lcView::UpdatePan(int x, int y)
{
if (x == mPanX && y == mPanY)
return;
lcModel* ActiveModel = GetActiveModel();
lcVector3 Points[4] =
{
lcVector3((float)x, (float)y, 0.0f),
lcVector3((float)x, (float)y, 1.0f),
lcVector3(mPanX, mPanY, 0.0f),
lcVector3(mPanX, mPanY, 1.0f)
};
UnprojectPoints(Points, 4);
const lcVector3& CurrentStart = Points[0];
const lcVector3& CurrentEnd = Points[1];
const lcVector3& MouseDownStart = Points[2];
const lcVector3& MouseDownEnd = Points[3];
lcVector3 Center = ActiveModel->GetSelectionOrModelCenter();
lcVector3 PlaneNormal(mCamera->mPosition - mCamera->mTargetPosition);
lcVector4 Plane(PlaneNormal, -lcDot(PlaneNormal, Center));
lcVector3 Intersection, MoveStart;
if (!lcLineSegmentPlaneIntersection(&Intersection, CurrentStart, CurrentEnd, Plane) || !lcLineSegmentPlaneIntersection(&MoveStart, MouseDownStart, MouseDownEnd, Plane))
{
Center = MouseDownStart + lcNormalize(MouseDownEnd - MouseDownStart) * 10.0f;
Plane = lcVector4(PlaneNormal, -lcDot(PlaneNormal, Center));
if (!lcLineSegmentPlaneIntersection(&Intersection, CurrentStart, CurrentEnd, Plane) || !lcLineSegmentPlaneIntersection(&MoveStart, MouseDownStart, MouseDownEnd, Plane))
return;
}
mPanX = x;
mPanY = y;
ActiveModel->UpdatePanTool(mCamera, MoveStart - Intersection);
}
void lcView::EndPanGesture(bool Accept)
{
lcModel* ActiveModel = GetActiveModel();
ActiveModel->EndMouseTool(lcTool::Pan, this, Accept);
}
lcVector3 lcView::GetTrackballPoint(int x, int y) const
{
const float PointX = ((float)x - mTrackballCenter[0]) / mTrackballRadius;
const float PointY = ((float)y - mTrackballCenter[1]) / mTrackballRadius;
const float LengthSquared = PointX * PointX + PointY * PointY;
const float PointZ = LengthSquared <= 0.5f ? sqrtf(1.0f - LengthSquared) : 0.5f / sqrtf(LengthSquared);
return lcNormalize(lcVector3(PointX, PointY, PointZ));
}
void lcView::StartTracking(lcTrackButton TrackButton)
{
mTrackButton = TrackButton;
mTrackUpdated = false;
mMouseDownX = mMouseX;
mMouseDownY = mMouseY;
mCameraRotationAngle = 0.0f;
lcTool Tool = GetCurrentTool();
lcModel* ActiveModel = GetActiveModel();
switch (Tool)
{
case lcTool::Insert:
case lcTool::PointLight:
case lcTool::SpotLight:
case lcTool::DirectionalLight:
case lcTool::AreaLight:
case lcTool::Camera:
case lcTool::Select:
break;
case lcTool::Move:
case lcTool::Rotate:
ActiveModel->BeginMouseTool(Tool, this);
if (mTrackTool == lcTrackTool::RotateX || mTrackTool == lcTrackTool::RotateY || mTrackTool == lcTrackTool::RotateZ || mTrackTool == lcTrackTool::RotateCamera)
{
lcVector3 OverlayCenter;
lcMatrix33 RelativeRotation;
ActiveModel->GetMoveRotateTransform(OverlayCenter, RelativeRotation);
lcMatrix44 WorldMatrix = lcMatrix44(RelativeRotation, OverlayCenter);
if (ActiveModel != mModel)
WorldMatrix = lcMul(WorldMatrix, mActiveSubmodelTransform);
const lcVector3 ScreenCenter = ProjectPoint(WorldMatrix.GetTranslation());
mCameraRotationMouseAngle = atan2f((float)mMouseY - ScreenCenter[1], (float)mMouseX - ScreenCenter[0]);
mCameraRotationLastMouseAngle = mCameraRotationMouseAngle;
}
if (mTrackTool == lcTrackTool::RotateTrackBall)
{
lcVector3 OverlayCenter;
lcMatrix33 RelativeRotation;
ActiveModel->GetMoveRotateTransform(OverlayCenter, RelativeRotation);
lcMatrix44 WorldMatrix = lcMatrix44(RelativeRotation, OverlayCenter);
if (ActiveModel != mModel)
WorldMatrix = lcMul(WorldMatrix, mActiveSubmodelTransform);
mTrackballCenter = ProjectPoint(WorldMatrix.GetTranslation());
const lcMatrix44 CameraWorldMatrix = lcMatrix44AffineInverse(mCamera->mWorldView);
const lcVector3 CameraRight = lcNormalize(lcMul30(lcVector3(1.0f, 0.0f, 0.0f), CameraWorldMatrix));
const lcVector3 RadiusPoint = ProjectPoint(WorldMatrix.GetTranslation() + CameraRight * (2.0f * GetOverlayScale()));
mTrackballRadius = lcMax(1.0f, lcLength(RadiusPoint - mTrackballCenter));
mTrackballVector = GetTrackballPoint(mMouseX, mMouseY);
mTrackballPendingRotation = lcMatrix33Identity();
mTrackballSnapping = gMainWindow->GetAngleSnap() != 0.0f;
mTrackballHasPendingRotation = false;
}
break;
case lcTool::Eraser:
case lcTool::Paint:
case lcTool::ColorPicker:
break;
case lcTool::Pan:
StartPan(mMouseX, mMouseY);
ActiveModel->BeginMouseTool(Tool, this);
break;
case lcTool::Zoom:
case lcTool::RotateView:
case lcTool::Roll:
ActiveModel->BeginMouseTool(Tool, this);
break;
case lcTool::ZoomRegion:
break;
case lcTool::Count:
break;
}
UpdateCursor();
}
void lcView::StopTracking(bool Accept)
{
if (mTrackButton == lcTrackButton::None)
return;
lcTool Tool = GetCurrentTool();
lcModel* ActiveModel = GetActiveModel();
switch (Tool)
{
case lcTool::Insert:
case lcTool::PointLight:
case lcTool::SpotLight:
case lcTool::DirectionalLight:
case lcTool::AreaLight:
case lcTool::Camera:
break;
case lcTool::Select:
if (Accept && mMouseDownX != mMouseX && mMouseDownY != mMouseY)
{
std::vector<lcObject*> Objects = FindObjectsInBox(mMouseDownX, mMouseDownY, mMouseX, mMouseY);
if (mMouseModifiers & Qt::ControlModifier)
ActiveModel->AddToSelectionAction(Objects);
else if (mMouseModifiers & Qt::ShiftModifier)
ActiveModel->RemoveFromSelectionAction(Objects);
else
ActiveModel->SetSelectionAndFocusAction(Objects, nullptr, 0, gMainWindow->GetSelectionMode());
}
break;
case lcTool::Move:
case lcTool::Rotate:
ActiveModel->EndMouseTool(Tool, this, Accept);
break;
case lcTool::Eraser:
case lcTool::Paint:
case lcTool::ColorPicker:
break;
case lcTool::Zoom:
case lcTool::Pan:
case lcTool::RotateView:
case lcTool::Roll:
ActiveModel->EndMouseTool(Tool, this, Accept);
break;
case lcTool::ZoomRegion:
{
if (mMouseX == mMouseDownX || mMouseY == mMouseDownY)
break;
lcVector3 Points[6] =
{
lcVector3((mMouseDownX + lcMin(mMouseX, mWidth - 1)) / 2, (mMouseDownY + lcMin(mMouseY, mHeight - 1)) / 2, 0.0f),
lcVector3((mMouseDownX + lcMin(mMouseX, mWidth - 1)) / 2, (mMouseDownY + lcMin(mMouseY, mHeight - 1)) / 2, 1.0f),
lcVector3((float)mMouseX, (float)mMouseY, 0.0f),
lcVector3((float)mMouseX, (float)mMouseY, 1.0f),
lcVector3(mMouseDownX, mMouseDownY, 0.0f),
lcVector3(mMouseDownX, mMouseDownY, 1.0f)
};
UnprojectPoints(Points, 5);
lcVector3 Center = ActiveModel->GetSelectionOrModelCenter();
lcVector3 PlaneNormal(mCamera->mPosition - mCamera->mTargetPosition);
lcVector4 Plane(PlaneNormal, -lcDot(PlaneNormal, Center));
lcVector3 Target, Corners[2];
if (lcLineSegmentPlaneIntersection(&Target, Points[0], Points[1], Plane) && lcLineSegmentPlaneIntersection(&Corners[0], Points[2], Points[3], Plane) && lcLineSegmentPlaneIntersection(&Corners[1], Points[3], Points[4], Plane))
{
float AspectRatio = (float)mWidth / (float)mHeight;
ActiveModel->ZoomRegionToolClicked(this, AspectRatio, Points[0], Target, Corners);
}
}
break;
case lcTool::Count:
break;
}
mTrackButton = lcTrackButton::None;
UpdateTrackTool();
ActiveModel->UpdateAllViews();
}
void lcView::CancelTrackingOrClearSelectionAction()
{
if (mTrackButton != lcTrackButton::None)
StopTracking(false);
else
{
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel)
ActiveModel->ClearSelectionAction();
}
}
void lcView::OnButtonDown(lcTrackButton TrackButton)
{
lcModel* ActiveModel = GetActiveModel();
mToolClicked = false;
auto AddLight = [this, ActiveModel](lcLightType LightType)
{
ActiveModel->InsertLightToolClicked(GetCameraLightInsertPosition(), LightType);
if ((mMouseModifiers & Qt::ControlModifier) == 0)
gMainWindow->SetTool(lcTool::Select);
mToolClicked = true;
UpdateTrackTool();
};
switch (mTrackTool)
{
case lcTrackTool::None:
break;
case lcTrackTool::Insert:
{
UpdatePiecePreview();
if (!ActiveModel->InsertPieceToolClicked())
break;
if ((mMouseModifiers & Qt::ControlModifier) == 0)
gMainWindow->SetTool(lcTool::Select);
mToolClicked = true;
UpdateTrackTool();
}
break;
case lcTrackTool::PointLight:
AddLight(lcLightType::Point);
break;
case lcTrackTool::SpotLight:
AddLight(lcLightType::Spot);
break;
case lcTrackTool::DirectionalLight:
AddLight(lcLightType::Directional);
break;
case lcTrackTool::AreaLight:
AddLight(lcLightType::Area);
break;
case lcTrackTool::Camera:
{
ActiveModel->InsertCameraToolClicked(GetCameraLightInsertPosition());
if ((mMouseModifiers & Qt::ControlModifier) == 0)
gMainWindow->SetTool(lcTool::Select);
mToolClicked = true;
UpdateTrackTool();
}
break;
case lcTrackTool::Select:
{
lcObjectSection ObjectSection = FindObjectUnderPointer(false, false);
if (mMouseModifiers & Qt::ControlModifier)
ActiveModel->FocusOrDeselectObjectAction(ObjectSection.Object, ObjectSection.Section, gMainWindow->GetSelectionMode());
else if (mMouseModifiers & Qt::ShiftModifier)
{
if (ObjectSection.Object)
ActiveModel->RemoveFromSelectionAction({ ObjectSection.Object });
}
else
ActiveModel->SetSelectionAndFocusAction(std::vector<lcObject*>(), ObjectSection.Object, ObjectSection.Section, gMainWindow->GetSelectionMode());
StartTracking(TrackButton);
}
break;
case lcTrackTool::MoveX:
case lcTrackTool::MoveY:
case lcTrackTool::MoveZ:
case lcTrackTool::MoveXY:
case lcTrackTool::MoveXZ:
case lcTrackTool::MoveYZ:
case lcTrackTool::MoveXYZ:
if (ActiveModel->AnyObjectsSelected())
StartTracking(TrackButton);
break;
case lcTrackTool::RotateX:
case lcTrackTool::RotateY:
case lcTrackTool::RotateZ:
case lcTrackTool::RotateTrackBall:
case lcTrackTool::RotateCamera:
if (ActiveModel->CanRotateSelection())
StartTracking(TrackButton);
break;
case lcTrackTool::RotateTrainTrackRight:
case lcTrackTool::RotateTrainTrackLeft:
{
ActiveModel->RotateFocusedTrainTrack(mTrackTool == lcTrackTool::RotateTrainTrackRight ? 1 : -1);
mToolClicked = true;
UpdateTrackTool();
}
break;
case lcTrackTool::InsertTrainTrack:
{
ShowTrainTrackPopup();
mToolClicked = true;
UpdateTrackTool();
}
break;
case lcTrackTool::SelectTrainTrack:
{
lcObject* Focus = ActiveModel->GetFocusObject();
if (!Focus || !Focus->IsPiece())
break;
lcPiece* Piece = (lcPiece*)Focus;
const lcTrainTrackInfo* TrainTrackInfo = Piece->mPieceData->GetTrainTrackInfo();
if (!TrainTrackInfo)
break;
if (mTrackToolSection < LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST)
break;
lcObjectSection ObjectSection;
ObjectSection.Object = Focus;
ObjectSection.Section = mTrackToolSection;
ActiveModel->SetSelectionAndFocusAction(std::vector<lcObject*>(), ObjectSection.Object, ObjectSection.Section, gMainWindow->GetSelectionMode());
}
break;
case lcTrackTool::ScalePlus:
case lcTrackTool::ScaleMinus:
if (ActiveModel->AnyPiecesSelected())
StartTracking(TrackButton);
break;
case lcTrackTool::Eraser:
ActiveModel->EraserToolClicked(FindObjectUnderPointer(false, false).Object);
mToolClicked = true;
break;
case lcTrackTool::Paint:
ActiveModel->PaintToolClicked(FindObjectUnderPointer(true, false).Object);
StartTracking(TrackButton);
mToolClicked = true;
break;
case lcTrackTool::ColorPicker:
ActiveModel->ColorPickerToolClicked(FindObjectUnderPointer(true, false).Object);
mToolClicked = true;
break;
case lcTrackTool::Zoom:
case lcTrackTool::Pan:
case lcTrackTool::OrbitX:
case lcTrackTool::OrbitY:
case lcTrackTool::OrbitXY:
case lcTrackTool::Roll:
case lcTrackTool::ZoomRegion:
StartTracking(TrackButton);
break;
case lcTrackTool::Count:
break;
}
}
void lcView::OnLeftButtonDown()
{
if (mTrackButton != lcTrackButton::None)
{
StopTracking(false);
return;
}
if (mViewSphere->OnLeftButtonDown())
return;
lcTrackTool OverrideTool = GetOverrideTrackTool(Qt::LeftButton);
if (OverrideTool != lcTrackTool::None)
{
mTrackTool = OverrideTool;
UpdateCursor();
}
OnButtonDown(lcTrackButton::Left);
}
void lcView::OnLeftButtonUp()
{
StopTracking(mTrackButton == lcTrackButton::Left);
if (mViewSphere->OnLeftButtonUp())
return;
}
void lcView::OnLeftButtonDoubleClick()
{
if (mViewType != lcViewType::View)
{
ZoomExtents(lcGeometryBoundsMode::IncludeFallback, false);
return;
}
lcModel* ActiveModel = GetActiveModel();
if (mTrackTool == lcTrackTool::RotateTrainTrackRight || mTrackTool == lcTrackTool::RotateTrainTrackLeft)
{
ActiveModel->RotateFocusedTrainTrack(mTrackTool == lcTrackTool::RotateTrainTrackRight ? 1 : -1);
mToolClicked = true;
UpdateTrackTool();
return;
}
lcObjectSection ObjectSection = FindObjectUnderPointer(false, false);
if (mMouseModifiers & Qt::ControlModifier)
ActiveModel->FocusOrDeselectObjectAction(ObjectSection.Object, ObjectSection.Section, gMainWindow->GetSelectionMode());
else if (mMouseModifiers & Qt::ShiftModifier)
{
if (ObjectSection.Object)
ActiveModel->RemoveFromSelectionAction({ ObjectSection.Object });
}
else
ActiveModel->SetSelectionAndFocusAction(std::vector<lcObject*>(), ObjectSection.Object, ObjectSection.Section, gMainWindow->GetSelectionMode());
}
void lcView::OnMiddleButtonDown()
{
if (mTrackButton != lcTrackButton::None)
{
StopTracking(false);
return;
}
lcTrackTool OverrideTool = GetOverrideTrackTool(Qt::MiddleButton);
if (OverrideTool != lcTrackTool::None)
{
mTrackTool = OverrideTool;
UpdateCursor();
}
OnButtonDown(lcTrackButton::Middle);
}
void lcView::OnMiddleButtonUp()
{
StopTracking(mTrackButton == lcTrackButton::Middle);
}
void lcView::OnRightButtonDown()
{
if (mTrackButton != lcTrackButton::None)
{
StopTracking(false);
return;
}
lcTrackTool OverrideTool = GetOverrideTrackTool(Qt::RightButton);
if (OverrideTool != lcTrackTool::None)
{
mTrackTool = OverrideTool;
UpdateCursor();
}
OnButtonDown(lcTrackButton::Right);
}
void lcView::OnRightButtonUp()
{
bool ShowMenu = !mToolClicked && (mTrackButton == lcTrackButton::None || !mTrackUpdated);
if (mTrackButton != lcTrackButton::None)
StopTracking(mTrackButton == lcTrackButton::Right);
if (ShowMenu)
ShowContextMenu();
}
void lcView::OnBackButtonDown()
{
}
void lcView::OnBackButtonUp()
{
gMainWindow->HandleCommand(LC_VIEW_TIME_PREVIOUS);
}
void lcView::OnForwardButtonDown()
{
}
void lcView::OnForwardButtonUp()
{
gMainWindow->HandleCommand(LC_VIEW_TIME_NEXT);
}
void lcView::OnMouseMove()
{
lcModel* ActiveModel = GetActiveModel();
if (!ActiveModel)
return;
if (mTrackButton == lcTrackButton::None)
{
if (mViewSphere->OnMouseMove())
{
lcTrackTool NewTrackTool = mViewSphere->IsDragging() ? lcTrackTool::OrbitXY : lcTrackTool::None;
if (NewTrackTool != mTrackTool)
{
mTrackTool = NewTrackTool;
UpdateCursor();
}
return;
}
UpdateTrackTool();
if (mTrackTool == lcTrackTool::Insert)
{
UpdatePiecePreview();
ActiveModel->UpdateAllViews();
}
return;
}
mTrackUpdated = true;
const float MouseSensitivity = 0.5f / (21.0f - lcGetPreferences().mMouseSensitivity);
switch (mTrackTool)
{
case lcTrackTool::None:
case lcTrackTool::Insert:
case lcTrackTool::PointLight:
case lcTrackTool::SpotLight:
case lcTrackTool::DirectionalLight:
case lcTrackTool::AreaLight:
case lcTrackTool::Camera:
break;
case lcTrackTool::Select:
Redraw();
break;
case lcTrackTool::MoveX:
case lcTrackTool::MoveY:
case lcTrackTool::MoveZ:
case lcTrackTool::MoveXY:
case lcTrackTool::MoveXZ:
case lcTrackTool::MoveYZ:
case lcTrackTool::MoveXYZ:
case lcTrackTool::ScalePlus:
case lcTrackTool::ScaleMinus:
{
lcVector3 Points[4] =
{
lcVector3((float)mMouseX, (float)mMouseY, 0.0f),
lcVector3((float)mMouseX, (float)mMouseY, 1.0f),
lcVector3(mMouseDownX, mMouseDownY, 0.0f),
lcVector3(mMouseDownX, mMouseDownY, 1.0f)
};
UnprojectPoints(Points, 4);
const lcVector3& CurrentStart = Points[0];
const lcVector3& CurrentEnd = Points[1];
const lcVector3& MouseDownStart = Points[2];
const lcVector3& MouseDownEnd = Points[3];
lcVector3 OverlayCenter;
lcMatrix33 RelativeRotation;
ActiveModel->GetMoveRotateTransform(OverlayCenter, RelativeRotation);
lcMatrix44 WorldMatrix = lcMatrix44(RelativeRotation, OverlayCenter);
if (ActiveModel != mModel)
WorldMatrix = lcMul(WorldMatrix, mActiveSubmodelTransform);
const lcVector3 Center = WorldMatrix.GetTranslation();
if (mTrackTool == lcTrackTool::MoveX || mTrackTool == lcTrackTool::MoveY || mTrackTool == lcTrackTool::MoveZ)
{
lcVector3 Direction;
if (mTrackTool == lcTrackTool::MoveX)
Direction = lcVector3(1.0f, 0.0f, 0.0f);
else if (mTrackTool == lcTrackTool::MoveY)
Direction = lcVector3(0.0f, 1.0f, 0.0f);
else
Direction = lcVector3(0.0f, 0.0f, 1.0f);
Direction = lcMul30(Direction, WorldMatrix);
lcVector3 Intersection;
lcClosestPointsBetweenLines(Center, Center + Direction, CurrentStart, CurrentEnd, &Intersection, nullptr);
lcVector3 MoveStart;
lcClosestPointsBetweenLines(Center, Center + Direction, MouseDownStart, MouseDownEnd, &MoveStart, nullptr);
lcVector3 Distance = Intersection - MoveStart;
Distance = lcMul(Distance, lcMatrix33AffineInverse(lcMatrix33(WorldMatrix)));
ActiveModel->UpdateMoveTool(Distance, true, mTrackButton != lcTrackButton::Left);
}
else if (mTrackTool == lcTrackTool::MoveXY || mTrackTool == lcTrackTool::MoveXZ || mTrackTool == lcTrackTool::MoveYZ)
{
lcVector3 PlaneNormal;
if (mTrackTool == lcTrackTool::MoveXY)
PlaneNormal = lcVector3(0.0f, 0.0f, 1.0f);
else if (mTrackTool == lcTrackTool::MoveXZ)
PlaneNormal = lcVector3(0.0f, 1.0f, 0.0f);
else
PlaneNormal = lcVector3(1.0f, 0.0f, 0.0f);
PlaneNormal = lcMul30(PlaneNormal, WorldMatrix);
lcVector4 Plane(PlaneNormal, -lcDot(PlaneNormal, Center));
lcVector3 Intersection;
if (lcLineSegmentPlaneIntersection(&Intersection, CurrentStart, CurrentEnd, Plane))
{
lcVector3 MoveStart;
if (lcLineSegmentPlaneIntersection(&MoveStart, MouseDownStart, MouseDownEnd, Plane))
{
lcVector3 Distance = Intersection - MoveStart;
Distance = lcMul(Distance, lcMatrix33AffineInverse(lcMatrix33(WorldMatrix)));
ActiveModel->UpdateMoveTool(Distance, true, mTrackButton != lcTrackButton::Left);
}
}
}
else if (mTrackTool == lcTrackTool::MoveXYZ && mMouseDownPiece)
{
if (mMouseDownPiece->mPieceData->GetTrainTrackInfo())
ActiveModel->UpdateSelectedPiecesTrainTrackConnections();
auto [PieceInfoTransforms, IsConnection] = GetMouseInsertPieceInfo(true, false, mMouseDownPiece->mPieceData, mMouseDownPiece);
if (!PieceInfoTransforms.empty())
{
ActiveModel->UpdateFreeMoveTool(mMouseDownPiece, mMouseDownTransform, PieceInfoTransforms.back().Transform, IsConnection, mTrackButton != lcTrackButton::Left);
}
}
else if (mTrackTool == lcTrackTool::ScalePlus || mTrackTool == lcTrackTool::ScaleMinus)
{
lcVector3 Direction;
if (mTrackTool == lcTrackTool::ScalePlus)
Direction = lcVector3(1.0f, 0.0f, 0.0f);
else
Direction = lcVector3(-1.0f, 0.0f, 0.0f);
Direction = lcMul30(Direction, WorldMatrix);
lcVector3 Intersection;
lcClosestPointsBetweenLines(Center, Center + Direction, CurrentStart, CurrentEnd, &Intersection, nullptr);
lcObject* Focus = ActiveModel->GetFocusObject();
if (Focus && Focus->IsPiece())
{
lcPiece* Piece = (lcPiece*)Focus;
quint32 Section = Piece->GetFocusSection();
if (Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST && Section <= LC_PIECE_SECTION_CONTROL_POINT_LAST)
{
const float ScaleMax = 200.0f;
const float OverlayScale = GetOverlayScale();
const float ScaleStart = 2.0f * OverlayScale;
lcVector3 Position = Piece->GetSectionPosition(Section);
lcVector3 Start = Position + Direction * ScaleStart;
float Distance = lcLength(Intersection - Start);
if (lcDot(Direction, Intersection - Start) < 0.0f)
Distance = 0.0f;
float Scale = lcClamp(Distance, 0.1f, ScaleMax);
ActiveModel->UpdateScaleTool(Scale);
}
}
}
else
{
lcVector3 PlaneNormal = lcNormalize(mCamera->mTargetPosition - mCamera->mPosition);
lcVector4 Plane(PlaneNormal, -lcDot(PlaneNormal, Center));
lcVector3 Intersection;
if (lcLineSegmentPlaneIntersection(&Intersection, CurrentStart, CurrentEnd, Plane))
{
lcVector3 MoveStart;
if (lcLineSegmentPlaneIntersection(&MoveStart, MouseDownStart, MouseDownEnd, Plane))
{
lcVector3 Distance = Intersection - MoveStart;
ActiveModel->UpdateMoveTool(Distance, true, mTrackButton != lcTrackButton::Left);
}
}
}
}
break;
case lcTrackTool::RotateX:
case lcTrackTool::RotateY:
case lcTrackTool::RotateZ:
{
lcVector3 OverlayCenter;
lcMatrix33 RelativeRotation;
ActiveModel->GetMoveRotateTransform(OverlayCenter, RelativeRotation);
lcMatrix44 WorldMatrix = lcMatrix44(RelativeRotation, OverlayCenter);
if (ActiveModel != mModel)
WorldMatrix = lcMul(WorldMatrix, mActiveSubmodelTransform);
const lcVector3 ScreenCenter = ProjectPoint(WorldMatrix.GetTranslation());
const float MouseAngle = atan2f((float)mMouseY - ScreenCenter[1], (float)mMouseX - ScreenCenter[0]);
float Angle = mCameraRotationLastMouseAngle - MouseAngle;
if (Angle > LC_PI)
Angle -= LC_2PI;
else if (Angle < -LC_PI)
Angle += LC_2PI;
mCameraRotationAngle += Angle;
mCameraRotationLastMouseAngle = MouseAngle;
lcVector3 Axis;
switch (mTrackTool)
{
case lcTrackTool::RotateX:
Axis = lcVector3(1.0f, 0.0f, 0.0f);
break;
case lcTrackTool::RotateY:
Axis = lcVector3(0.0f, 1.0f, 0.0f);
break;
case lcTrackTool::RotateZ:
Axis = lcVector3(0.0f, 0.0f, 1.0f);
break;
default:
Axis = lcVector3(0.0f, 0.0f, 1.0f);
break;
}
// The rings rotate with the current gizmo frame, so derive the displayed
// axis from the current transform before determining its screen direction.
lcVector3 DisplayAxis = lcNormalize(lcMul30(Axis, WorldMatrix));
if (WorldMatrix.Determinant() < 0.0f)
DisplayAxis = -DisplayAxis;
const lcVector3 CameraAxis = lcNormalize(mCamera->mTargetPosition - mCamera->mPosition);
const float AxisDirection = lcDot(DisplayAxis, CameraAxis) < 0.0f ? -1.0f : 1.0f;
ActiveModel->UpdateRotateTool(Axis, AxisDirection * mCameraRotationAngle * LC_RTOD, mTrackButton != lcTrackButton::Left, true);
}
break;
case lcTrackTool::RotateTrackBall:
{
const lcVector3 CurrentVector = GetTrackballPoint(mMouseX, mMouseY);
const lcVector3 LocalAxis = lcCross(mTrackballVector, CurrentVector);
const float AxisLength = lcLength(LocalAxis);
if (AxisLength > 0.0001f)
{
const float Angle = atan2f(AxisLength, lcClamp(lcDot(mTrackballVector, CurrentVector), -1.0f, 1.0f));
const lcMatrix44 CameraWorldMatrix = lcMatrix44AffineInverse(mCamera->mWorldView);
lcVector3 RotationAxis = lcNormalize(lcMul30(LocalAxis, CameraWorldMatrix));
if (ActiveModel != mModel)
{
const lcMatrix44 ActiveSubmodelInverse = lcMatrix44AffineInverse(mActiveSubmodelTransform);
RotationAxis = lcNormalize(lcMul30(RotationAxis, ActiveSubmodelInverse));
if (ActiveSubmodelInverse.Determinant() < 0.0f)
RotationAxis = -RotationAxis;
}
const lcMatrix33 IncrementalRotation = lcMatrix33FromAxisAngle(RotationAxis, Angle);
const bool Snapping = gMainWindow->GetAngleSnap() != 0.0f;
if (Snapping != mTrackballSnapping)
{
if (!Snapping && mTrackballHasPendingRotation)
ActiveModel->UpdateRotateTool(mTrackballPendingRotation, mTrackButton != lcTrackButton::Left, false);
mTrackballPendingRotation = lcMatrix33Identity();
mTrackballSnapping = Snapping;
mTrackballHasPendingRotation = false;
}
if (!Snapping)
{
ActiveModel->UpdateRotateTool(IncrementalRotation, mTrackButton != lcTrackButton::Left, false);
}
else
{
mTrackballPendingRotation = lcMul(mTrackballPendingRotation, IncrementalRotation);
mTrackballHasPendingRotation = true;
const lcVector4 AxisAngle = lcMatrix44ToAxisAngle(lcMatrix44(mTrackballPendingRotation, lcVector3(0.0f, 0.0f, 0.0f)));
const float SnappedAngle = ActiveModel->SnapRotation(lcVector3(AxisAngle[3] * LC_RTOD, 0.0f, 0.0f))[0];
if (SnappedAngle != 0.0f)
{
const lcMatrix33 SnappedRotation = lcMatrix33FromAxisAngle(lcVector3(AxisAngle), SnappedAngle * LC_DTOR);
ActiveModel->UpdateRotateTool(SnappedRotation, mTrackButton != lcTrackButton::Left, false);
mTrackballPendingRotation = lcMul(lcMatrix33AffineInverse(SnappedRotation), mTrackballPendingRotation);
mTrackballHasPendingRotation = lcMatrix44ToAxisAngle(lcMatrix44(mTrackballPendingRotation, lcVector3(0.0f, 0.0f, 0.0f)))[3] > 0.000001f;
}
}
}
mTrackballVector = CurrentVector;
}
break;
case lcTrackTool::RotateCamera:
{
lcVector3 OverlayCenter;
lcMatrix33 RelativeRotation;
ActiveModel->GetMoveRotateTransform(OverlayCenter, RelativeRotation);
lcMatrix44 WorldMatrix = lcMatrix44(RelativeRotation, OverlayCenter);
if (ActiveModel != mModel)
WorldMatrix = lcMul(WorldMatrix, mActiveSubmodelTransform);
const lcVector3 ScreenCenter = ProjectPoint(WorldMatrix.GetTranslation());
const float MouseAngle = atan2f((float)mMouseY - ScreenCenter[1], (float)mMouseX - ScreenCenter[0]);
// Unwrap each clockwise-positive pointer delta so full turns do not flip
// when the pointer crosses the opposite side of the ring.
float Angle = mCameraRotationLastMouseAngle - MouseAngle;
if (Angle > LC_PI)
Angle -= LC_2PI;
else if (Angle < -LC_PI)
Angle += LC_2PI;
mCameraRotationAngle += Angle;
mCameraRotationLastMouseAngle = MouseAngle;
lcVector3 CameraAxis = lcNormalize(mCamera->mTargetPosition - mCamera->mPosition);
if (ActiveModel != mModel)
{
const lcMatrix44 ActiveSubmodelInverse = lcMatrix44AffineInverse(mActiveSubmodelTransform);
CameraAxis = lcNormalize(lcMul30(CameraAxis, ActiveSubmodelInverse));
if (ActiveSubmodelInverse.Determinant() < 0.0f)
CameraAxis = -CameraAxis;
}
ActiveModel->UpdateRotateTool(CameraAxis, mCameraRotationAngle * LC_RTOD, mTrackButton != lcTrackButton::Left, false);
}
break;
case lcTrackTool::RotateTrainTrackRight:
case lcTrackTool::RotateTrainTrackLeft:
case lcTrackTool::InsertTrainTrack:
case lcTrackTool::SelectTrainTrack:
case lcTrackTool::Eraser:
break;
case lcTrackTool::Paint:
ActiveModel->PaintToolClicked(FindObjectUnderPointer(true, false).Object);
break;
case lcTrackTool::ColorPicker:
break;
case lcTrackTool::Zoom:
ActiveModel->UpdateZoomTool(mCamera, 2.0f * MouseSensitivity * (mMouseY - mMouseDownY));
break;
case lcTrackTool::Pan:
UpdatePan(mMouseX, mMouseY);
break;
case lcTrackTool::OrbitX:
ActiveModel->UpdateOrbitTool(mCamera, 0.1f * MouseSensitivity * (mMouseX - mMouseDownX), 0.0f);
break;
case lcTrackTool::OrbitY:
ActiveModel->UpdateOrbitTool(mCamera, 0.0f, 0.1f * MouseSensitivity * (mMouseY - mMouseDownY));
break;
case lcTrackTool::OrbitXY:
ActiveModel->UpdateOrbitTool(mCamera, 0.1f * MouseSensitivity * (mMouseX - mMouseDownX), 0.1f * MouseSensitivity * (mMouseY - mMouseDownY));
break;
case lcTrackTool::Roll:
ActiveModel->UpdateRollTool(mCamera, 2.0f * MouseSensitivity * (mMouseX - mMouseDownX) * LC_DTOR);
break;
case lcTrackTool::ZoomRegion:
Redraw();
break;
case lcTrackTool::Count:
break;
}
}
void lcView::OnMouseLeave()
{
if (mTrackTool == lcTrackTool::Insert)
{
mTrackTool = lcTrackTool::None;
UpdateCursor();
lcModel* ActiveModel = GetActiveModel();
if (ActiveModel)
ActiveModel->SetPreviewInsertPieceInfo(std::vector<lcInsertPieceInfo>());
}
}
void lcView::OnMouseWheel(float Direction)
{
float Scale = 10.0f;
if (mMouseModifiers & Qt::ControlModifier)
Scale = 100.0f;
else if (mMouseModifiers & Qt::ShiftModifier)
Scale = 1.0f;
mModel->Zoom(mCamera, static_cast<int>(Direction * Scale));
}