Files
leocad/common/piece.cpp
T
Leonardo Zide 3ec8b274b1 Unify asset loading priorities and clarify wait APIs
Replace load flags and the loader's separate priority enum with Low, Normal, and Blocking priorities. Blocking loads wait for completion; asynchronous loads and internal scheduling retain promotion-only behavior.

Acquire consumer references in the library, reuse loader loading for stud-style rebuilds, and remove QueuePiece, single-piece readiness wrappers, and the library texture readiness wrapper. Rename scoped readiness APIs to WaitFor functions and document their scheduling and lifetime contracts.

Validated with a Debug build and a temporary lifecycle harness covering priority promotion, blocking loads, reference cleanup, selective style reloads, cancellation, pause/resume, and shutdown.
2026-10-13 23:27:29 -07:00

1604 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 "pieceinf.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(PieceInfo* Info)
: lcObject(lcObjectType::Piece)
{
SetPieceInfo(Info, QString(), true, true);
mColorIndex = gDefaultColor;
mColorCode = 16;
mPivotMatrix = lcMatrix44Identity();
}
lcPiece::lcPiece(const lcPiece& Other)
: lcObject(lcObjectType::Piece)
{
SetPieceInfo(Other.mPieceInfo, 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 (mPieceInfo)
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
Library->ReleasePieceInfo(mPieceInfo);
Library->NotifyConsumersChanged();
}
delete mMesh;
}
void lcPiece::SetPieceInfo(PieceInfo* Info, const QString& ID, bool Wait, bool UpdateSynthInfo)
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
PieceInfo* PreviousInfo = mPieceInfo;
if (PreviousInfo != Info || UpdateSynthInfo)
Library->CancelSynthMesh(this);
if (PreviousInfo != Info)
{
delete mMesh;
mMesh = nullptr;
}
mPieceInfo = Info;
if (mPieceInfo)
Library->LoadPieceInfo(mPieceInfo, Wait ? lcPieceLoadPriority::Blocking : lcPieceLoadPriority::Normal);
if (!ID.isEmpty())
mID = ID;
else if (mPieceInfo)
mID = mPieceInfo->mFileName;
else
mID.clear();
if (UpdateSynthInfo)
{
mControlPoints.clear();
const lcSynthInfo* SynthInfo = mPieceInfo ? mPieceInfo->GetSynthInfo() : nullptr;
if (SynthInfo)
SynthInfo->GetDefaultControlPoints(mControlPoints);
UpdateMesh();
}
else if (PreviousInfo && PreviousInfo != Info)
UpdateMesh();
Library->NotifyConsumersChanged();
}
bool lcPiece::SetPieceId(PieceInfo* Info)
{
if (mPieceInfo == Info)
return false;
lcPiecesLibrary* Library = lcGetPiecesLibrary();
Library->ReleasePieceInfo(mPieceInfo);
SetPieceInfo(Info, QString(), true, true);
return true;
}
void lcPiece::UpdateID()
{
mID = mPieceInfo->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");
PieceInfo* pInfo = lcGetPiecesLibrary()->FindPiece(name, Project, true, false);
SetPieceInfo(pInfo, 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.PieceInfoRayTest))
{
ObjectRayTest.ObjectSection.Object = const_cast<lcPiece*>(this);
ObjectRayTest.ObjectSection.Section = LC_PIECE_SECTION_POSITION;
ObjectRayTest.PieceInfoRayTest.Transform = lcMul(ObjectRayTest.PieceInfoRayTest.Transform, mModelWorld);
}
if (mPieceInfo->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.PieceInfoRayTest.Plane = Plane;
}
}
}
}
bool lcPiece::MinIntersectDist(const lcVector3& Start, const lcVector3& End, float& MinDistance, lcPieceInfoRayTest& PieceInfoRayTest) const
{
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceInfo->IsLoading())
return mPieceInfo->MinIntersectDist(Start, End, MinDistance, PieceInfoRayTest);
if (!Mesh->MinIntersectDist(Start, End, MinDistance, PieceInfoRayTest.Plane))
return false;
PieceInfoRayTest.Info = mPieceInfo;
PieceInfoRayTest.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 || mPieceInfo->IsLoading())
return mPieceInfo->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.GetSelectionRenderMode() == lcSelectionRenderMode::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 (mPieceInfo->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*>(mPieceInfo);
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<PieceInfo*>(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;
}
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 lcModel* Model) const
{
lcPieceHistoryState State;
State.Id = mId;
State.Hidden = mHidden;
State.FileLine = mFileLine;
State.PieceId = mID;
State.GroupIndex = ~0U;
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;
const std::vector<std::unique_ptr<lcGroup>>& Groups = Model->GetGroups();
if (mGroup)
{
for (size_t GroupIndex = 0; GroupIndex < Groups.size(); GroupIndex++)
{
if (mGroup == Groups[GroupIndex].get())
{
State.GroupIndex = static_cast<uint32_t>(GroupIndex);
break;
}
}
}
return State;
}
void lcPiece::SetHistoryState(const lcPieceHistoryState& State, const lcModel* Model)
{
const std::vector<std::unique_ptr<lcGroup>>& Groups = Model->GetGroups();
mId = State.Id;
mHidden = State.Hidden;
mFileLine = State.FileLine;
mID = State.PieceId;
mGroup = (State.GroupIndex < Groups.size()) ? Groups[State.GroupIndex].get() : nullptr;
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;
PieceInfo* Info = lcGetPiecesLibrary()->FindPiece(mID.toLatin1(), Model->GetProject(), true, true);
if (mPieceInfo != Info)
{
const bool NewPiece = !mPieceInfo;
if (mPieceInfo)
lcGetPiecesLibrary()->ReleasePieceInfo(mPieceInfo);
SetPieceInfo(Info, 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 (mPieceInfo->GetSynthInfo() && !HasGeneratedMesh() && Scene->GetRequireGeneratedMeshes())
{
Scene->MarkMissingAssets();
return;
}
if (mSynthPending && Scene->GetRequireCompleteAssets())
Scene->MarkMissingAssets();
else if (lcMesh* Mesh = GetDisplayMesh(); Mesh && !mPieceInfo->IsLoading())
Scene->AddMesh(Mesh, mModelWorld, mColorIndex, RenderMeshState);
else
mPieceInfo->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 (mPieceInfo->GetSynthInfo() && !HasGeneratedMesh() && Scene->GetRequireGeneratedMeshes())
{
Scene->MarkMissingAssets();
return;
}
if (mSynthPending && Scene->GetRequireCompleteAssets())
Scene->MarkMissingAssets();
else if (lcMesh* Mesh = GetDisplayMesh(); Mesh && !mPieceInfo->IsLoading())
Scene->AddMesh(Mesh, lcMul(mModelWorld, WorldMatrix), ColorIndex, RenderMeshState);
else
mPieceInfo->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 || mPieceInfo->IsLoading())
{
mPieceInfo->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 && mPieceInfo->GetSynthInfo() && !HasGeneratedMesh())
return;
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceInfo->IsLoading())
mPieceInfo->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 = mPieceInfo->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 (mPieceInfo->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 (mPieceInfo->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 = mPieceInfo->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 (mPieceInfo->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 (mPieceInfo->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 = mPieceInfo->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 (mPieceInfo->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 (mPieceInfo->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 = mPieceInfo->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 = mPieceInfo->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 = mPieceInfo->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 = mPieceInfo->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(mPieceInfo->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 || mPieceInfo->mState != lcPieceInfoState::Loaded)
mPieceInfo->GetModelParts(lcMul(mModelWorld, WorldMatrix), ColorIndex, ModelParts);
else
ModelParts.emplace_back(lcModelPartsEntry{ lcMul(mModelWorld, WorldMatrix), mPieceInfo, mMesh, ColorIndex });
}
const lcBoundingBox& lcPiece::GetBoundingBox() const
{
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceInfo->IsLoading())
return mPieceInfo->GetBoundingBox();
else
return Mesh->mBoundingBox;
}
void lcPiece::CompareBoundingBox(lcVector3& Min, lcVector3& Max) const
{
lcMesh* Mesh = GetDisplayMesh();
if (!Mesh || mPieceInfo->IsLoading())
mPieceInfo->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 = mPieceInfo && mPieceInfo->GetSynthInfo();
if (mSynthPending)
lcGetPiecesLibrary()->QueueSynthMesh(this);
else
{
delete mMesh;
mMesh = nullptr;
lcGetPiecesLibrary()->CancelSynthMesh(this);
}
}
lcMesh* lcPiece::GetDisplayMesh() const
{
if (!mPieceInfo || mPieceInfo->mState != lcPieceInfoState::Loaded)
return nullptr;
if (mMesh)
return mMesh;
if (mSynthPending && mPieceInfo && mPieceInfo->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();
}