Files
leocad/common/piece.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

1605 lines
45 KiB
C++

#include "lc_global.h"
#include "lc_mesh.h"
#include "lc_colors.h"
#include <string.h>
#include <stdio.h>
#include <math.h>
#include "lc_piecedata.h"
#include "piece.h"
#include "group.h"
#include "lc_file.h"
#include "lc_application.h"
#include "lc_library.h"
#include "lc_context.h"
#include "lc_scene.h"
#include "lc_qutils.h"
#include "lc_synth.h"
#include "lc_traintrack.h"
#include "lc_string.h"
#include "lc_model.h"
constexpr float LC_PIECE_CONTROL_POINT_SIZE = 10.0f;
lcPiece::lcPiece()
: lcObject(lcObjectType::Piece)
{
}
lcPiece::lcPiece(lcPieceData* PieceData)
: lcPiece(lcPieceDataRef(PieceData))
{
}
lcPiece::lcPiece(lcPieceDataRef PieceDataRef)
: lcObject(lcObjectType::Piece)
{
SetPieceData(std::move(PieceDataRef), QString(), true, true);
mColorIndex = gDefaultColor;
mColorCode = 16;
mPivotMatrix = lcMatrix44Identity();
}
lcPiece::lcPiece(const lcPiece& Other)
: lcObject(lcObjectType::Piece)
{
SetPieceData(Other.mPieceData, Other.mID, true, false);
mHidden = Other.mHidden;
mSelected = Other.mSelected;
mColorIndex = Other.mColorIndex;
mColorCode = Other.mColorCode;
mStepShow = Other.mStepShow;
mStepHide = Other.mStepHide;
mGroup = Other.mGroup;
mPivotMatrix = Other.mPivotMatrix;
mPivotPointValid = Other.mPivotPointValid;
mPosition = Other.mPosition;
mRotation = Other.mRotation;
mControlPoints = Other.mControlPoints;
UpdateMesh();
}
lcPiece::~lcPiece()
{
lcGetPiecesLibrary()->CancelSynthMesh(this);
if (mPieceData)
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
mPieceData.Reset();
Library->NotifyAssetStatusChanged();
}
delete mMesh;
}
void lcPiece::SetPieceData(lcPieceDataRef PieceDataRef, const QString& ID, bool Wait, bool UpdateSynthInfo)
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
const bool DataChanged = mPieceData != PieceDataRef;
if (DataChanged || UpdateSynthInfo)
Library->CancelSynthMesh(this);
if (DataChanged)
{
delete mMesh;
mMesh = nullptr;
}
lcPieceDataRef PreviousData(std::move(mPieceData));
mPieceData = std::move(PieceDataRef);
if (mPieceData)
{
if (Wait)
Library->WaitForPieceData(mPieceData);
else
Library->RequestPieceDataLoad(mPieceData.Get(), lcLoadPriority::Normal);
}
if (!ID.isEmpty())
mID = ID;
else if (mPieceData)
mID = mPieceData->mFileName;
else
mID.clear();
if (UpdateSynthInfo)
{
mControlPoints.clear();
const lcSynthInfo* SynthInfo = mPieceData ? mPieceData->GetSynthInfo() : nullptr;
if (SynthInfo)
SynthInfo->GetDefaultControlPoints(mControlPoints);
UpdateMesh();
}
else if (PreviousData && DataChanged)
UpdateMesh();
PreviousData.Reset();
Library->NotifyAssetStatusChanged();
}
bool lcPiece::SetPieceId(lcPieceData* PieceData)
{
if (mPieceData == PieceData)
return false;
SetPieceData(lcPieceDataRef(PieceData), QString(), true, true);
return true;
}
void lcPiece::UpdateID()
{
mID = mPieceData->mFileName;
}
void lcPiece::SaveLDraw(QTextStream& Stream) const
{
const QLatin1String LineEnding("\r\n");
if (mStepHide != LC_STEP_MAX)
Stream << QLatin1String("0 !LEOCAD PIECE STEP_HIDE ") << mStepHide << LineEnding;
if (IsHidden())
Stream << QLatin1String("0 !LEOCAD PIECE HIDDEN") << LineEnding;
if (mPivotPointValid)
{
const float* PivotMatrix = mPivotMatrix.GetFloats();
const float PivotNumbers[12] = { PivotMatrix[12], -PivotMatrix[14], PivotMatrix[13], PivotMatrix[0], -PivotMatrix[8], PivotMatrix[4], -PivotMatrix[2], PivotMatrix[10], -PivotMatrix[6], PivotMatrix[1], -PivotMatrix[9], PivotMatrix[5] };
Stream << QLatin1String("0 !LEOCAD PIECE PIVOT ");
for (int NumberIdx = 0; NumberIdx < 12; NumberIdx++)
Stream << ' ' << lcFormatValue(PivotNumbers[NumberIdx], NumberIdx < 3 ? 4 : 6);
Stream << LineEnding;
}
mPosition.Save(Stream, "PIECE", "POSITION", false);
mRotation.Save(Stream, "PIECE", "ROTATION", false);
Stream << "1 " << mColorCode << ' ';
const float* Matrix = mModelWorld.GetFloats();
const float Numbers[12] = { Matrix[12], -Matrix[14], Matrix[13], Matrix[0], -Matrix[8], Matrix[4], -Matrix[2], Matrix[10], -Matrix[6], Matrix[1], -Matrix[9], Matrix[5] };
for (int NumberIdx = 0; NumberIdx < 12; NumberIdx++)
Stream << lcFormatValue(Numbers[NumberIdx], NumberIdx < 3 ? 4 : 6) << ' ';
Stream << mID << LineEnding;
}
bool lcPiece::ParseLDrawLine(QTextStream& Stream)
{
while (!Stream.atEnd())
{
QString Token;
Stream >> Token;
if (Token == QLatin1String("STEP_HIDE"))
Stream >> mStepHide;
else if (Token == QLatin1String("HIDDEN"))
SetHidden(true);
else if (Token == QLatin1String("PIVOT"))
{
float PivotNumbers[12];
for (int TokenIdx = 0; TokenIdx < 12; TokenIdx++)
Stream >> PivotNumbers[TokenIdx];
const lcMatrix44 PivotMatrix(lcVector4( PivotNumbers[3], PivotNumbers[9], -PivotNumbers[6], 0.0f), lcVector4(PivotNumbers[5], PivotNumbers[11], -PivotNumbers[8], 0.0f),
lcVector4(-PivotNumbers[4], -PivotNumbers[10], PivotNumbers[7], 0.0f), lcVector4(PivotNumbers[0], PivotNumbers[2], -PivotNumbers[1], 1.0f));
mPivotMatrix = PivotMatrix;
mPivotPointValid = true;
}
else if (mPosition.Load(Stream, Token, "POSITION"))
continue;
else if (mRotation.Load(Stream, Token, "ROTATION"))
continue;
}
return false;
}
bool lcPiece::FileLoad(lcFile& file, Project* Project)
{
quint8 version, ch;
version = file.ReadU8();
if (version > 12) // LeoCAD 0.80
return false;
const float PositionScale = (version < 12) ? 25.0f : 1.0f;
if (version > 8)
{
if (file.ReadU8() != 1)
return false;
quint16 time;
float param[4];
quint8 type;
quint32 n;
file.ReadU32(&n, 1);
while (n--)
{
file.ReadU16(&time, 1);
file.ReadFloats(param, 4);
file.ReadU8(&type, 1);
if (type == 0)
mPosition.ChangeKey(lcVector3(param[0], param[1], param[2]) * PositionScale, time, true);
else if (type == 1)
mRotation.ChangeKey(lcMatrix33FromAxisAngle(lcVector3(param[0], param[1], param[2]), param[3] * LC_DTOR), time, true);
}
file.ReadU32(&n, 1);
while (n--)
{
file.ReadU16(&time, 1);
file.ReadFloats(param, 4);
file.ReadU8(&type, 1);
}
}
if (version < 9)
{
quint16 time;
quint8 type;
if (version > 5)
{
quint32 keys;
float param[4];
file.ReadU32(&keys, 1);
while (keys--)
{
file.ReadFloats(param, 4);
file.ReadU16(&time, 1);
file.ReadU8(&type, 1);
if (type == 0)
mPosition.ChangeKey(lcVector3(param[0], param[1], param[2]) * PositionScale, time, true);
else if (type == 1)
mRotation.ChangeKey(lcMatrix33FromAxisAngle(lcVector3(param[0], param[1], param[2]), param[3] * LC_DTOR), time, true);
}
file.ReadU32(&keys, 1);
while (keys--)
{
file.ReadFloats(param, 4);
file.ReadU16(&time, 1);
file.ReadU8(&type, 1);
}
}
else
{
if (version > 2)
{
file.ReadU8(&ch, 1);
while (ch--)
{
lcMatrix44 ModelWorld;
if (version > 3)
{
file.ReadFloats(ModelWorld.GetFloats(), 16);
}
else
{
lcVector3 Translation;
float Rotation[3];
file.ReadFloats(Translation.GetFloats(), 3);
file.ReadFloats(Rotation, 3);
ModelWorld = lcMatrix44Translation(Translation);
ModelWorld = lcMul(lcMatrix44RotationZ(Rotation[2] * LC_DTOR), lcMul(lcMatrix44RotationY(Rotation[1] * LC_DTOR), lcMul(lcMatrix44RotationX(Rotation[0] * LC_DTOR), ModelWorld)));
}
quint8 b;
file.ReadU8(&b, 1);
time = b;
mPosition.ChangeKey(ModelWorld.GetTranslation() * PositionScale, 1, true);
mRotation.ChangeKey(lcMatrix33(ModelWorld), time, true);
qint32 bl;
file.ReadS32(&bl, 1);
}
}
else
{
lcVector3 Translation;
float Rotation[3];
file.ReadFloats(Translation.GetFloats(), 3);
file.ReadFloats(Rotation, 3);
lcMatrix44 ModelWorld = lcMatrix44Translation(Translation);
ModelWorld = lcMul(lcMatrix44RotationZ(Rotation[2] * LC_DTOR), lcMul(lcMatrix44RotationY(Rotation[1] * LC_DTOR), lcMul(lcMatrix44RotationX(Rotation[0] * LC_DTOR), ModelWorld)));
mPosition.ChangeKey(lcVector3(ModelWorld.r[3][0], ModelWorld.r[3][1], ModelWorld.r[3][2]) * PositionScale, 1, true);
mRotation.ChangeKey(lcMatrix33(ModelWorld), 1, true);
}
}
}
// Common to all versions.
char name[LC_PIECE_NAME_LEN];
if (version < 10)
{
memset(name, 0, LC_PIECE_NAME_LEN);
file.ReadBuffer(name, 9);
}
else
file.ReadBuffer(name, LC_PIECE_NAME_LEN);
lcstrcat(name, ".dat");
SetPieceData(lcGetPiecesLibrary()->AcquirePieceData(name, Project, true, false), QString(), true, true);
// 11 (0.77)
if (version < 11)
{
quint8 Color;
file.ReadU8(&Color, 1);
if (version < 5)
mColorCode = lcGetColorCodeFromOriginalColor(Color);
else
mColorCode = lcGetColorCodeFromExtendedColor(Color);
}
else
file.ReadU32(&mColorCode, 1);
mColorIndex = lcGetColorIndex(mColorCode);
quint8 Step;
file.ReadU8(&Step, 1);
mStepShow = Step;
if (version > 1)
{
file.ReadU8(&Step, 1);
mStepHide = Step == 255 ? LC_STEP_MAX : Step;
}
else
mStepHide = LC_STEP_MAX;
if (version > 5)
{
file.ReadU16(); // m_nFrameShow
file.ReadU16(); // m_nFrameHide
if (version > 7)
{
quint8 Hidden;
file.ReadU8(&Hidden, 1);
if (Hidden & 1)
mHidden = true;
file.ReadU8(&ch, 1);
file.Seek(ch, SEEK_CUR);
}
else
{
qint32 hide;
file.ReadS32(&hide, 1);
if (hide != 0)
mHidden = true;
file.Seek(81, SEEK_CUR);
}
// 7 (0.64)
qint32 i = -1;
if (version > 6)
file.ReadS32(&i, 1);
mGroup = (lcGroup*)(quintptr)i;
}
else
{
file.ReadU8(&ch, 1);
if (ch == 0)
mGroup = (lcGroup*)-1;
else
mGroup = (lcGroup*)(quintptr)ch;
file.ReadU8(&ch, 1);
if (ch & 0x01)
mHidden = true;
}
return true;
}
void lcPiece::Initialize(const lcMatrix44& WorldMatrix, lcStep Step)
{
mStepShow = Step;
mPosition.SetValue(WorldMatrix.GetTranslation());
mRotation.SetValue(lcMatrix33(WorldMatrix));
UpdatePosition(Step);
}
void lcPiece::InsertTime(lcStep Start, lcStep Time)
{
if (mStepShow >= Start)
{
if (mStepShow < LC_STEP_MAX - Time)
mStepShow += Time;
else
mStepShow = LC_STEP_MAX;
}
if (mStepHide >= Start)
{
if (mStepHide < LC_STEP_MAX - Time)
mStepHide += Time;
else
mStepHide = LC_STEP_MAX;
}
if (mStepShow >= mStepHide)
{
if (mStepShow != LC_STEP_MAX)
mStepHide = mStepShow + 1;
else
{
mStepShow = LC_STEP_MAX - 1;
mStepHide = LC_STEP_MAX;
}
}
mPosition.InsertTime(Start, Time);
mRotation.InsertTime(Start, Time);
}
void lcPiece::RemoveTime(lcStep Start, lcStep Time)
{
if (mStepShow >= Start)
{
if (mStepShow > Time)
mStepShow -= Time;
else
mStepShow = 1;
}
if (mStepHide != LC_STEP_MAX)
{
if (mStepHide > Time)
mStepHide -= Time;
else
mStepHide = 1;
}
if (mStepShow >= mStepHide)
{
if (mStepShow != LC_STEP_MAX)
mStepHide = mStepShow + 1;
else
{
mStepShow = LC_STEP_MAX - 1;
mStepHide = LC_STEP_MAX;
}
}
mPosition.RemoveTime(Start, Time);
mRotation.RemoveTime(Start, Time);
}
void lcPiece::RayTest(lcObjectRayTest& ObjectRayTest) const
{
const lcMatrix44 InverseWorldMatrix = lcMatrix44AffineInverse(mModelWorld);
const lcVector3 Start = lcMul31(ObjectRayTest.Start, InverseWorldMatrix);
const lcVector3 End = lcMul31(ObjectRayTest.End, InverseWorldMatrix);
if (MinIntersectDist(Start, End, ObjectRayTest.Distance, ObjectRayTest.PieceRayTest))
{
ObjectRayTest.ObjectSection.Object = const_cast<lcPiece*>(this);
ObjectRayTest.ObjectSection.Section = LC_PIECE_SECTION_POSITION;
ObjectRayTest.PieceRayTest.Transform = lcMul(ObjectRayTest.PieceRayTest.Transform, mModelWorld);
}
if (mPieceData->GetSynthInfo() && AreControlPointsVisible())
{
const lcVector3 Min(-LC_PIECE_CONTROL_POINT_SIZE, -LC_PIECE_CONTROL_POINT_SIZE, -LC_PIECE_CONTROL_POINT_SIZE);
const lcVector3 Max(LC_PIECE_CONTROL_POINT_SIZE, LC_PIECE_CONTROL_POINT_SIZE, LC_PIECE_CONTROL_POINT_SIZE);
for (quint32 ControlPointIndex = 0; ControlPointIndex < mControlPoints.size(); ControlPointIndex++)
{
const lcMatrix44 InverseTransform = lcMatrix44AffineInverse(mControlPoints[ControlPointIndex].Transform);
const lcVector3 PointStart = lcMul31(Start, InverseTransform);
const lcVector3 PointEnd = lcMul31(End, InverseTransform);
float Distance;
lcVector3 Plane;
if (lcBoundingBoxRayIntersectDistance(Min, Max, PointStart, PointEnd, &Distance, nullptr, &Plane))
{
ObjectRayTest.ObjectSection.Object = const_cast<lcPiece*>(this);
ObjectRayTest.ObjectSection.Section = LC_PIECE_SECTION_CONTROL_POINT_FIRST + ControlPointIndex;
ObjectRayTest.Distance = Distance;
ObjectRayTest.PieceRayTest.Plane = Plane;
}
}
}
}
bool lcPiece::MinIntersectDist(const lcVector3& Start, const lcVector3& End, float& MinDistance, lcPieceRayTest& PieceRayTest) const
{
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceData->IsLoading())
return mPieceData->MinIntersectDist(Start, End, MinDistance, PieceRayTest);
if (!Mesh->MinIntersectDist(Start, End, MinDistance, PieceRayTest.Plane))
return false;
PieceRayTest.PieceData = mPieceData.Get();
PieceRayTest.Transform = lcMatrix44Identity();
return true;
}
void lcPiece::BoxTest(lcObjectBoxTest& ObjectBoxTest) const
{
if (IntersectsPlanes(mModelWorld, ObjectBoxTest.Planes))
ObjectBoxTest.Objects.emplace_back(const_cast<lcPiece*>(this));
}
bool lcPiece::IntersectsPlanes(const lcMatrix44& WorldMatrix, const lcVector4 Planes[6]) const
{
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceData->IsLoading())
return mPieceData->BoxTest(WorldMatrix, Planes);
const lcMatrix44 InverseWorldMatrix = lcMatrix44AffineInverse(WorldMatrix);
lcVector4 LocalPlanes[6];
for (int PlaneIdx = 0; PlaneIdx < 6; PlaneIdx++)
{
const lcVector3 PlaneNormal = lcMul30(Planes[PlaneIdx], InverseWorldMatrix);
LocalPlanes[PlaneIdx] = lcVector4(PlaneNormal, Planes[PlaneIdx][3] - lcDot3(InverseWorldMatrix[3], PlaneNormal));
}
lcVector3 Corners[8];
lcGetBoxCorners(Mesh->mBoundingBox, Corners);
int OutcodesOR = 0;
int OutcodesAND = 0x3f;
for (const lcVector3& Corner : Corners)
{
int Outcode = 0;
for (int PlaneIdx = 0; PlaneIdx < 6; PlaneIdx++)
if (lcDot3(Corner, LocalPlanes[PlaneIdx]) + LocalPlanes[PlaneIdx][3] > 0)
Outcode |= 1 << PlaneIdx;
OutcodesOR |= Outcode;
OutcodesAND &= Outcode;
}
if (OutcodesAND != 0)
return false;
if (OutcodesOR == 0)
return true;
return Mesh->IntersectsPlanes(LocalPlanes);
}
void lcPiece::DrawInterface(lcContext* Context, const lcScene& Scene) const
{
const lcPreferences& Preferences = lcGetPreferences();
const float LineWidth = Preferences.mLineWidth;
Context->SetLineWidth(2.0f * LineWidth);
const lcBoundingBox& BoundingBox = GetBoundingBox();
const lcVector3& Min = BoundingBox.Min;
const lcVector3& Max = BoundingBox.Max;
lcVector3 Edge((Max - Min) * 0.33f);
const float LineVerts[48][3] =
{
{ Max[0], Max[1], Max[2] }, { Max[0] - Edge[0], Max[1], Max[2] },
{ Max[0], Max[1], Max[2] }, { Max[0], Max[1] - Edge[1], Max[2] },
{ Max[0], Max[1], Max[2] }, { Max[0], Max[1], Max[2] - Edge[2] },
{ Min[0], Max[1], Max[2] }, { Min[0] + Edge[0], Max[1], Max[2] },
{ Min[0], Max[1], Max[2] }, { Min[0], Max[1] - Edge[1], Max[2] },
{ Min[0], Max[1], Max[2] }, { Min[0], Max[1], Max[2] - Edge[2] },
{ Max[0], Min[1], Max[2] }, { Max[0] - Edge[0], Min[1], Max[2] },
{ Max[0], Min[1], Max[2] }, { Max[0], Min[1] + Edge[1], Max[2] },
{ Max[0], Min[1], Max[2] }, { Max[0], Min[1], Max[2] - Edge[2] },
{ Min[0], Min[1], Max[2] }, { Min[0] + Edge[0], Min[1], Max[2] },
{ Min[0], Min[1], Max[2] }, { Min[0], Min[1] + Edge[1], Max[2] },
{ Min[0], Min[1], Max[2] }, { Min[0], Min[1], Max[2] - Edge[2] },
{ Max[0], Max[1], Min[2] }, { Max[0] - Edge[0], Max[1], Min[2] },
{ Max[0], Max[1], Min[2] }, { Max[0], Max[1] - Edge[1], Min[2] },
{ Max[0], Max[1], Min[2] }, { Max[0], Max[1], Min[2] + Edge[2] },
{ Min[0], Max[1], Min[2] }, { Min[0] + Edge[0], Max[1], Min[2] },
{ Min[0], Max[1], Min[2] }, { Min[0], Max[1] - Edge[1], Min[2] },
{ Min[0], Max[1], Min[2] }, { Min[0], Max[1], Min[2] + Edge[2] },
{ Max[0], Min[1], Min[2] }, { Max[0] - Edge[0], Min[1], Min[2] },
{ Max[0], Min[1], Min[2] }, { Max[0], Min[1] + Edge[1], Min[2] },
{ Max[0], Min[1], Min[2] }, { Max[0], Min[1], Min[2] + Edge[2] },
{ Min[0], Min[1], Min[2] }, { Min[0] + Edge[0], Min[1], Min[2] },
{ Min[0], Min[1], Min[2] }, { Min[0], Min[1] + Edge[1], Min[2] },
{ Min[0], Min[1], Min[2] }, { Min[0], Min[1], Min[2] + Edge[2] },
};
const lcMatrix44 WorldMatrix = Scene.ApplyActiveSubmodelTransform(mModelWorld);
Context->SetMaterial(lcMaterialType::UnlitColor);
Context->SetWorldMatrix(WorldMatrix);
if (IsFocused(LC_PIECE_SECTION_POSITION))
{
const lcVector4 FocusedColor = lcVector4FromColor(Preferences.mObjectFocusedColor);
Context->SetColor(FocusedColor);
}
else
{
const lcVector4 SelectedColor = lcVector4FromColor(Preferences.mObjectSelectedColor);
Context->SetColor(SelectedColor);
}
if (Scene.GetHighlightRenderMode() == lcHighlightRenderMode::Tint)
{
Context->SetVertexBufferPointer(LineVerts);
Context->SetVertexFormatPosition(3);
Context->DrawPrimitives(GL_LINES, 0, 48);
}
if (IsPivotPointVisible())
{
constexpr float Size = 5.0f;
constexpr float Verts[8 * 3] =
{
-Size, -Size, -Size, -Size, Size, -Size, Size, Size, -Size, Size, -Size, -Size,
-Size, -Size, Size, -Size, Size, Size, Size, Size, Size, Size, -Size, Size
};
const GLushort Indices[24] =
{
0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7
};
Context->SetWorldMatrix(lcMul(mPivotMatrix, WorldMatrix));
Context->SetVertexBufferPointer(Verts);
Context->SetVertexFormatPosition(3);
Context->SetIndexBufferPointer(Indices);
Context->DrawIndexedPrimitives(GL_LINES, 24, GL_UNSIGNED_SHORT, 0);
}
if (mPieceData->GetSynthInfo())
DrawSynthInterface(Context, WorldMatrix);
}
void lcPiece::DrawSynthInterface(lcContext* Context, const lcMatrix44& WorldMatrix) const
{
if (mControlPoints.empty() || !AreControlPointsVisible())
return;
float Verts[8 * 3];
float* CurVert = Verts;
lcVector3 CubeMin(-LC_PIECE_CONTROL_POINT_SIZE, -LC_PIECE_CONTROL_POINT_SIZE, -LC_PIECE_CONTROL_POINT_SIZE);
lcVector3 CubeMax(LC_PIECE_CONTROL_POINT_SIZE, LC_PIECE_CONTROL_POINT_SIZE, LC_PIECE_CONTROL_POINT_SIZE);
*CurVert++ = CubeMin[0]; *CurVert++ = CubeMin[1]; *CurVert++ = CubeMin[2];
*CurVert++ = CubeMin[0]; *CurVert++ = CubeMax[1]; *CurVert++ = CubeMin[2];
*CurVert++ = CubeMax[0]; *CurVert++ = CubeMax[1]; *CurVert++ = CubeMin[2];
*CurVert++ = CubeMax[0]; *CurVert++ = CubeMin[1]; *CurVert++ = CubeMin[2];
*CurVert++ = CubeMin[0]; *CurVert++ = CubeMin[1]; *CurVert++ = CubeMax[2];
*CurVert++ = CubeMin[0]; *CurVert++ = CubeMax[1]; *CurVert++ = CubeMax[2];
*CurVert++ = CubeMax[0]; *CurVert++ = CubeMax[1]; *CurVert++ = CubeMax[2];
*CurVert++ = CubeMax[0]; *CurVert++ = CubeMin[1]; *CurVert++ = CubeMax[2];
const GLushort Indices[36] =
{
0, 1, 2, 0, 2, 3, 7, 6, 5, 7, 5, 4, 5, 1, 0, 4, 5, 0,
7, 3, 2, 6, 7, 2, 0, 3, 7, 0, 7, 4, 6, 2, 1, 5, 6, 1
};
Context->EnableColorBlend(true);
Context->EnableCullFace(true);
const lcPreferences& Preferences = lcGetPreferences();
const lcVector4 ControlPointColor = lcVector4FromColor(Preferences.mControlPointColor);
const lcVector4 ControlPointFocusedColor = lcVector4FromColor(Preferences.mControlPointFocusedColor);
for (quint32 ControlPointIndex = 0; ControlPointIndex < mControlPoints.size(); ControlPointIndex++)
{
Context->SetWorldMatrix(lcMul(mControlPoints[ControlPointIndex].Transform, WorldMatrix));
Context->SetVertexBufferPointer(Verts);
Context->SetVertexFormatPosition(3);
Context->SetIndexBufferPointer(Indices);
if (IsFocused(LC_PIECE_SECTION_CONTROL_POINT_FIRST + ControlPointIndex))
Context->SetColor(ControlPointFocusedColor);
else
Context->SetColor(ControlPointColor);
Context->DrawIndexedPrimitives(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0);
}
Context->EnableCullFace(false);
Context->EnableColorBlend(false);
}
QVariant lcPiece::GetPropertyValue(lcObjectPropertyId PropertyId) const
{
switch (PropertyId)
{
case lcObjectPropertyId::PieceId:
return QVariant::fromValue<void*>(mPieceData.Get());
case lcObjectPropertyId::PieceColor:
return GetColorIndex();
case lcObjectPropertyId::PieceStepShow:
case lcObjectPropertyId::PieceStepHide:
case lcObjectPropertyId::CameraName:
case lcObjectPropertyId::CameraProjection:
case lcObjectPropertyId::CameraFOV:
case lcObjectPropertyId::CameraNear:
case lcObjectPropertyId::CameraFar:
case lcObjectPropertyId::CameraPositionX:
case lcObjectPropertyId::CameraPositionY:
case lcObjectPropertyId::CameraPositionZ:
case lcObjectPropertyId::CameraTargetX:
case lcObjectPropertyId::CameraTargetY:
case lcObjectPropertyId::CameraTargetZ:
case lcObjectPropertyId::CameraUpX:
case lcObjectPropertyId::CameraUpY:
case lcObjectPropertyId::CameraUpZ:
case lcObjectPropertyId::LightName:
case lcObjectPropertyId::LightType:
case lcObjectPropertyId::LightColor:
case lcObjectPropertyId::LightBlenderPower:
case lcObjectPropertyId::LightPOVRayPower:
case lcObjectPropertyId::LightCastShadow:
case lcObjectPropertyId::LightPOVRayFadeDistance:
case lcObjectPropertyId::LightPOVRayFadePower:
case lcObjectPropertyId::LightBlenderRadius:
case lcObjectPropertyId::LightBlenderAngle:
case lcObjectPropertyId::LightAreaSizeX:
case lcObjectPropertyId::LightAreaSizeY:
case lcObjectPropertyId::LightSpotConeAngle:
case lcObjectPropertyId::LightSpotPenumbraAngle:
case lcObjectPropertyId::LightPOVRaySpotTightness:
case lcObjectPropertyId::LightAreaShape:
case lcObjectPropertyId::LightPOVRayAreaGridX:
case lcObjectPropertyId::LightPOVRayAreaGridY:
case lcObjectPropertyId::ObjectPositionX:
case lcObjectPropertyId::ObjectPositionY:
case lcObjectPropertyId::ObjectPositionZ:
case lcObjectPropertyId::ObjectRotationX:
case lcObjectPropertyId::ObjectRotationY:
case lcObjectPropertyId::ObjectRotationZ:
case lcObjectPropertyId::Count:
break;
}
return QVariant();
}
bool lcPiece::SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value)
{
Q_UNUSED(Step);
Q_UNUSED(AddKey);
switch (PropertyId)
{
case lcObjectPropertyId::PieceId:
return SetPieceId(static_cast<lcPieceData*>(Value.value<void*>()));
case lcObjectPropertyId::PieceColor:
return SetColorIndex(Value.toInt());
case lcObjectPropertyId::PieceStepShow:
case lcObjectPropertyId::PieceStepHide:
case lcObjectPropertyId::CameraName:
case lcObjectPropertyId::CameraProjection:
case lcObjectPropertyId::CameraFOV:
case lcObjectPropertyId::CameraNear:
case lcObjectPropertyId::CameraFar:
case lcObjectPropertyId::CameraPositionX:
case lcObjectPropertyId::CameraPositionY:
case lcObjectPropertyId::CameraPositionZ:
case lcObjectPropertyId::CameraTargetX:
case lcObjectPropertyId::CameraTargetY:
case lcObjectPropertyId::CameraTargetZ:
case lcObjectPropertyId::CameraUpX:
case lcObjectPropertyId::CameraUpY:
case lcObjectPropertyId::CameraUpZ:
case lcObjectPropertyId::LightName:
case lcObjectPropertyId::LightType:
case lcObjectPropertyId::LightColor:
case lcObjectPropertyId::LightBlenderPower:
case lcObjectPropertyId::LightPOVRayPower:
case lcObjectPropertyId::LightCastShadow:
case lcObjectPropertyId::LightPOVRayFadeDistance:
case lcObjectPropertyId::LightPOVRayFadePower:
case lcObjectPropertyId::LightBlenderRadius:
case lcObjectPropertyId::LightBlenderAngle:
case lcObjectPropertyId::LightAreaSizeX:
case lcObjectPropertyId::LightAreaSizeY:
case lcObjectPropertyId::LightSpotConeAngle:
case lcObjectPropertyId::LightSpotPenumbraAngle:
case lcObjectPropertyId::LightPOVRaySpotTightness:
case lcObjectPropertyId::LightAreaShape:
case lcObjectPropertyId::LightPOVRayAreaGridX:
case lcObjectPropertyId::LightPOVRayAreaGridY:
case lcObjectPropertyId::ObjectPositionX:
case lcObjectPropertyId::ObjectPositionY:
case lcObjectPropertyId::ObjectPositionZ:
case lcObjectPropertyId::ObjectRotationX:
case lcObjectPropertyId::ObjectRotationY:
case lcObjectPropertyId::ObjectRotationZ:
case lcObjectPropertyId::Count:
break;
}
return false;
}
lcStep lcPiece::GetLastStep() const
{
lcStep Step = std::max({ mStepShow, mPosition.GetLastKeyFrame(), mRotation.GetLastKeyFrame() });
if (mStepHide != LC_STEP_MAX)
Step = lcMax(Step, mStepHide);
return Step;
}
bool lcPiece::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const
{
switch (PropertyId)
{
case lcObjectPropertyId::PieceId:
case lcObjectPropertyId::PieceColor:
case lcObjectPropertyId::PieceStepShow:
case lcObjectPropertyId::PieceStepHide:
case lcObjectPropertyId::CameraName:
case lcObjectPropertyId::CameraProjection:
case lcObjectPropertyId::CameraFOV:
case lcObjectPropertyId::CameraNear:
case lcObjectPropertyId::CameraFar:
case lcObjectPropertyId::CameraPositionX:
case lcObjectPropertyId::CameraPositionY:
case lcObjectPropertyId::CameraPositionZ:
case lcObjectPropertyId::CameraTargetX:
case lcObjectPropertyId::CameraTargetY:
case lcObjectPropertyId::CameraTargetZ:
case lcObjectPropertyId::CameraUpX:
case lcObjectPropertyId::CameraUpY:
case lcObjectPropertyId::CameraUpZ:
case lcObjectPropertyId::LightName:
case lcObjectPropertyId::LightType:
case lcObjectPropertyId::LightColor:
case lcObjectPropertyId::LightBlenderPower:
case lcObjectPropertyId::LightPOVRayPower:
case lcObjectPropertyId::LightCastShadow:
case lcObjectPropertyId::LightPOVRayFadeDistance:
case lcObjectPropertyId::LightPOVRayFadePower:
case lcObjectPropertyId::LightBlenderRadius:
case lcObjectPropertyId::LightBlenderAngle:
case lcObjectPropertyId::LightAreaSizeX:
case lcObjectPropertyId::LightAreaSizeY:
case lcObjectPropertyId::LightSpotConeAngle:
case lcObjectPropertyId::LightSpotPenumbraAngle:
case lcObjectPropertyId::LightPOVRaySpotTightness:
case lcObjectPropertyId::LightAreaShape:
case lcObjectPropertyId::LightPOVRayAreaGridX:
case lcObjectPropertyId::LightPOVRayAreaGridY:
return false;
case lcObjectPropertyId::ObjectPositionX:
case lcObjectPropertyId::ObjectPositionY:
case lcObjectPropertyId::ObjectPositionZ:
return mPosition.HasKeyFrame(Time);
case lcObjectPropertyId::ObjectRotationX:
case lcObjectPropertyId::ObjectRotationY:
case lcObjectPropertyId::ObjectRotationZ:
return mRotation.HasKeyFrame(Time);
case lcObjectPropertyId::Count:
return false;
}
return false;
}
bool lcPiece::SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame)
{
switch (PropertyId)
{
case lcObjectPropertyId::PieceId:
case lcObjectPropertyId::PieceColor:
case lcObjectPropertyId::PieceStepShow:
case lcObjectPropertyId::PieceStepHide:
case lcObjectPropertyId::CameraName:
case lcObjectPropertyId::CameraProjection:
case lcObjectPropertyId::CameraFOV:
case lcObjectPropertyId::CameraNear:
case lcObjectPropertyId::CameraFar:
case lcObjectPropertyId::CameraPositionX:
case lcObjectPropertyId::CameraPositionY:
case lcObjectPropertyId::CameraPositionZ:
case lcObjectPropertyId::CameraTargetX:
case lcObjectPropertyId::CameraTargetY:
case lcObjectPropertyId::CameraTargetZ:
case lcObjectPropertyId::CameraUpX:
case lcObjectPropertyId::CameraUpY:
case lcObjectPropertyId::CameraUpZ:
case lcObjectPropertyId::LightName:
case lcObjectPropertyId::LightType:
case lcObjectPropertyId::LightColor:
case lcObjectPropertyId::LightBlenderPower:
case lcObjectPropertyId::LightPOVRayPower:
case lcObjectPropertyId::LightCastShadow:
case lcObjectPropertyId::LightPOVRayFadeDistance:
case lcObjectPropertyId::LightPOVRayFadePower:
case lcObjectPropertyId::LightBlenderRadius:
case lcObjectPropertyId::LightBlenderAngle:
case lcObjectPropertyId::LightAreaSizeX:
case lcObjectPropertyId::LightAreaSizeY:
case lcObjectPropertyId::LightSpotConeAngle:
case lcObjectPropertyId::LightSpotPenumbraAngle:
case lcObjectPropertyId::LightPOVRaySpotTightness:
case lcObjectPropertyId::LightAreaShape:
case lcObjectPropertyId::LightPOVRayAreaGridX:
case lcObjectPropertyId::LightPOVRayAreaGridY:
return false;
case lcObjectPropertyId::ObjectPositionX:
case lcObjectPropertyId::ObjectPositionY:
case lcObjectPropertyId::ObjectPositionZ:
return mPosition.SetKeyFrame(Time, KeyFrame);
case lcObjectPropertyId::ObjectRotationX:
case lcObjectPropertyId::ObjectRotationY:
case lcObjectPropertyId::ObjectRotationZ:
return mRotation.SetKeyFrame(Time, KeyFrame);
case lcObjectPropertyId::Count:
return false;
}
return false;
}
void lcPiece::RemoveKeyFrames()
{
mPosition.RemoveAllKeys();
mRotation.RemoveAllKeys();
}
lcPieceHistoryState lcPiece::GetHistoryState() const
{
lcPieceHistoryState State;
State.Id = mId;
State.Hidden = mHidden;
State.FileLine = mFileLine;
State.PieceId = mID;
State.GroupId = mGroup ? static_cast<uint32_t>(mGroup->GetId()) : LC_GROUP_ID_INVALID;
State.ColorIndex = mColorIndex;
State.ColorCode = mColorCode;
State.StepShow = mStepShow;
State.StepHide = mStepHide;
State.PivotMatrix = mPivotMatrix;
State.PivotPointValid = mPivotPointValid;
State.ControlPoints = mControlPoints;
State.Position = mPosition;
State.Rotation = mRotation;
return State;
}
void lcPiece::SetHistoryState(const lcPieceHistoryState& State, Project* Project, lcGroup* Group)
{
mId = State.Id;
mHidden = State.Hidden;
mFileLine = State.FileLine;
mID = State.PieceId;
mGroup = Group;
mColorIndex = State.ColorIndex;
mColorCode = State.ColorCode;
mStepShow = State.StepShow;
mStepHide = State.StepHide;
mPivotMatrix = State.PivotMatrix;
mPivotPointValid = State.PivotPointValid;
mControlPoints = State.ControlPoints;
mPosition = State.Position;
mRotation = State.Rotation;
lcPieceDataRef RestoredData = lcGetPiecesLibrary()->AcquirePieceData(mID.toLatin1(), Project, true, true);
if (mPieceData != RestoredData)
{
const bool NewPiece = !mPieceData;
SetPieceData(std::move(RestoredData), mID, true, false);
if (NewPiece)
UpdateMesh();
}
else
UpdateMesh();
// std::vector<bool> mTrainTrackConnections;
}
void lcPiece::AddMainModelRenderMeshes(lcScene* Scene, bool Highlight, bool Fade) const
{
lcRenderMeshState RenderMeshState = lcRenderMeshState::Default;
bool ParentActive = false;
if (Highlight)
RenderMeshState = lcRenderMeshState::Highlighted;
if (Fade)
RenderMeshState = lcRenderMeshState::Faded;
if (Scene->GetDrawInterface())
{
const lcPiece* ActiveSubmodelInstance = Scene->GetActiveSubmodelInstance();
if (!ActiveSubmodelInstance)
RenderMeshState = IsFocused() ? lcRenderMeshState::Focused : (IsSelected() ? lcRenderMeshState::Selected : RenderMeshState);
else if (ActiveSubmodelInstance == this)
ParentActive = true;
else
RenderMeshState = lcRenderMeshState::Faded;
}
if (mPieceData->GetSynthInfo() && !HasGeneratedMesh() && Scene->GetRequireGeneratedMeshes())
{
Scene->MarkMissingAssets();
return;
}
if (mSynthPending && Scene->GetRequireCompleteAssets())
Scene->MarkMissingAssets();
else if (lcMesh* Mesh = GetDisplayMesh(); Mesh && !mPieceData->IsLoading())
Scene->AddMesh(Mesh, mModelWorld, mColorIndex, RenderMeshState);
else
mPieceData->AddRenderMeshes(Scene, mModelWorld, mColorIndex, RenderMeshState, ParentActive);
if (RenderMeshState == lcRenderMeshState::Focused || RenderMeshState == lcRenderMeshState::Selected)
Scene->AddInterfaceObject(this);
}
void lcPiece::AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMatrix, int DefaultColorIndex, lcRenderMeshState RenderMeshState, bool ParentActive) const
{
int ColorIndex = mColorIndex;
if (ColorIndex == gDefaultColor)
ColorIndex = DefaultColorIndex;
const lcPiece* ActiveSubmodelInstance = Scene->GetActiveSubmodelInstance();
if (ActiveSubmodelInstance == this)
RenderMeshState = lcRenderMeshState::Default;
else if (ParentActive)
RenderMeshState = IsFocused() ? lcRenderMeshState::Focused : (IsSelected() ? lcRenderMeshState::Selected : lcRenderMeshState::Default);
if (mPieceData->GetSynthInfo() && !HasGeneratedMesh() && Scene->GetRequireGeneratedMeshes())
{
Scene->MarkMissingAssets();
return;
}
if (mSynthPending && Scene->GetRequireCompleteAssets())
Scene->MarkMissingAssets();
else if (lcMesh* Mesh = GetDisplayMesh(); Mesh && !mPieceData->IsLoading())
Scene->AddMesh(Mesh, lcMul(mModelWorld, WorldMatrix), ColorIndex, RenderMeshState);
else
mPieceData->AddRenderMeshes(Scene, lcMul(mModelWorld, WorldMatrix), ColorIndex, RenderMeshState, ActiveSubmodelInstance == this);
if (ParentActive && (RenderMeshState == lcRenderMeshState::Focused || RenderMeshState == lcRenderMeshState::Selected))
Scene->AddInterfaceObject(this);
}
void lcPiece::SubModelCompareBoundingBox(const lcMatrix44& WorldMatrix, lcVector3& Min, lcVector3& Max) const
{
const lcMatrix44 ModelWorldMatrix = lcMul(mModelWorld, WorldMatrix);
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceData->IsLoading())
{
mPieceData->CompareBoundingBox(ModelWorldMatrix, Min, Max);
return;
}
lcVector3 Points[8];
lcGetBoxCorners(Mesh->mBoundingBox, Points);
for (const lcVector3& Corner : Points)
{
const lcVector3 Point = lcMul31(Corner, ModelWorldMatrix);
Min = lcMin(Point, Min);
Max = lcMax(Point, Max);
}
}
void lcPiece::SubModelAddBoundingBoxPoints(const lcMatrix44& WorldMatrix, std::vector<lcVector3>& Points, lcGeometryBoundsMode Mode) const
{
if (Mode == lcGeometryBoundsMode::AvailableOnly && mPieceData->GetSynthInfo() && !HasGeneratedMesh())
return;
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceData->IsLoading())
mPieceData->AddSubModelBoundingBoxPoints(lcMul(mModelWorld, WorldMatrix), Points, Mode);
else
{
lcVector3 BoxPoints[8];
const lcMatrix44 ModelWorldMatrix = lcMul(mModelWorld, WorldMatrix);
lcGetBoxCorners(Mesh->mBoundingBox, BoxPoints);
for (int i = 0; i < 8; i++)
Points.emplace_back(lcMul31(BoxPoints[i], ModelWorldMatrix));
}
}
void lcPiece::MoveSelected(lcStep Step, bool AddKey, const lcVector3& Distance)
{
const quint32 Section = GetFocusSection();
if (Section == LC_PIECE_SECTION_POSITION || Section == LC_PIECE_SECTION_INVALID)
{
const lcVector3 Position = mModelWorld.GetTranslation() + Distance;
SetPosition(Position, Step, AddKey);
mModelWorld.SetTranslation(Position);
}
else if (Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST)
{
const quint32 ControlPointIndex = Section - LC_PIECE_SECTION_CONTROL_POINT_FIRST;
if (ControlPointIndex < mControlPoints.size())
{
const lcMatrix33 InverseWorldMatrix = lcMatrix33AffineInverse(lcMatrix33(mModelWorld));
lcMatrix44& Transform = mControlPoints[ControlPointIndex].Transform;
Transform.SetTranslation(Transform.GetTranslation() + lcMul(Distance, InverseWorldMatrix));
}
UpdateMesh();
}
}
void lcPiece::Rotate(lcStep Step, bool AddKey, const lcMatrix33& RotationMatrix, const lcVector3& Center, const lcMatrix33& RotationFrame)
{
const quint32 Section = GetFocusSection();
if (Section == LC_PIECE_SECTION_POSITION || Section == LC_PIECE_SECTION_INVALID)
{
lcVector3 Distance = mModelWorld.GetTranslation() - Center;
const lcMatrix33 LocalToWorldMatrix = lcMatrix33(mModelWorld);
const lcMatrix33 LocalToFocusMatrix = lcMul(LocalToWorldMatrix, RotationFrame);
lcMatrix33 NewLocalToWorldMatrix = lcMul(LocalToFocusMatrix, RotationMatrix);
const lcMatrix33 WorldToLocalMatrix = lcMatrix33AffineInverse(LocalToWorldMatrix);
Distance = lcMul(Distance, WorldToLocalMatrix);
Distance = lcMul(Distance, NewLocalToWorldMatrix);
NewLocalToWorldMatrix.Orthonormalize();
SetPosition(Center + Distance, Step, AddKey);
SetRotation(NewLocalToWorldMatrix, Step, AddKey);
}
else if (Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST)
{
const quint32 ControlPointIndex = Section - LC_PIECE_SECTION_CONTROL_POINT_FIRST;
if (ControlPointIndex < mControlPoints.size())
{
lcMatrix44& Transform = mControlPoints[ControlPointIndex].Transform;
const lcMatrix33 PieceWorldMatrix(mModelWorld);
const lcMatrix33 LocalToWorldMatrix = lcMul(lcMatrix33(Transform), PieceWorldMatrix);
const lcMatrix33 LocalToFocusMatrix = lcMul(LocalToWorldMatrix, RotationFrame);
lcMatrix33 NewLocalToWorldMatrix = lcMul(lcMul(LocalToFocusMatrix, RotationMatrix), lcMatrix33AffineInverse(PieceWorldMatrix));
NewLocalToWorldMatrix.Orthonormalize();
Transform = lcMatrix44(NewLocalToWorldMatrix, Transform.GetTranslation());
}
UpdateMesh();
}
}
void lcPiece::MovePivotPoint(const lcVector3& Distance)
{
if (!IsFocused(LC_PIECE_SECTION_POSITION))
return;
mPivotMatrix.SetTranslation(mPivotMatrix.GetTranslation() + lcMul30(Distance, lcMatrix44AffineInverse(mModelWorld)));
mPivotPointValid = true;
}
void lcPiece::RotatePivotPoint(const lcMatrix33& RotationMatrix)
{
if (!IsFocused(LC_PIECE_SECTION_POSITION))
return;
lcMatrix33 NewPivotRotationMatrix = lcMul(RotationMatrix, lcMatrix33(mPivotMatrix));
NewPivotRotationMatrix.Orthonormalize();
mPivotMatrix = lcMatrix44(NewPivotRotationMatrix, mPivotMatrix.GetTranslation());
mPivotPointValid = true;
}
quint32 lcPiece::GetAllowedTransforms() const
{
const quint32 Section = GetFocusSection();
if (Section == LC_PIECE_SECTION_POSITION || Section == LC_PIECE_SECTION_INVALID)
return LC_OBJECT_TRANSFORM_MOVE_XYZ | LC_OBJECT_TRANSFORM_ROTATE_XYZ;
const lcSynthInfo* SynthInfo = mPieceData->GetSynthInfo();
if (SynthInfo)
{
if (SynthInfo->IsUnidirectional())
return LC_OBJECT_TRANSFORM_MOVE_Z;
if (SynthInfo->IsCurve())
return LC_OBJECT_TRANSFORM_MOVE_XYZ | LC_OBJECT_TRANSFORM_ROTATE_XYZ | LC_OBJECT_TRANSFORM_SCALE_X;
if (SynthInfo->IsNondirectional())
return LC_OBJECT_TRANSFORM_MOVE_XYZ;
}
return 0;
}
lcVector3 lcPiece::GetSectionPosition(quint32 Section) const
{
if (Section == LC_PIECE_SECTION_POSITION)
{
if (mPivotPointValid)
return lcMul(mPivotMatrix, mModelWorld).GetTranslation();
else
return mModelWorld.GetTranslation();
}
if (mPieceData->GetSynthInfo())
{
if (Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST)
{
const quint32 ControlPointIndex = Section - LC_PIECE_SECTION_CONTROL_POINT_FIRST;
if (ControlPointIndex < mControlPoints.size())
{
const lcMatrix44& Transform = mControlPoints[ControlPointIndex].Transform;
return lcMul(Transform, mModelWorld).GetTranslation();
}
}
}
if (mPieceData->GetTrainTrackInfo())
{
if (Section >= LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST)
{
const quint32 ConnectionIndex = Section - LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST;
const std::vector<lcTrainTrackConnection>& Connections = mPieceData->GetTrainTrackInfo()->GetConnections();
if (ConnectionIndex < Connections.size())
{
const lcMatrix44& Transform = Connections[ConnectionIndex].Transform;
return lcMul(Transform, mModelWorld).GetTranslation();
}
}
}
return lcVector3(0.0f, 0.0f, 0.0f);
}
lcVector3 lcPiece::GetRotationCenter() const
{
const quint32 Section = GetFocusSection();
if (Section == LC_PIECE_SECTION_POSITION || Section == LC_PIECE_SECTION_INVALID)
{
if (mPivotPointValid)
return lcMul31(mPivotMatrix.GetTranslation(), mModelWorld);
}
else if (mPieceData->GetSynthInfo())
{
if (Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST)
{
const quint32 ControlPointIndex = Section - LC_PIECE_SECTION_CONTROL_POINT_FIRST;
if (ControlPointIndex < mControlPoints.size())
{
const lcMatrix44& Transform = mControlPoints[ControlPointIndex].Transform;
return lcMul31(Transform.GetTranslation(), mModelWorld);
}
}
}
else if (mPieceData->GetTrainTrackInfo())
{
if (Section >= LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST)
{
const quint32 ConnectionIndex = Section - LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST;
const std::vector<lcTrainTrackConnection>& Connections = mPieceData->GetTrainTrackInfo()->GetConnections();
if (ConnectionIndex < Connections.size())
{
const lcMatrix44& Transform = Connections[ConnectionIndex].Transform;
return lcMul(Transform, mModelWorld).GetTranslation();
}
}
}
return mModelWorld.GetTranslation();
}
lcMatrix33 lcPiece::GetRelativeRotation() const
{
const quint32 Section = GetFocusSection();
if (Section == LC_PIECE_SECTION_POSITION || Section == LC_PIECE_SECTION_INVALID)
{
if (mPivotPointValid)
return lcMatrix33(lcMul(mModelWorld, mPivotMatrix));
else
return lcMatrix33(mModelWorld);
}
else if (mPieceData->GetSynthInfo())
{
if (Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST)
{
const quint32 ControlPointIndex = Section - LC_PIECE_SECTION_CONTROL_POINT_FIRST;
if (ControlPointIndex < mControlPoints.size())
{
const lcMatrix44& Transform = mControlPoints[ControlPointIndex].Transform;
return lcMatrix33(lcMul(Transform, mModelWorld));
}
}
}
else if (mPieceData->GetTrainTrackInfo())
{
if (Section >= LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST)
{
const quint32 ConnectionIndex = Section - LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST;
const std::vector<lcTrainTrackConnection>& Connections = mPieceData->GetTrainTrackInfo()->GetConnections();
if (ConnectionIndex < Connections.size())
{
const lcMatrix44& Transform = Connections[ConnectionIndex].Transform;
return lcMatrix33(lcMul(Transform, mModelWorld));
}
}
}
return lcMatrix33Identity();
}
bool lcPiece::CanAddControlPoint() const
{
if (mControlPoints.size() >= LC_MAX_CONTROL_POINTS)
return false;
const lcSynthInfo* SynthInfo = mPieceData->GetSynthInfo();
return SynthInfo && SynthInfo->CanAddControlPoints();
}
bool lcPiece::CanRemoveControlPoint() const
{
const quint32 Section = GetFocusSection();
return Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST && Section <= LC_PIECE_SECTION_CONTROL_POINT_LAST && mControlPoints.size() > 2;
}
bool lcPiece::InsertControlPoint(const lcVector3& WorldStart, const lcVector3& WorldEnd)
{
if (!CanAddControlPoint())
return false;
const lcMatrix44 InverseWorldMatrix = lcMatrix44AffineInverse(mModelWorld);
const lcVector3 Start = lcMul31(WorldStart, InverseWorldMatrix);
const lcVector3 End = lcMul31(WorldEnd, InverseWorldMatrix);
const lcSynthInfo* SynthInfo = mPieceData->GetSynthInfo();
const int ControlPointIndex = SynthInfo->InsertControlPoint(mControlPoints, Start, End);
if (ControlPointIndex)
{
SetFocused(GetFocusSection(), false);
SetFocused(LC_PIECE_SECTION_CONTROL_POINT_FIRST + ControlPointIndex, true);
UpdateMesh();
return true;
}
return false;
}
bool lcPiece::RemoveFocusedControlPoint()
{
quint32 Section = GetFocusSection();
if (Section < LC_PIECE_SECTION_CONTROL_POINT_FIRST)
return false;
const quint32 ControlPointIndex = Section - LC_PIECE_SECTION_CONTROL_POINT_FIRST;
if (ControlPointIndex >= mControlPoints.size() || mControlPoints.size() <= 2)
return false;
SetFocused(GetFocusSection(), false);
SetFocused(LC_PIECE_SECTION_POSITION, true);
mControlPoints.erase(mControlPoints.begin() + ControlPointIndex);
UpdateMesh();
return true;
}
void lcPiece::VerifyControlPoints(std::vector<lcPieceControlPoint>& ControlPoints) const
{
const lcSynthInfo* SynthInfo = mPieceData->GetSynthInfo();
if (!SynthInfo)
{
ControlPoints.clear();
}
else
{
if (ControlPoints.size() > LC_MAX_CONTROL_POINTS)
ControlPoints.resize(LC_MAX_CONTROL_POINTS);
SynthInfo->VerifyControlPoints(ControlPoints);
}
}
QString lcPiece::GetName() const
{
return QString::fromLatin1(mPieceData->m_strDescription);
}
bool lcPiece::IsVisible(lcStep Step) const
{
return !mHidden && (mStepShow <= Step) && (mStepHide > Step || mStepHide == LC_STEP_MAX);
}
bool lcPiece::IsVisibleInSubModel() const
{
return (mStepHide == LC_STEP_MAX) && !mHidden;
}
void lcPiece::GetModelParts(const lcMatrix44& WorldMatrix, int DefaultColorIndex, std::vector<lcModelPartsEntry>& ModelParts) const
{
if (!IsVisibleInSubModel())
return;
int ColorIndex = mColorIndex;
if (ColorIndex == gDefaultColor)
ColorIndex = DefaultColorIndex;
if (!mMesh || mPieceData->mState != lcAssetState::Loaded)
mPieceData->GetModelParts(lcMul(mModelWorld, WorldMatrix), ColorIndex, ModelParts);
else
ModelParts.emplace_back(lcModelPartsEntry{ lcMul(mModelWorld, WorldMatrix), mPieceData.Get(), mMesh, ColorIndex });
}
const lcBoundingBox& lcPiece::GetBoundingBox() const
{
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceData->IsLoading())
return mPieceData->GetBoundingBox();
else
return Mesh->mBoundingBox;
}
void lcPiece::CompareBoundingBox(lcVector3& Min, lcVector3& Max) const
{
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceData->IsLoading())
mPieceData->CompareBoundingBox(mModelWorld, Min, Max);
else
{
lcVector3 Points[8];
lcGetBoxCorners(Mesh->mBoundingBox, Points);
for (int i = 0; i < 8; i++)
{
const lcVector3 Point = lcMul31(Points[i], mModelWorld);
Min = lcMin(Point, Min);
Max = lcMax(Point, Max);
}
}
}
lcGroup* lcPiece::GetTopGroup()
{
return mGroup ? mGroup->GetTopGroup() : nullptr;
}
void lcPiece::UpdatePosition(lcStep Step)
{
mPosition.Update(Step);
mRotation.Update(Step);
mModelWorld = lcMatrix44(mRotation, mPosition);
}
void lcPiece::UpdateMesh()
{
mSynthPending = mPieceData && mPieceData->GetSynthInfo();
if (mSynthPending)
lcGetPiecesLibrary()->RequestSynthMesh(this);
else
{
delete mMesh;
mMesh = nullptr;
lcGetPiecesLibrary()->CancelSynthMesh(this);
}
}
lcMesh* lcPiece::GetDisplayMesh() const
{
if (!mPieceData || mPieceData->mState != lcAssetState::Loaded)
return nullptr;
if (mMesh)
return mMesh;
if (mSynthPending && mPieceData && mPieceData->GetSynthInfo())
return lcGetPiecesLibrary()->GetLoadingMesh();
return nullptr;
}
void lcPiece::SetGeneratedMesh(lcMesh* Mesh)
{
delete mMesh;
mMesh = Mesh;
mSynthPending = false;
}
void lcPiece::RefreshGeneratedMesh()
{
delete mMesh;
mMesh = nullptr;
UpdateMesh();
}