mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
Use model and submodel terminology in user-facing help, errors, and the Windows file-type description. Standardize keyframe and spotlight text across menus, properties, undo history, and default light names while preserving internal identifiers and file-format tokens. Correct the directional-light and movement-snapping descriptions, remove obsolete Ctrl advice from Zoom Extents, and fix deselection and rotation wording. Leave translation catalogs unchanged for the deferred catalog update. Validation: debug build passes without warnings; UI XML and string-only diff checks pass.
6339 lines
152 KiB
C++
6339 lines
152 KiB
C++
#include "lc_global.h"
|
|
#include "lc_model.h"
|
|
#include "lc_modelhistory.h"
|
|
#include "piece.h"
|
|
#include "camera.h"
|
|
#include "light.h"
|
|
#include "group.h"
|
|
#include "project.h"
|
|
#include "lc_mainwindow.h"
|
|
#include "lc_profile.h"
|
|
#include "lc_library.h"
|
|
#include "lc_scene.h"
|
|
#include "lc_traintrack.h"
|
|
#include "lc_file.h"
|
|
#include "lc_piecedata.h"
|
|
#include "lc_view.h"
|
|
#include "minifig.h"
|
|
#include "lc_arraydialog.h"
|
|
#include "lc_minifigdialog.h"
|
|
#include "lc_groupdialog.h"
|
|
#include "lc_editgroupsdialog.h"
|
|
#include "lc_qpropertiesdialog.h"
|
|
#include "lc_qutils.h"
|
|
#include "lc_lxf.h"
|
|
#include "lc_previewwidget.h"
|
|
#include "lc_string.h"
|
|
#include <locale.h>
|
|
|
|
void lcModelProperties::LoadDefaults()
|
|
{
|
|
mAuthor = lcGetProfileString(LC_PROFILE_DEFAULT_AUTHOR_NAME);
|
|
mAmbientColor = lcVector3FromColor(lcGetProfileUInt(LC_PROFILE_DEFAULT_AMBIENT_COLOR));
|
|
}
|
|
|
|
void lcModelProperties::SaveDefaults()
|
|
{
|
|
lcSetProfileUInt(LC_PROFILE_DEFAULT_AMBIENT_COLOR, lcColorFromVector3(mAmbientColor));
|
|
}
|
|
|
|
void lcModelProperties::SaveLDraw(QTextStream& Stream) const
|
|
{
|
|
const QLatin1String LineEnding("\r\n");
|
|
|
|
Stream << QLatin1String("0 ") << mDescription << LineEnding;
|
|
Stream << QLatin1String("0 Name: ") << mModelName << LineEnding;
|
|
Stream << QLatin1String("0 Author: ") << mAuthor << LineEnding;
|
|
|
|
if (!mComments.isEmpty())
|
|
{
|
|
const QStringList Comments = mComments.split('\n');
|
|
for (const QString& Comment : Comments)
|
|
Stream << QLatin1String("0 !LEOCAD MODEL COMMENT ") << Comment << LineEnding;
|
|
}
|
|
|
|
// lcVector3 mAmbientColor;
|
|
}
|
|
|
|
bool lcModelProperties::ParseLDrawHeader(QString Line, bool FirstLine)
|
|
{
|
|
QTextStream LineStream(&Line, QIODevice::ReadOnly);
|
|
|
|
QString Token;
|
|
LineStream >> Token;
|
|
const int StartPos = LineStream.pos();
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("!LEOCAD"))
|
|
return false;
|
|
|
|
if (Token == QLatin1String("Name:"))
|
|
{
|
|
mModelName = LineStream.readLine().mid(1);
|
|
return true;
|
|
}
|
|
|
|
if (Token == QLatin1String("Author:"))
|
|
{
|
|
mAuthor = LineStream.readLine().mid(1);
|
|
return true;
|
|
}
|
|
|
|
if (FirstLine)
|
|
{
|
|
LineStream.seek(StartPos);
|
|
mDescription = LineStream.readLine().mid(1);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void lcModelProperties::ParseLDrawLine(QTextStream& Stream)
|
|
{
|
|
QString Token;
|
|
Stream >> Token;
|
|
|
|
if (Token == QLatin1String("AUTHOR"))
|
|
mAuthor = Stream.readLine().mid(1);
|
|
else if (Token == QLatin1String("DESCRIPTION"))
|
|
mDescription = Stream.readLine().mid(1);
|
|
else if (Token == QLatin1String("COMMENT"))
|
|
{
|
|
QString Comment = Stream.readLine().mid(1);
|
|
if (!mComments.isEmpty())
|
|
mComments += '\n';
|
|
mComments += Comment;
|
|
}
|
|
}
|
|
|
|
void lcPOVRayOptions::ParseLDrawLine(QTextStream& LineStream)
|
|
{
|
|
QString Token;
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("HEADER_INCLUDE_FILE"))
|
|
{
|
|
LineStream >> HeaderIncludeFile;
|
|
if (!QFileInfo(HeaderIncludeFile).isReadable())
|
|
HeaderIncludeFile.clear();
|
|
}
|
|
else if (Token == QLatin1String("FOOTER_INCLUDE_FILE"))
|
|
{
|
|
LineStream >> FooterIncludeFile;
|
|
if (!QFileInfo(FooterIncludeFile).isReadable())
|
|
FooterIncludeFile.clear();
|
|
}
|
|
else if (Token == QLatin1String("FLOOR_AXIS"))
|
|
{
|
|
LineStream >> FloorAxis;
|
|
if (FloorAxis < 0 || FloorAxis > 2)
|
|
FloorAxis = 1; // y
|
|
}
|
|
else if (Token == QLatin1String("FLOOR_COLOR_RGB"))
|
|
LineStream >> FloorColor[0] >> FloorColor[1] >> FloorColor[2];
|
|
else if (Token == QLatin1String("FLOOR_AMBIENT"))
|
|
LineStream >> FloorAmbient;
|
|
else if (Token == QLatin1String("FLOOR_DIFFUSE"))
|
|
LineStream >> FloorDiffuse;
|
|
else if (Token == QLatin1String("EXCLUDE_FLOOR"))
|
|
ExcludeFloor = true;
|
|
else if (Token == QLatin1String("EXCLUDE_BACKGROUND"))
|
|
ExcludeBackground = true;
|
|
else if (Token == QLatin1String("NO_REFLECTION"))
|
|
NoReflection = true;
|
|
else if (Token == QLatin1String("NO_SHADOWS"))
|
|
NoShadow = true;
|
|
else if (Token == QLatin1String("USE_LGEO"))
|
|
UseLGEO = true;
|
|
}
|
|
|
|
void lcPOVRayOptions::SaveLDraw(QTextStream& Stream) const
|
|
{
|
|
const QLatin1String LineEnding("\r\n");
|
|
if (!HeaderIncludeFile.isEmpty())
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY HEADER_INCLUDE_FILE ") << QDir::toNativeSeparators(HeaderIncludeFile) << LineEnding;
|
|
if (!FooterIncludeFile.isEmpty())
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY FOOTER_INCLUDE_FILE ") << QDir::toNativeSeparators(FooterIncludeFile) << LineEnding;
|
|
if (FloorAxis != 1)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY FLOOR_AXIS ") << FloorAxis << LineEnding;
|
|
if (FloorColor != lcVector3(0.8f,0.8f,0.8f))
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY FLOOR_COLOR_RGB ") << FloorColor[0] << ' ' << FloorColor[1] << ' ' << FloorColor[2] << LineEnding;
|
|
if (FloorAmbient != 0.4f)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY FLOOR_AMBIENT ") << FloorAmbient << LineEnding;
|
|
if (FloorDiffuse != 0.4f)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY FLOOR_DIFFUSE ") << FloorDiffuse << LineEnding;
|
|
if (ExcludeFloor)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY EXCLUDE_FLOOR") << LineEnding;
|
|
if (ExcludeBackground)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY EXCLUDE_BACKGROUND") << LineEnding;
|
|
if (NoReflection)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY NO_REFLECTION") << LineEnding;
|
|
if (NoShadow)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY NO_SHADOWS") << LineEnding;
|
|
if (UseLGEO)
|
|
Stream << QLatin1String("0 !LEOCAD POV_RAY USE_LGEO") << LineEnding;
|
|
}
|
|
|
|
lcModel::lcModel(const QString& FileName, Project* Project, bool Preview)
|
|
: mProject(Project), mIsPreview(Preview)
|
|
{
|
|
mProperties.mModelName = FileName;
|
|
mProperties.mFileName = FileName;
|
|
mProperties.LoadDefaults();
|
|
|
|
mActive = false;
|
|
mCurrentStep = 1;
|
|
}
|
|
|
|
void lcModel::SetProject(Project* Project)
|
|
{
|
|
if (mProject && mProject != Project)
|
|
{
|
|
const QString SourceFileName = mProject->GetFileName();
|
|
const QString SourceDirectory = SourceFileName.isEmpty() ? QString() : QFileInfo(SourceFileName).absolutePath();
|
|
|
|
PreserveAssetSourceDirectory(SourceDirectory, false);
|
|
}
|
|
|
|
mProject = Project;
|
|
}
|
|
|
|
void lcModel::PreserveAssetSourceDirectory(const QString& Directory, bool PreferCurrentDirectory)
|
|
{
|
|
if (!Directory.isEmpty())
|
|
{
|
|
const QString CleanDirectory = QDir::cleanPath(Directory);
|
|
const std::vector<QString>::iterator NewEnd = std::remove(mAssetSourceDirectories.begin(), mAssetSourceDirectories.end(), CleanDirectory);
|
|
|
|
mAssetSourceDirectories.erase(NewEnd, mAssetSourceDirectories.end());
|
|
|
|
if (mPreferCurrentAssetDirectory)
|
|
mAssetSourceDirectories.insert(mAssetSourceDirectories.begin(), CleanDirectory);
|
|
else
|
|
mAssetSourceDirectories.push_back(CleanDirectory);
|
|
}
|
|
|
|
mPreferCurrentAssetDirectory = PreferCurrentDirectory;
|
|
}
|
|
|
|
lcModelAssetSourceState lcModel::GetAssetSourceState() const
|
|
{
|
|
return { mAssetSourceDirectories, mPreferCurrentAssetDirectory };
|
|
}
|
|
|
|
void lcModel::RestoreAssetSourceState(const lcModelAssetSourceState& State)
|
|
{
|
|
mAssetSourceDirectories = State.Directories;
|
|
mPreferCurrentAssetDirectory = State.PreferCurrentDirectory;
|
|
}
|
|
|
|
std::vector<QString> lcModel::GetAssetSearchDirectories() const
|
|
{
|
|
std::vector<QString> Directories;
|
|
QString CurrentDirectory;
|
|
|
|
if (mProject && !mProject->GetFileName().isEmpty())
|
|
CurrentDirectory = QFileInfo(mProject->GetFileName()).absolutePath();
|
|
|
|
if (mPreferCurrentAssetDirectory && !CurrentDirectory.isEmpty())
|
|
Directories.push_back(CurrentDirectory);
|
|
|
|
for (const QString& Directory : mAssetSourceDirectories)
|
|
{
|
|
if (mPreferCurrentAssetDirectory && Directory == CurrentDirectory)
|
|
continue;
|
|
|
|
Directories.push_back(Directory);
|
|
}
|
|
|
|
if (!mPreferCurrentAssetDirectory && !CurrentDirectory.isEmpty() &&
|
|
std::find(mAssetSourceDirectories.begin(), mAssetSourceDirectories.end(), CurrentDirectory) == mAssetSourceDirectories.end())
|
|
Directories.push_back(CurrentDirectory);
|
|
|
|
return Directories;
|
|
}
|
|
|
|
lcModel::~lcModel()
|
|
{
|
|
mPreviewInsertPieceInfo.clear();
|
|
|
|
if (mPieceData)
|
|
{
|
|
if (mPieceData->GetModel() == this)
|
|
{
|
|
if (!mIsPreview && gMainWindow && gMainWindow->GetInsertPieceData() == mPieceData)
|
|
gMainWindow->SetInsertPieceData(nullptr);
|
|
|
|
// Keep the name indexed while placements still reference the deleted model.
|
|
lcGetPiecesLibrary()->InvalidatePieceDataLoad(mPieceData.Get());
|
|
mPieceData->DetachContainer();
|
|
mPieceData->SetFailed(tr("Model '%1' was deleted.").arg(QString::fromLatin1(mPieceData->mFileName)));
|
|
lcGetPiecesLibrary()->NotifyAssetStatusChanged();
|
|
}
|
|
|
|
mPieceData.Reset();
|
|
}
|
|
|
|
DeleteModel();
|
|
}
|
|
|
|
bool lcModel::GetPieceWorldMatrix(lcPiece* Piece, lcMatrix44& ParentWorldMatrix) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& ModelPiece : mPieces)
|
|
{
|
|
if (ModelPiece.get() == Piece)
|
|
{
|
|
ParentWorldMatrix = lcMul(ModelPiece->mModelWorld, ParentWorldMatrix);
|
|
return true;
|
|
}
|
|
|
|
const lcPieceData* PieceData = ModelPiece->mPieceData.Get();
|
|
|
|
if (PieceData->IsModel())
|
|
{
|
|
lcMatrix44 WorldMatrix = lcMul(ModelPiece->mModelWorld, ParentWorldMatrix);
|
|
|
|
if (PieceData->GetPieceWorldMatrix(Piece, WorldMatrix))
|
|
{
|
|
ParentWorldMatrix = WorldMatrix;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool lcModel::IncludesModel(const lcModel* Model) const
|
|
{
|
|
std::unordered_set<const lcModel*> Visited;
|
|
const std::function<bool(const lcModel*)> Visit = [Model, &Visited, &Visit](const lcModel* Current)
|
|
{
|
|
if (Current == Model)
|
|
return true;
|
|
|
|
if (!Current || !Visited.insert(Current).second)
|
|
return false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : Current->mPieces)
|
|
{
|
|
const lcPieceData* PieceData = Piece->mPieceData.Get();
|
|
|
|
if (PieceData && PieceData->IsModel() && Visit(PieceData->GetModel()))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
return Visit(this);
|
|
}
|
|
|
|
void lcModel::DeleteModel()
|
|
{
|
|
if (gMainWindow)
|
|
{
|
|
std::vector<lcView*> Views = lcView::GetModelViews(this);
|
|
|
|
// TODO: this is only needed to avoid a dangling pointer during undo/redo if a camera is set to a view but we should find a better solution instead
|
|
for (lcView* View : Views)
|
|
{
|
|
lcCamera* ViewCamera = View->GetCamera();
|
|
|
|
if (!ViewCamera->IsSimple())
|
|
{
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
{
|
|
if (Camera.get() == ViewCamera)
|
|
{
|
|
View->SetCamera(ViewCamera, true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mPieces.clear();
|
|
mCameras.clear();
|
|
mLights.clear();
|
|
mGroups.clear();
|
|
mFileLines.clear();
|
|
mHasDirectGeometry = false;
|
|
}
|
|
|
|
void lcModel::CreatePieceData(Project* Project)
|
|
{
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
bool Reused;
|
|
mPieceData = Library->AcquireModelPieceData(mProperties.mFileName.toLatin1().constData(), Project, Reused);
|
|
mPieceData->SetModel(this);
|
|
|
|
if (Reused)
|
|
{
|
|
// Reusing a referenced name can turn an existing placement into a cycle.
|
|
mPieces.erase(std::remove_if(mPieces.begin(), mPieces.end(), [this](const std::unique_ptr<lcPiece>& Piece)
|
|
{
|
|
return Piece->mPieceData && Piece->mPieceData->IncludesModel(this);
|
|
}), mPieces.end());
|
|
}
|
|
|
|
// The model owns the reference acquired by AcquireModelPieceData(), including while
|
|
// mesh requests are deferred until its source lines have been parsed.
|
|
if (!Project->DefersModelMeshRequests())
|
|
{
|
|
if (mIsPreview)
|
|
Library->RequestPieceDataLoad(mPieceData.Get(), lcLoadPriority::Normal);
|
|
else
|
|
Library->WaitForPieceData(mPieceData);
|
|
}
|
|
|
|
if (Reused)
|
|
{
|
|
UpdateAllViews();
|
|
|
|
if (!Project->DefersModelMeshRequests())
|
|
RefreshPreview();
|
|
}
|
|
}
|
|
|
|
bool lcModel::RebindPieceData(lcPieceData* Previous, lcPieceData* Replacement)
|
|
{
|
|
bool Changed = false;
|
|
|
|
mPieces.erase(std::remove_if(mPieces.begin(), mPieces.end(), [this, Previous, Replacement, &Changed](const std::unique_ptr<lcPiece>& Piece)
|
|
{
|
|
if (Piece->mPieceData != Previous)
|
|
return false;
|
|
|
|
Changed = true;
|
|
|
|
if (Replacement->IncludesModel(this))
|
|
return true;
|
|
|
|
const QString PieceId = Piece->GetID();
|
|
Piece->SetPieceData(lcPieceDataRef(Replacement), PieceId, false, false);
|
|
|
|
return false;
|
|
}), mPieces.end());
|
|
|
|
return Changed;
|
|
}
|
|
|
|
void lcModel::QueueMeshBuild()
|
|
{
|
|
mPieceData->SetModel(this);
|
|
lcGetPiecesLibrary()->RebuildModelPieceData(mPieceData.Get());
|
|
}
|
|
|
|
void lcModel::UpdateAllViews() const
|
|
{
|
|
lcView::UpdateProjectViews(mProject);
|
|
}
|
|
|
|
void lcModel::RefreshPreview() const
|
|
{
|
|
if (mIsPreview || !mProject)
|
|
return;
|
|
|
|
if (gMainWindow && gMainWindow->GetPreviewWidget())
|
|
gMainWindow->GetPreviewWidget()->RefreshModel(this);
|
|
}
|
|
|
|
std::vector<lcPieceDataRef> lcModel::GetRequiredPieceData() const
|
|
{
|
|
std::vector<lcPieceDataRef> RequiredPieceData;
|
|
std::unordered_set<const lcModel*> Visited;
|
|
|
|
const std::function<void(const lcModel*)> Collect = [&RequiredPieceData, &Visited, &Collect](const lcModel* Model)
|
|
{
|
|
if (!Model || !Visited.insert(Model).second)
|
|
return;
|
|
|
|
if (Model->mPieceData)
|
|
RequiredPieceData.push_back(Model->mPieceData);
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : Model->mPieces)
|
|
{
|
|
const lcPieceDataRef& PieceData = Piece->mPieceData;
|
|
|
|
if (PieceData->IsModel())
|
|
Collect(PieceData->GetModel());
|
|
else if (PieceData->IsProject())
|
|
Collect(PieceData->GetProject()->GetMainModel());
|
|
else
|
|
RequiredPieceData.push_back(PieceData);
|
|
}
|
|
};
|
|
|
|
Collect(this);
|
|
|
|
std::sort(RequiredPieceData.begin(), RequiredPieceData.end());
|
|
RequiredPieceData.erase(std::unique(RequiredPieceData.begin(), RequiredPieceData.end()), RequiredPieceData.end());
|
|
|
|
return RequiredPieceData;
|
|
}
|
|
|
|
std::vector<lcPiece*> lcModel::GetRequiredSynthPieces() const
|
|
{
|
|
std::vector<lcPiece*> Required;
|
|
std::unordered_set<const lcModel*> Visited;
|
|
|
|
const std::function<void(const lcModel*)> Collect = [&Required, &Visited, &Collect](const lcModel* Model)
|
|
{
|
|
if (!Model || !Visited.insert(Model).second)
|
|
return;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : Model->mPieces)
|
|
{
|
|
lcPieceData* PieceData = Piece->mPieceData.Get();
|
|
|
|
if (PieceData->IsModel())
|
|
Collect(PieceData->GetModel());
|
|
else if (PieceData->IsProject())
|
|
Collect(PieceData->GetProject()->GetMainModel());
|
|
else if (PieceData->GetSynthInfo())
|
|
Required.push_back(Piece.get());
|
|
}
|
|
};
|
|
|
|
Collect(this);
|
|
|
|
return Required;
|
|
}
|
|
|
|
void lcModel::WaitForAssets() const
|
|
{
|
|
const std::vector<lcPieceDataRef> RequiredPieceData = GetRequiredPieceData();
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
|
|
Library->WaitForPieceData(RequiredPieceData);
|
|
Library->WaitForSynthMeshes(GetRequiredSynthPieces());
|
|
|
|
// Temporary minifigure and thumbnail models have no container piece to update.
|
|
if (mPieceData)
|
|
{
|
|
std::vector<lcModel*> UpdatedModels;
|
|
const_cast<lcModel*>(this)->UpdatePieceData(UpdatedModels);
|
|
}
|
|
}
|
|
|
|
lcResult<void> lcModel::EnsureAssetsReady() const
|
|
{
|
|
WaitForAssets();
|
|
|
|
const std::vector<lcPieceDataRef> RequiredPieceData = GetRequiredPieceData();
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
|
|
for (const lcPieceDataRef& RequiredData : RequiredPieceData)
|
|
{
|
|
if (RequiredData->mState == lcAssetState::Loaded)
|
|
continue;
|
|
|
|
const QString Error = Library->GetPieceDataLoadError(RequiredData.Get());
|
|
|
|
if (!Error.isEmpty())
|
|
return lcUnexpected(Error);
|
|
|
|
return lcUnexpected(tr("Could not load all required pieces."));
|
|
}
|
|
|
|
const std::vector<lcPiece*> GeneratedPieces = GetRequiredSynthPieces();
|
|
|
|
for (const lcPiece* Piece : GeneratedPieces)
|
|
if (!Piece->HasGeneratedMesh())
|
|
{
|
|
QString Error = Library->GetSynthMeshError(Piece);
|
|
|
|
if (Error.isEmpty())
|
|
Error = tr("Could not generate geometry for piece '%1'.").arg(QString::fromLatin1(Piece->mPieceData->mFileName));
|
|
|
|
return lcUnexpected(Error);
|
|
}
|
|
|
|
return lcResult<void>();
|
|
}
|
|
|
|
void lcModel::UpdatePieceData(std::vector<lcModel*>& UpdatedModels)
|
|
{
|
|
if (std::find(UpdatedModels.begin(), UpdatedModels.end(), this) != UpdatedModels.end())
|
|
return;
|
|
|
|
mPieceData->SetModel(this);
|
|
UpdatedModels.push_back(this);
|
|
|
|
const lcMesh* Mesh = mPieceData->GetDisplayMesh();
|
|
|
|
if (mPieces.empty() && !Mesh)
|
|
{
|
|
mPieceData->SetBoundingBox(lcVector3(0.0f, 0.0f, 0.0f), lcVector3(0.0f, 0.0f, 0.0f));
|
|
return;
|
|
}
|
|
|
|
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsVisibleInSubModel())
|
|
{
|
|
Piece->mPieceData->UpdateBoundingBox(UpdatedModels);
|
|
Piece->CompareBoundingBox(Min, Max);
|
|
}
|
|
}
|
|
|
|
if (Mesh)
|
|
{
|
|
Min = lcMin(Min, Mesh->mBoundingBox.Min);
|
|
Max = lcMax(Max, Mesh->mBoundingBox.Max);
|
|
}
|
|
|
|
mPieceData->SetBoundingBox(Min, Max);
|
|
}
|
|
|
|
void lcModel::SaveLDraw(QTextStream& Stream, bool SelectedOnly, lcStep LastStep) const
|
|
{
|
|
const QLatin1String LineEnding("\r\n");
|
|
|
|
mProperties.SaveLDraw(Stream);
|
|
|
|
for (const QString& Directory : mAssetSourceDirectories)
|
|
Stream << QLatin1String("0 !LEOCAD MODEL ASSET_SOURCE ") << QString::fromLatin1(Directory.toUtf8().toBase64()) << LineEnding;
|
|
|
|
if (!mAssetSourceDirectories.empty() && !mPreferCurrentAssetDirectory)
|
|
Stream << QLatin1String("0 !LEOCAD MODEL ASSET_SOURCE_PRIORITY SOURCE") << LineEnding;
|
|
|
|
std::vector<lcGroup*> CurrentGroups;
|
|
lcStep Step = 1;
|
|
int CurrentLine = 0;
|
|
int AddedSteps = 0;
|
|
bool SavedStep = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (SelectedOnly && !Piece->IsSelected())
|
|
continue;
|
|
|
|
while (Piece->GetFileLine() > CurrentLine && CurrentLine < mFileLines.size())
|
|
{
|
|
QString Line = mFileLines[CurrentLine];
|
|
QTextStream LineStream(&Line, QIODevice::ReadOnly);
|
|
|
|
QString Token;
|
|
LineStream >> Token;
|
|
bool Skip = false;
|
|
|
|
if (Token == QLatin1String("0"))
|
|
{
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("STEP"))
|
|
{
|
|
if (Piece->GetStepShow() > Step)
|
|
{
|
|
if (LastStep && Step >= LastStep)
|
|
{
|
|
SavedStep = true;
|
|
break;
|
|
}
|
|
|
|
Step++;
|
|
}
|
|
else
|
|
Skip = true;
|
|
}
|
|
}
|
|
|
|
if (!Skip)
|
|
{
|
|
Stream << mFileLines[CurrentLine];
|
|
if (AddedSteps > 0)
|
|
AddedSteps--;
|
|
}
|
|
CurrentLine++;
|
|
}
|
|
|
|
if (SavedStep || (LastStep && Piece->GetStepShow() > LastStep))
|
|
break;
|
|
|
|
while (Piece->GetStepShow() > Step)
|
|
{
|
|
Stream << QLatin1String("0 STEP\r\n");
|
|
AddedSteps++;
|
|
Step++;
|
|
}
|
|
|
|
lcGroup* PieceGroup = Piece->GetGroup();
|
|
|
|
if (PieceGroup)
|
|
{
|
|
if (CurrentGroups.empty() || (!CurrentGroups.empty() && PieceGroup != CurrentGroups[CurrentGroups.size() - 1]))
|
|
{
|
|
std::deque<lcGroup*> PieceParents;
|
|
|
|
for (lcGroup* Group = PieceGroup; Group; Group = Group->mGroup)
|
|
PieceParents.push_front(Group);
|
|
|
|
std::deque<lcGroup*>::iterator ParentsToAdd = PieceParents.begin();
|
|
|
|
while (!CurrentGroups.empty())
|
|
{
|
|
lcGroup* Group = CurrentGroups.back();
|
|
const std::deque<lcGroup*>::iterator ParentFound = std::find(PieceParents.begin(), PieceParents.end(), Group);
|
|
|
|
if (ParentFound == PieceParents.end())
|
|
{
|
|
CurrentGroups.pop_back();
|
|
Stream << QLatin1String("0 !LEOCAD GROUP END\r\n");
|
|
}
|
|
else
|
|
{
|
|
ParentsToAdd = ParentFound + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (std::deque<lcGroup*>::iterator ParentIt = ParentsToAdd; ParentIt != PieceParents.end(); ParentIt++)
|
|
{
|
|
lcGroup* Group = *ParentIt;
|
|
CurrentGroups.emplace_back(Group);
|
|
Stream << QLatin1String("0 !LEOCAD GROUP BEGIN ") << Group->mName << LineEnding;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (CurrentGroups.size())
|
|
{
|
|
CurrentGroups.pop_back();
|
|
Stream << QLatin1String("0 !LEOCAD GROUP END\r\n");
|
|
}
|
|
}
|
|
|
|
if (Piece->mPieceData->GetSynthInfo())
|
|
{
|
|
Stream << QLatin1String("0 !LEOCAD SYNTH BEGIN\r\n");
|
|
|
|
const std::vector<lcPieceControlPoint>& ControlPoints = Piece->GetControlPoints();
|
|
for (const lcPieceControlPoint& ControlPoint : ControlPoints)
|
|
{
|
|
Stream << QLatin1String("0 !LEOCAD SYNTH CONTROL_POINT");
|
|
|
|
const float* FloatMatrix = ControlPoint.Transform.GetFloats();
|
|
const float Numbers[13] = { FloatMatrix[12], -FloatMatrix[14], FloatMatrix[13], FloatMatrix[0], -FloatMatrix[8], FloatMatrix[4], -FloatMatrix[2], FloatMatrix[10], -FloatMatrix[6], FloatMatrix[1], -FloatMatrix[9], FloatMatrix[5], ControlPoint.Scale };
|
|
|
|
for (int NumberIdx = 0; NumberIdx < 13; NumberIdx++)
|
|
Stream << ' ' << lcFormatValue(Numbers[NumberIdx], NumberIdx < 3 ? 4 : 6);
|
|
|
|
Stream << LineEnding;
|
|
}
|
|
}
|
|
|
|
Piece->SaveLDraw(Stream);
|
|
|
|
if (Piece->mPieceData->GetSynthInfo())
|
|
Stream << QLatin1String("0 !LEOCAD SYNTH END\r\n");
|
|
}
|
|
|
|
while (!SavedStep && CurrentLine < mFileLines.size())
|
|
{
|
|
QString Line = mFileLines[CurrentLine];
|
|
QTextStream LineStream(&Line, QIODevice::ReadOnly);
|
|
|
|
QString Token;
|
|
LineStream >> Token;
|
|
bool Skip = false;
|
|
|
|
if (Token == QLatin1String("0"))
|
|
{
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("STEP"))
|
|
{
|
|
if (AddedSteps-- > 0)
|
|
Skip = true;
|
|
else
|
|
{
|
|
if (LastStep && Step >= LastStep)
|
|
break;
|
|
|
|
Step++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Skip)
|
|
Stream << mFileLines[CurrentLine];
|
|
CurrentLine++;
|
|
}
|
|
|
|
while (CurrentGroups.size())
|
|
{
|
|
CurrentGroups.pop_back();
|
|
Stream << QLatin1String("0 !LEOCAD GROUP END\r\n");
|
|
}
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (!SelectedOnly || Camera->IsSelected())
|
|
Camera->SaveLDraw(Stream);
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
if (!SelectedOnly || Light->IsSelected())
|
|
Light->SaveLDraw(Stream);
|
|
|
|
Stream.flush();
|
|
}
|
|
|
|
void lcModel::LoadLDraw(QIODevice& Device, Project* Project)
|
|
{
|
|
lcPiece* Piece = nullptr;
|
|
lcCamera* Camera = nullptr;
|
|
lcLight* Light = nullptr;
|
|
std::vector<lcGroup*> CurrentGroups;
|
|
std::vector<lcPieceControlPoint> ControlPoints;
|
|
int CurrentStep = 1;
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
mHasDirectGeometry = false;
|
|
|
|
mProperties.mAuthor.clear();
|
|
mProperties.mDescription.clear();
|
|
mProperties.mComments.clear();
|
|
mAssetSourceDirectories.clear();
|
|
mPreferCurrentAssetDirectory = true;
|
|
bool ReadingHeader = true;
|
|
bool FirstLine = true;
|
|
|
|
while (!Device.atEnd())
|
|
{
|
|
const qint64 Pos = Device.pos();
|
|
QString OriginalLine = Device.readLine();
|
|
QString Line = OriginalLine.trimmed();
|
|
QTextStream LineStream(&Line, QIODevice::ReadOnly);
|
|
|
|
QString Token;
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("0"))
|
|
{
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("FILE"))
|
|
{
|
|
QString Name = LineStream.readAll().trimmed();
|
|
|
|
if (mProperties.mFileName != Name)
|
|
{
|
|
Device.seek(Pos);
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else if (Token == QLatin1String("NOFILE") || Token == QLatin1String("!DATA"))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (ReadingHeader)
|
|
{
|
|
ReadingHeader = mProperties.ParseLDrawHeader(Line, FirstLine);
|
|
FirstLine = false;
|
|
|
|
if (ReadingHeader)
|
|
continue;
|
|
}
|
|
|
|
if (Token == QLatin1String("STEP"))
|
|
{
|
|
delete Piece;
|
|
Piece = nullptr;
|
|
CurrentStep++;
|
|
mFileLines.append(OriginalLine);
|
|
continue;
|
|
}
|
|
|
|
if (Token != QLatin1String("!LEOCAD"))
|
|
{
|
|
mFileLines.append(OriginalLine);
|
|
|
|
if (Token == QLatin1String("!:"))
|
|
{
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("1") || Token == QLatin1String("2") || Token == QLatin1String("3") || Token == QLatin1String("4") || Token == QLatin1String("5"))
|
|
mHasDirectGeometry = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("MODEL"))
|
|
{
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("ASSET_SOURCE"))
|
|
{
|
|
const QString Directory = QString::fromUtf8(QByteArray::fromBase64(LineStream.readAll().trimmed().toLatin1()));
|
|
|
|
if (QDir::isAbsolutePath(Directory))
|
|
{
|
|
const QString CleanDirectory = QDir::cleanPath(Directory);
|
|
|
|
if (std::find(mAssetSourceDirectories.begin(), mAssetSourceDirectories.end(), CleanDirectory) == mAssetSourceDirectories.end())
|
|
mAssetSourceDirectories.push_back(CleanDirectory);
|
|
}
|
|
}
|
|
else if (Token == QLatin1String("ASSET_SOURCE_PRIORITY"))
|
|
{
|
|
LineStream >> Token;
|
|
mPreferCurrentAssetDirectory = Token != QLatin1String("SOURCE");
|
|
}
|
|
else
|
|
{
|
|
QString PropertiesLine = Token + LineStream.readAll();
|
|
QTextStream PropertiesStream(&PropertiesLine, QIODevice::ReadOnly);
|
|
mProperties.ParseLDrawLine(PropertiesStream);
|
|
}
|
|
}
|
|
else if (Token == QLatin1String("PIECE"))
|
|
{
|
|
if (!Piece)
|
|
Piece = new lcPiece(nullptr);
|
|
|
|
Piece->ParseLDrawLine(LineStream);
|
|
}
|
|
else if (Token == QLatin1String("CAMERA"))
|
|
{
|
|
if (!Camera)
|
|
Camera = new lcCamera(false);
|
|
|
|
if (Camera->ParseLDrawLine(LineStream))
|
|
{
|
|
Camera->CreateName(mCameras);
|
|
mCameras.emplace_back(Camera);
|
|
Camera = nullptr;
|
|
}
|
|
}
|
|
else if (Token == QLatin1String("LIGHT"))
|
|
{
|
|
if (!Light)
|
|
Light = new lcLight(lcVector3(0.0f, 0.0f, 0.0f), lcLightType::Point);
|
|
|
|
if (Light->ParseLDrawLine(LineStream))
|
|
{
|
|
Light->CreateName(mLights);
|
|
mLights.emplace_back(Light);
|
|
Light = nullptr;
|
|
}
|
|
}
|
|
else if (Token == QLatin1String("POV_RAY"))
|
|
{
|
|
mPOVRayOptions.ParseLDrawLine(LineStream);
|
|
}
|
|
else if (Token == QLatin1String("GROUP"))
|
|
{
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("BEGIN"))
|
|
{
|
|
QString Name = LineStream.readAll().trimmed();
|
|
lcGroup* Group = GetGroup(Name, true);
|
|
if (!CurrentGroups.empty())
|
|
Group->mGroup = CurrentGroups[CurrentGroups.size() - 1];
|
|
else
|
|
Group->mGroup = nullptr;
|
|
CurrentGroups.emplace_back(Group);
|
|
}
|
|
else if (Token == QLatin1String("END"))
|
|
{
|
|
if (!CurrentGroups.empty())
|
|
CurrentGroups.pop_back();
|
|
}
|
|
}
|
|
else if (Token == QLatin1String("SYNTH"))
|
|
{
|
|
LineStream >> Token;
|
|
|
|
if (Token == QLatin1String("BEGIN"))
|
|
{
|
|
ControlPoints.clear();
|
|
}
|
|
else if (Token == QLatin1String("END"))
|
|
{
|
|
ControlPoints.clear();
|
|
}
|
|
else if (Token == QLatin1String("CONTROL_POINT"))
|
|
{
|
|
float Numbers[13];
|
|
for (int TokenIdx = 0; TokenIdx < 13; TokenIdx++)
|
|
LineStream >> Numbers[TokenIdx];
|
|
|
|
lcPieceControlPoint& PieceControlPoint = ControlPoints.emplace_back();
|
|
PieceControlPoint.Transform = lcMatrix44(lcVector4(Numbers[3], Numbers[9], -Numbers[6], 0.0f), lcVector4(Numbers[5], Numbers[11], -Numbers[8], 0.0f),
|
|
lcVector4(-Numbers[4], -Numbers[10], Numbers[7], 0.0f), lcVector4(Numbers[0], Numbers[2], -Numbers[1], 1.0f));
|
|
PieceControlPoint.Scale = Numbers[12];
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else if (Token == QLatin1String("1"))
|
|
{
|
|
ReadingHeader = false;
|
|
int ColorCode;
|
|
LineStream >> ColorCode;
|
|
|
|
float IncludeMatrix[12];
|
|
for (int TokenIdx = 0; TokenIdx < 12; TokenIdx++)
|
|
LineStream >> IncludeMatrix[TokenIdx];
|
|
|
|
lcMatrix44 IncludeTransform(lcVector4(IncludeMatrix[3], IncludeMatrix[6], IncludeMatrix[9], 0.0f), lcVector4(IncludeMatrix[4], IncludeMatrix[7], IncludeMatrix[10], 0.0f),
|
|
lcVector4(IncludeMatrix[5], IncludeMatrix[8], IncludeMatrix[11], 0.0f), lcVector4(IncludeMatrix[0], IncludeMatrix[1], IncludeMatrix[2], 1.0f));
|
|
|
|
QString PartId = LineStream.readAll().trimmed();
|
|
|
|
if (PartId.isEmpty())
|
|
continue;
|
|
|
|
QByteArray CleanId = PartId.toLatin1().toUpper().replace('\\', '/');
|
|
|
|
if (Library->FindPrimitive(CleanId.constData()))
|
|
{
|
|
mFileLines.append(OriginalLine);
|
|
mHasDirectGeometry = true;
|
|
}
|
|
else
|
|
{
|
|
if (!Piece)
|
|
Piece = new lcPiece(nullptr);
|
|
|
|
if (!CurrentGroups.empty())
|
|
Piece->SetGroup(CurrentGroups[CurrentGroups.size() - 1]);
|
|
|
|
lcPieceDataRef PieceData = Library->AcquirePieceData(PartId.toLatin1().constData(), Project, true, true);
|
|
|
|
const float* Matrix = IncludeTransform.GetFloats();
|
|
const lcMatrix44 Transform(lcVector4(Matrix[0], Matrix[2], -Matrix[1], 0.0f), lcVector4(Matrix[8], Matrix[10], -Matrix[9], 0.0f),
|
|
lcVector4(-Matrix[4], -Matrix[6], Matrix[5], 0.0f), lcVector4(Matrix[12], Matrix[14], -Matrix[13], 1.0f));
|
|
|
|
Piece->SetFileLine(mFileLines.size());
|
|
Piece->SetPieceData(std::move(PieceData), PartId, false, false);
|
|
Piece->Initialize(Transform, CurrentStep);
|
|
Piece->SetColorCode(ColorCode);
|
|
Piece->VerifyControlPoints(ControlPoints);
|
|
Piece->SetControlPoints(ControlPoints);
|
|
ControlPoints.clear();
|
|
|
|
if (Piece->mPieceData->IsModel() && Piece->mPieceData->GetModel()->IncludesModel(this))
|
|
{
|
|
Project->AddLoadWarning(tr("Model '%1': reference to '%2' was ignored because it would create a circular reference.").arg(GetFileName(), PartId));
|
|
delete Piece;
|
|
Piece = nullptr;
|
|
continue;
|
|
}
|
|
|
|
AddPiece(Piece);
|
|
Piece = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReadingHeader = false;
|
|
mFileLines.append(OriginalLine);
|
|
|
|
if (Token == QLatin1String("2") || Token == QLatin1String("3") || Token == QLatin1String("4") || Token == QLatin1String("5"))
|
|
mHasDirectGeometry = true;
|
|
}
|
|
|
|
FirstLine = false;
|
|
}
|
|
|
|
mCurrentStep = CurrentStep;
|
|
CalculateStep(mCurrentStep);
|
|
|
|
delete Piece;
|
|
delete Camera;
|
|
delete Light;
|
|
}
|
|
|
|
bool lcModel::LoadBinary(lcFile* file)
|
|
{
|
|
qint32 i, count;
|
|
char id[32];
|
|
quint32 rgb;
|
|
float fv = 0.4f;
|
|
quint8 ch;
|
|
quint16 sh;
|
|
|
|
file->Seek(0, SEEK_SET);
|
|
file->ReadBuffer(id, 32);
|
|
sscanf(&id[7], "%f", &fv);
|
|
|
|
if (memcmp(id, "LeoCAD ", 7))
|
|
return false;
|
|
|
|
if (fv == 0.0f)
|
|
{
|
|
const lconv *loc = localeconv();
|
|
id[8] = loc->decimal_point[0];
|
|
sscanf(&id[7], "%f", &fv);
|
|
|
|
if (fv == 0.0f)
|
|
return false;
|
|
}
|
|
|
|
if (fv > 0.4f)
|
|
file->ReadFloats(&fv, 1);
|
|
|
|
file->ReadU32(&rgb, 1);
|
|
|
|
if (fv < 0.6f) // old view
|
|
{
|
|
double eye[3], target[3];
|
|
file->ReadDoubles(eye, 3);
|
|
file->ReadDoubles(target, 3);
|
|
}
|
|
|
|
file->Seek(28, SEEK_CUR);
|
|
file->ReadS32(&i, 1);
|
|
mCurrentStep = i;
|
|
|
|
if (fv > 0.8f)
|
|
file->ReadU32();//m_nScene
|
|
|
|
file->ReadS32(&count, 1);
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
|
|
const size_t FirstNewPiece = mPieces.size();
|
|
|
|
while (count--)
|
|
{
|
|
if (fv > 0.4f)
|
|
{
|
|
lcPiece* pPiece = new lcPiece(nullptr);
|
|
pPiece->FileLoad(*file, mProject);
|
|
AddPiece(pPiece);
|
|
}
|
|
else
|
|
{
|
|
char name[LC_PIECE_NAME_LEN];
|
|
lcVector3 pos, rot;
|
|
quint8 color, step, group;
|
|
|
|
file->ReadFloats(pos.GetFloats(), 3);
|
|
file->ReadFloats(rot.GetFloats(), 3);
|
|
file->ReadU8(&color, 1);
|
|
file->ReadBuffer(name, 9);
|
|
lcstrcat(name, ".dat");
|
|
file->ReadU8(&step, 1);
|
|
file->ReadU8(&group, 1);
|
|
|
|
pos *= 25.0f;
|
|
lcMatrix44 WorldMatrix = lcMul(lcMatrix44RotationZ(rot[2] * LC_DTOR), lcMul(lcMatrix44RotationY(rot[1] * LC_DTOR), lcMatrix44RotationX(rot[0] * LC_DTOR)));
|
|
WorldMatrix.SetTranslation(pos);
|
|
|
|
lcPiece* pPiece = new lcPiece(Library->AcquirePieceData(name, mProject, true, false));
|
|
|
|
pPiece->Initialize(WorldMatrix, step);
|
|
pPiece->SetColorCode(lcGetColorCodeFromOriginalColor(color));
|
|
AddPiece(pPiece);
|
|
|
|
// pPiece->SetGroup((lcGroup*)group);
|
|
}
|
|
}
|
|
|
|
if (fv >= 0.4f)
|
|
{
|
|
file->ReadBuffer(&ch, 1);
|
|
if (ch == 0xFF) file->ReadU16(&sh, 1); else sh = ch;
|
|
if (sh > 100)
|
|
file->Seek(sh, SEEK_CUR);
|
|
else
|
|
{
|
|
QByteArray Author;
|
|
Author.resize(sh + 1);
|
|
file->ReadBuffer(Author.data(), sh);
|
|
Author[sh] = 0;
|
|
mProperties.mAuthor = QString::fromUtf8(Author);
|
|
}
|
|
|
|
file->ReadBuffer(&ch, 1);
|
|
if (ch == 0xFF) file->ReadU16(&sh, 1); else sh = ch;
|
|
if (sh > 100)
|
|
file->Seek(sh, SEEK_CUR);
|
|
else
|
|
{
|
|
QByteArray Description;
|
|
Description.resize(sh + 1);
|
|
file->ReadBuffer(Description.data(), sh);
|
|
Description[sh] = 0;
|
|
mProperties.mDescription = QString::fromUtf8(Description);
|
|
}
|
|
|
|
file->ReadBuffer(&ch, 1);
|
|
if (ch == 0xFF && fv < 1.3f) file->ReadU16(&sh, 1); else sh = ch;
|
|
if (sh > 255)
|
|
file->Seek(sh, SEEK_CUR);
|
|
else
|
|
{
|
|
QByteArray Comments;
|
|
Comments.resize(sh + 1);
|
|
file->ReadBuffer(Comments.data(), sh);
|
|
Comments[sh] = 0;
|
|
mProperties.mComments = QString::fromUtf8(Comments);
|
|
mProperties.mComments.replace(QLatin1String("\r\n"), QLatin1String("\n"));
|
|
}
|
|
}
|
|
|
|
if (fv >= 0.5f)
|
|
{
|
|
const size_t NumGroups = mGroups.size();
|
|
|
|
file->ReadS32(&count, 1);
|
|
for (i = 0; i < count; i++)
|
|
mGroups.emplace_back(new lcGroup());
|
|
|
|
for (size_t GroupIdx = NumGroups; GroupIdx < mGroups.size(); GroupIdx++)
|
|
{
|
|
lcGroup* Group = mGroups[GroupIdx].get();
|
|
|
|
if (fv < 1.0f)
|
|
{
|
|
char Name[LC_MAX_GROUP_NAME + 1];
|
|
file->ReadBuffer(Name, sizeof(Name));
|
|
Group->mName = QString::fromUtf8(Name);
|
|
file->ReadBuffer(&ch, 1);
|
|
Group->mGroup = (lcGroup*)-1;
|
|
}
|
|
else
|
|
Group->FileLoad(file);
|
|
}
|
|
|
|
for (size_t GroupIdx = NumGroups; GroupIdx < mGroups.size(); GroupIdx++)
|
|
{
|
|
lcGroup* Group = mGroups[GroupIdx].get();
|
|
|
|
i = (qint32)(quintptr)(Group->mGroup);
|
|
Group->mGroup = nullptr;
|
|
|
|
if (i > 0xFFFF || i == -1)
|
|
continue;
|
|
|
|
Group->mGroup = mGroups[NumGroups + i].get();
|
|
}
|
|
|
|
for (size_t PieceIndex = FirstNewPiece; PieceIndex < mPieces.size(); PieceIndex++)
|
|
{
|
|
lcPiece* Piece = mPieces[PieceIndex].get();
|
|
|
|
i = (qint32)(quintptr)(Piece->GetGroup());
|
|
Piece->SetGroup(nullptr);
|
|
|
|
if (i > 0xFFFF || i == -1)
|
|
continue;
|
|
|
|
Piece->SetGroup(mGroups[NumGroups + i].get());
|
|
}
|
|
|
|
RemoveEmptyGroups();
|
|
}
|
|
|
|
if (fv >= 0.6f)
|
|
{
|
|
if (fv < 1.0f)
|
|
file->Seek(4, SEEK_CUR);
|
|
else
|
|
file->Seek(2, SEEK_CUR);
|
|
|
|
file->ReadS32(&count, 1);
|
|
|
|
for (i = 0; i < count; i++)
|
|
lcCamera::FileLoad(*file);
|
|
}
|
|
|
|
if (fv >= 0.7f)
|
|
{
|
|
file->Seek(24, SEEK_CUR);
|
|
|
|
if (fv < 1.3f)
|
|
{
|
|
file->ReadU8(&ch, 1);
|
|
if (ch == 0xFF)
|
|
file->ReadU16(&sh, 1);
|
|
sh = ch;
|
|
}
|
|
else
|
|
file->ReadU16(&sh, 1);
|
|
|
|
file->Seek(sh, SEEK_CUR); // Background
|
|
}
|
|
|
|
if (fv >= 0.8f)
|
|
{
|
|
file->ReadBuffer(&ch, 1);
|
|
file->Seek(ch, SEEK_CUR);
|
|
file->ReadBuffer(&ch, 1);
|
|
file->Seek(ch, SEEK_CUR);
|
|
}
|
|
|
|
if (fv > 0.9f)
|
|
{
|
|
file->ReadU32(&rgb, 1);
|
|
mProperties.mAmbientColor[0] = (float)((unsigned char) (rgb))/255;
|
|
mProperties.mAmbientColor[1] = (float)((unsigned char) (((unsigned short) (rgb)) >> 8))/255;
|
|
mProperties.mAmbientColor[2] = (float)((unsigned char) ((rgb) >> 16))/255;
|
|
|
|
if (fv < 1.3f)
|
|
file->Seek(23, SEEK_CUR);
|
|
else
|
|
file->Seek(11, SEEK_CUR);
|
|
}
|
|
|
|
if (fv > 1.0f)
|
|
{
|
|
file->ReadU32(&rgb, 1);
|
|
file->ReadU32(&rgb, 1);
|
|
}
|
|
|
|
CalculateStep(mCurrentStep);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool lcModel::LoadLDD(const QString& FileData)
|
|
{
|
|
std::vector<lcPiece*> Pieces;
|
|
std::vector<std::vector<lcPiece*>> Groups;
|
|
|
|
if (!lcImportLXFMLFile(FileData, mProject, Pieces, Groups))
|
|
return false;
|
|
|
|
for (lcPiece* Piece : Pieces)
|
|
AddPiece(Piece);
|
|
|
|
for (const std::vector<lcPiece*>& Group : Groups)
|
|
{
|
|
lcGroup* NewGroup = AddGroup(tr("Group #"), nullptr);
|
|
for (lcPiece* Piece : Group)
|
|
Piece->SetGroup(NewGroup);
|
|
}
|
|
|
|
CalculateStep(mCurrentStep);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool lcModel::LoadInventory(const std::vector<lcSetInventoryItem>& SetInventory)
|
|
{
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
|
|
for (const lcSetInventoryItem& SetInventoryItem : SetInventory)
|
|
{
|
|
const lcPieceDataRef PieceData = Library->AcquirePieceData(SetInventoryItem.PartID + ".dat", mProject, true, false);
|
|
int Quantity = SetInventoryItem.Quantity;
|
|
|
|
while (Quantity--)
|
|
{
|
|
lcPiece* Piece = new lcPiece(nullptr);
|
|
Piece->SetPieceData(PieceData, QString(), false, true);
|
|
Piece->Initialize(lcMatrix44Identity(), 1);
|
|
Piece->SetColorCode(SetInventoryItem.ColorCode);
|
|
AddPiece(Piece);
|
|
}
|
|
}
|
|
|
|
if (mPieces.empty())
|
|
return false;
|
|
|
|
std::vector<lcPieceDataRef> RequiredPieceData;
|
|
|
|
RequiredPieceData.reserve(mPieces.size());
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
RequiredPieceData.push_back(Piece->mPieceData);
|
|
|
|
if (!Library->WaitForPieceData(RequiredPieceData))
|
|
return false;
|
|
|
|
auto RoundBounds = [](float& Value)
|
|
{
|
|
Value = ((Value < 0.0f) ? floor((Value - 5.0f) / 10.0f) : ceil((Value + 5.0f) / 10.0f)) * 10.0f;
|
|
};
|
|
|
|
constexpr float TargetHeight = 800.0f;
|
|
float CurrentX = 0.0f;
|
|
float CurrentY = 0.0f;
|
|
float ColumnWidth = 0.0f;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
lcBoundingBox BoundingBox = Piece->mPieceData->GetBoundingBox();
|
|
RoundBounds(BoundingBox.Min.x);
|
|
RoundBounds(BoundingBox.Min.y);
|
|
RoundBounds(BoundingBox.Max.x);
|
|
RoundBounds(BoundingBox.Max.y);
|
|
|
|
const float PieceWidth = BoundingBox.Max.x - BoundingBox.Min.x;
|
|
const float PieceHeight = BoundingBox.Max.y - BoundingBox.Min.y;
|
|
|
|
if (CurrentY + PieceHeight > TargetHeight)
|
|
{
|
|
CurrentY = 0.0f;
|
|
CurrentX += ColumnWidth;
|
|
ColumnWidth = 0.0f;
|
|
}
|
|
|
|
Piece->SetPosition(lcVector3(CurrentX + PieceWidth / 2.0f, CurrentY + PieceHeight / 2.0f, 0.0f), 1, false);
|
|
CurrentY += PieceHeight;
|
|
ColumnWidth = qMax(ColumnWidth, PieceWidth);
|
|
}
|
|
|
|
CalculateStep(mCurrentStep);
|
|
|
|
return true;
|
|
}
|
|
|
|
void lcModel::Merge(std::unique_ptr<lcModel> Other)
|
|
{
|
|
for (std::unique_ptr<lcPiece>& Piece : Other->mPieces)
|
|
{
|
|
Piece->SetFileLine(-1);
|
|
AddPiece(Piece.release());
|
|
}
|
|
|
|
Other->mPieces.clear();
|
|
|
|
for (std::unique_ptr<lcCamera>& Camera : Other->mCameras)
|
|
{
|
|
Camera->CreateName(mCameras);
|
|
mCameras.emplace_back(std::move(Camera));
|
|
}
|
|
|
|
Other->mCameras.clear();
|
|
|
|
for (std::unique_ptr<lcLight>& Light : Other->mLights)
|
|
{
|
|
Light->CreateName(mLights);
|
|
mLights.emplace_back(std::move(Light));
|
|
}
|
|
|
|
Other->mLights.clear();
|
|
|
|
for (std::vector<std::unique_ptr<lcGroup>>::iterator GroupIt = Other->mGroups.begin(); GroupIt != Other->mGroups.end(); GroupIt++)
|
|
{
|
|
std::unique_ptr<lcGroup>& Group = *GroupIt;
|
|
Group->CreateName(mGroups);
|
|
mGroups.emplace_back(std::move(Group));
|
|
}
|
|
|
|
Other->mGroups.clear();
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
}
|
|
|
|
lcResult<void> lcModel::Cut()
|
|
{
|
|
if (!AnyObjectsSelected())
|
|
return lcResult<void>();
|
|
|
|
const lcResult<void> Result = Copy();
|
|
|
|
if (!Result)
|
|
return Result;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
RemoveSelectedObjects();
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Cut"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
|
|
return lcResult<void>();
|
|
}
|
|
|
|
lcResult<void> lcModel::Copy()
|
|
{
|
|
if (mProject)
|
|
{
|
|
const lcResult<QByteArray> File = mProject->SerializeModel(this, true, 0);
|
|
|
|
if (!File)
|
|
return lcUnexpected(File.error());
|
|
|
|
gApplication->ExportClipboard(File.value());
|
|
}
|
|
else
|
|
{
|
|
QByteArray File;
|
|
QTextStream Stream(&File, QIODevice::WriteOnly);
|
|
SaveLDraw(Stream, true, 0);
|
|
gApplication->ExportClipboard(File);
|
|
}
|
|
|
|
return lcResult<void>();
|
|
}
|
|
|
|
lcResult<void> lcModel::Paste(bool PasteToCurrentStep)
|
|
{
|
|
if (gApplication->mClipboard.isEmpty())
|
|
return lcResult<void>();
|
|
|
|
const lcResult<lcModel*> Imported = lcGetActiveProject()->ImportClipboard(gApplication->mClipboard);
|
|
|
|
if (!Imported)
|
|
return lcUnexpected(Imported.error());
|
|
|
|
std::unique_ptr<lcModel> Model(Imported.value());
|
|
|
|
if (!Model)
|
|
return lcResult<void>();
|
|
|
|
const std::vector<std::unique_ptr<lcPiece>>& PastedPieces = Model->mPieces;
|
|
std::vector<lcObject*> SelectedObjects;
|
|
SelectedObjects.reserve(PastedPieces.size());
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : PastedPieces)
|
|
{
|
|
Piece->SetFileLine(-1);
|
|
|
|
if (PasteToCurrentStep)
|
|
{
|
|
Piece->SetStepShow(mCurrentStep);
|
|
SelectedObjects.emplace_back(Piece.get());
|
|
}
|
|
else
|
|
{
|
|
if (Piece->GetStepShow() <= mCurrentStep)
|
|
SelectedObjects.emplace_back(Piece.get());
|
|
}
|
|
}
|
|
|
|
if (PastedPieces.empty())
|
|
return lcResult<void>();
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
Merge(std::move(Model));
|
|
|
|
CalculateStep(mCurrentStep);
|
|
|
|
EndEditHistory();
|
|
|
|
if (SelectedObjects.size() == 1)
|
|
SetSelectionAndFocus(std::vector<lcObject*>(), SelectedObjects.front(), LC_PIECE_SECTION_POSITION, lcSelectionMode::Single);
|
|
else
|
|
SetSelectionAndFocus(SelectedObjects, nullptr, 0, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Paste"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
|
|
return lcResult<void>();
|
|
}
|
|
|
|
void lcModel::DuplicateSelectedPieces()
|
|
{
|
|
if (!AnyPiecesSelected())
|
|
{
|
|
QMessageBox::information(gMainWindow, tr("Duplicate Pieces"), tr("No pieces selected."));
|
|
return;
|
|
}
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
std::vector<lcObject*> NewPieces;
|
|
lcPiece* Focus = nullptr;
|
|
std::map<lcGroup*, lcGroup*> GroupMap;
|
|
|
|
std::function<lcGroup*(lcGroup*)> GetNewGroup = [this, &GroupMap, &GetNewGroup](lcGroup* Group)
|
|
{
|
|
const auto GroupIt = GroupMap.find(Group);
|
|
|
|
if (GroupIt != GroupMap.end())
|
|
return GroupIt->second;
|
|
else
|
|
{
|
|
lcGroup* Parent = Group->mGroup ? GetNewGroup(Group->mGroup) : nullptr;
|
|
QString GroupName = Group->mName;
|
|
|
|
while (!GroupName.isEmpty())
|
|
{
|
|
const QChar Last = GroupName[GroupName.size() - 1];
|
|
if (Last.isDigit())
|
|
GroupName.chop(1);
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (GroupName.isEmpty())
|
|
GroupName = Group->mName;
|
|
|
|
lcGroup* NewGroup = AddGroup(GroupName, Parent);
|
|
GroupMap[Group] = NewGroup;
|
|
return NewGroup;
|
|
}
|
|
};
|
|
|
|
for (size_t PieceIndex = 0; PieceIndex < mPieces.size(); PieceIndex++)
|
|
{
|
|
const lcPiece* Piece = mPieces[PieceIndex].get();
|
|
|
|
if (!Piece->IsSelected())
|
|
continue;
|
|
|
|
lcPiece* NewPiece = new lcPiece(*Piece);
|
|
NewPiece->UpdatePosition(mCurrentStep);
|
|
NewPieces.emplace_back(NewPiece);
|
|
|
|
if (Piece->IsFocused())
|
|
Focus = NewPiece;
|
|
|
|
PieceIndex++;
|
|
AddPiece(std::unique_ptr<lcPiece>(NewPiece), PieceIndex);
|
|
|
|
lcGroup* Group = Piece->GetGroup();
|
|
if (Group)
|
|
NewPiece->SetGroup(GetNewGroup(Group));
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
SetSelectionAndFocus(NewPieces, Focus, LC_PIECE_SECTION_POSITION, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Duplicate"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
}
|
|
|
|
void lcModel::PaintSelectedPieces()
|
|
{
|
|
SetSelectedPiecesColorIndex(gMainWindow->mColorIndex);
|
|
}
|
|
|
|
void lcModel::GetScene(lcScene* Scene, const lcCamera* ViewCamera, bool AllowHighlight, bool AllowFade) const
|
|
{
|
|
if (mPieceData)
|
|
mPieceData->AddRenderMesh(*Scene);
|
|
|
|
lcPiece* FocusPiece = nullptr;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected() || Piece->IsVisible(mCurrentStep))
|
|
{
|
|
if (Piece->IsFocused())
|
|
FocusPiece = Piece.get();
|
|
|
|
const lcStep StepShow = Piece->GetStepShow();
|
|
Piece->AddMainModelRenderMeshes(Scene, AllowHighlight && StepShow == mCurrentStep, AllowFade && StepShow < mCurrentStep);
|
|
}
|
|
}
|
|
|
|
if (Scene->GetDrawInterface() && !Scene->GetActiveSubmodelInstance())
|
|
{
|
|
if (FocusPiece)
|
|
UpdateTrainTrackConnections(FocusPiece, false);
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (Camera.get() != ViewCamera && Camera->IsVisible())
|
|
Scene->AddInterfaceObject(Camera.get());
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
if (Light->IsVisible())
|
|
Scene->AddInterfaceObject(Light.get());
|
|
}
|
|
|
|
if (Scene->GetDrawInsertPreview())
|
|
{
|
|
for (const lcPreviewInsertPieceInfo& PreviewPieceInfoTransform : mPreviewInsertPieceInfo)
|
|
PreviewPieceInfoTransform.PieceData->AddRenderMeshes(Scene, PreviewPieceInfoTransform.Transform, PreviewPieceInfoTransform.ColorIndex, lcRenderMeshState::Preview, false);
|
|
}
|
|
}
|
|
|
|
void lcModel::AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMatrix, int DefaultColorIndex, lcRenderMeshState RenderMeshState, bool ParentActive) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsVisibleInSubModel())
|
|
{
|
|
if (Piece->IsFocused())
|
|
UpdateTrainTrackConnections(Piece.get(), false);
|
|
|
|
Piece->AddSubModelRenderMeshes(Scene, WorldMatrix, DefaultColorIndex, RenderMeshState, ParentActive);
|
|
}
|
|
}
|
|
|
|
if (Scene->GetDrawInsertPreview())
|
|
{
|
|
for (const lcPreviewInsertPieceInfo& PreviewPieceInfoTransform : mPreviewInsertPieceInfo)
|
|
PreviewPieceInfoTransform.PieceData->AddRenderMeshes(Scene, lcMul(PreviewPieceInfoTransform.Transform, WorldMatrix), PreviewPieceInfoTransform.ColorIndex, lcRenderMeshState::Preview, false);
|
|
}
|
|
}
|
|
|
|
QImage lcModel::GetStepImage(bool Zoom, int Width, int Height, lcStep Step)
|
|
{
|
|
lcScopedGLContextState GLState;
|
|
|
|
WaitForAssets();
|
|
|
|
const lcResult<QImage> Image = RenderStepImageWithReadyAssets(Zoom, Width, Height, Step, nullptr, lcImageBackground::Default);
|
|
|
|
if (!Image)
|
|
{
|
|
if (gMainWindow)
|
|
QMessageBox::warning(gMainWindow, tr("Create Step Image"), Image.error());
|
|
|
|
return QImage();
|
|
}
|
|
|
|
return Image.value();
|
|
}
|
|
|
|
lcResult<QImage> lcModel::RenderStepImageWithReadyAssets(bool Zoom, int Width, int Height, lcStep Step, lcCamera* OutputCamera, lcImageBackground Background)
|
|
{
|
|
lcScopedGLContextState GLState;
|
|
|
|
const lcView* ActiveView = gMainWindow ? gMainWindow->GetActiveView() : nullptr;
|
|
const lcStep CurrentStep = mCurrentStep;
|
|
|
|
lcView View(lcViewType::View, this);
|
|
|
|
if (Background == lcImageBackground::Transparent)
|
|
View.SetBackgroundColorOverride(LC_RGBA(0, 0, 0, 0));
|
|
|
|
if (OutputCamera)
|
|
View.SetCamera(OutputCamera, true);
|
|
else if (ActiveView)
|
|
View.SetCamera(ActiveView->GetCamera(), true);
|
|
|
|
View.SetOffscreenContext();
|
|
View.MakeCurrent();
|
|
View.SetSize(Width, Height);
|
|
|
|
if (!OutputCamera && !ActiveView)
|
|
{
|
|
SetTemporaryStep(GetLastStep());
|
|
View.ZoomExtents(lcGeometryBoundsMode::AvailableOnly, true);
|
|
}
|
|
|
|
SetTemporaryStep(Step);
|
|
|
|
if (Zoom && ActiveView && !OutputCamera)
|
|
{
|
|
View.ZoomExtents(lcGeometryBoundsMode::AvailableOnly, true);
|
|
}
|
|
|
|
if (!View.BeginRenderToImage(Width, Height))
|
|
{
|
|
SetTemporaryStep(CurrentStep);
|
|
|
|
if (!mActive)
|
|
CalculateStep(LC_STEP_MAX);
|
|
|
|
return lcUnexpected(tr("Error creating images."));
|
|
}
|
|
|
|
View.OnDraw();
|
|
|
|
QImage Image = View.GetRenderImage();
|
|
|
|
View.EndRenderToImage();
|
|
|
|
SetTemporaryStep(CurrentStep);
|
|
|
|
if (!mActive)
|
|
CalculateStep(LC_STEP_MAX);
|
|
|
|
if (Background == lcImageBackground::Opaque)
|
|
Image = Image.convertToFormat(QImage::Format_RGB32);
|
|
|
|
if (Image.isNull())
|
|
return lcUnexpected(tr("Error creating images."));
|
|
|
|
return Image;
|
|
}
|
|
|
|
lcResult<QImage> lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundColor, QFont Font, QColor TextColor, bool KeepMissingParts) const
|
|
{
|
|
lcScopedGLContextState GLState;
|
|
|
|
WaitForAssets();
|
|
|
|
lcPartsList PartsList;
|
|
|
|
if (Step == 0)
|
|
GetPartsList(gDefaultColor, true, false, PartsList);
|
|
else
|
|
GetPartsListForStep(Step, gDefaultColor, PartsList, false);
|
|
|
|
if (PartsList.empty())
|
|
return QImage();
|
|
|
|
struct lcPartsListImage
|
|
{
|
|
QImage Thumbnail;
|
|
const lcPieceData* PieceData;
|
|
int ColorIndex;
|
|
int Count;
|
|
QRect Bounds;
|
|
QPoint Position;
|
|
};
|
|
|
|
std::vector<lcPartsListImage> Images;
|
|
|
|
for (const auto& PartIt : PartsList)
|
|
{
|
|
const lcPieceData* PieceData = PartIt.first;
|
|
|
|
if (!KeepMissingParts && !PieceData->IsModel() && !PieceData->IsProject() && (PieceData->mState != lcAssetState::Loaded || !PieceData->GetMesh()))
|
|
continue;
|
|
|
|
for (const auto& ColorIt : PartIt.second)
|
|
{
|
|
Images.push_back(lcPartsListImage());
|
|
lcPartsListImage& Image = Images.back();
|
|
Image.PieceData = PartIt.first;
|
|
Image.ColorIndex = ColorIt.first;
|
|
Image.Count = ColorIt.second;
|
|
}
|
|
}
|
|
|
|
if (Images.empty())
|
|
{
|
|
QImage Image(1, 1, QImage::Format_ARGB32);
|
|
|
|
if (Image.isNull())
|
|
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
|
|
|
Image.fill(lcQColorFromRGBA(BackgroundColor));
|
|
return Image;
|
|
}
|
|
|
|
auto ImageCompare = [](const lcPartsListImage& Image1, const lcPartsListImage& Image2)
|
|
{
|
|
if (Image1.ColorIndex != Image2.ColorIndex)
|
|
return Image1.ColorIndex < Image2.ColorIndex;
|
|
|
|
return strcmp(Image1.PieceData->m_strDescription, Image2.PieceData->m_strDescription) < 0;
|
|
};
|
|
|
|
std::sort(Images.begin(), Images.end(), ImageCompare);
|
|
|
|
lcView View(lcViewType::PartsList, nullptr);
|
|
View.SetOffscreenContext();
|
|
View.MakeCurrent();
|
|
lcContext* Context = View.mContext;
|
|
const int ThumbnailSize = qMin(MaxWidth, 512);
|
|
|
|
View.SetSize(ThumbnailSize, ThumbnailSize);
|
|
|
|
if (!View.BeginRenderToImage(ThumbnailSize, ThumbnailSize))
|
|
return lcUnexpected(tr("Could not create the pieces list framebuffer."));
|
|
|
|
float OrthoSize = 200.0f;
|
|
|
|
lcMatrix44 ProjectionMatrix = lcMatrix44Ortho(-OrthoSize, OrthoSize, -OrthoSize, OrthoSize, -5000.0f, 5000.0f);
|
|
const lcMatrix44 ViewMatrix = lcMatrix44LookAt(lcVector3(-100.0f, -100.0f, 75.0f), lcVector3(0.0f, 0.0f, 0.0f), lcVector3(0.0f, 0.0f, 1.0f));
|
|
const int Viewport[4] = { 0, 0, ThumbnailSize, ThumbnailSize };
|
|
|
|
float ExtraPixels = 0.0f;
|
|
|
|
for (const lcPartsListImage& Image : Images)
|
|
{
|
|
const lcPieceData* PieceData = Image.PieceData;
|
|
const lcBoundingBox& BoundingBox = PieceData->GetBoundingBox();
|
|
|
|
lcVector3 Points[8];
|
|
lcGetBoxCorners(BoundingBox.Min, BoundingBox.Max, Points);
|
|
|
|
for (lcVector3& Point : Points)
|
|
{
|
|
Point = lcProjectPoint(Point, ViewMatrix, ProjectionMatrix, Viewport);
|
|
|
|
ExtraPixels = qMax(ExtraPixels, -Point.x);
|
|
ExtraPixels = qMax(ExtraPixels, Point.x - ThumbnailSize);
|
|
ExtraPixels = qMax(ExtraPixels, -Point.y);
|
|
ExtraPixels = qMax(ExtraPixels, Point.y - ThumbnailSize);
|
|
}
|
|
}
|
|
|
|
if (ExtraPixels)
|
|
{
|
|
OrthoSize += ExtraPixels * (2.0f * OrthoSize / ThumbnailSize);
|
|
ProjectionMatrix = lcMatrix44Ortho(-OrthoSize, OrthoSize, -OrthoSize, OrthoSize, -5000.0f, 5000.0f);
|
|
}
|
|
|
|
Context->SetViewport(0, 0, ThumbnailSize, ThumbnailSize);
|
|
Context->SetDefaultState();
|
|
Context->SetProjectionMatrix(ProjectionMatrix);
|
|
|
|
bool RenderFailed = false;
|
|
|
|
for (lcPartsListImage& Image : Images)
|
|
{
|
|
View.BindRenderFramebuffer();
|
|
Context->ClearColorAndDepth(lcVector4(lcVector3FromColor(BackgroundColor), 0.0f));
|
|
|
|
lcScene Scene;
|
|
|
|
const lcPreferences& Preferences = lcGetPreferences();
|
|
lcShadingMode ShadingMode = Preferences.mShadingMode;
|
|
if (ShadingMode == lcShadingMode::Wireframe)
|
|
ShadingMode = lcShadingMode::Flat;
|
|
|
|
Scene.SetShadingMode(ShadingMode);
|
|
Scene.SetRequireCompleteAssets(true);
|
|
Scene.SetAllowLOD(false);
|
|
Scene.Begin(ViewMatrix);
|
|
|
|
Image.PieceData->AddRenderMeshes(&Scene, lcMatrix44Identity(), Image.ColorIndex, lcRenderMeshState::Default, true);
|
|
|
|
Scene.End();
|
|
|
|
Scene.Draw(Context, {});
|
|
|
|
View.UnbindRenderFramebuffer();
|
|
Image.Thumbnail = View.GetRenderFramebufferImage().convertToFormat(QImage::Format_ARGB32);
|
|
|
|
if (Image.Thumbnail.isNull())
|
|
{
|
|
RenderFailed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
View.EndRenderToImage();
|
|
Context->ClearResources();
|
|
|
|
if (RenderFailed)
|
|
return lcUnexpected(tr("Could not read the pieces list image."));
|
|
|
|
auto CalculateImageBounds = [](lcPartsListImage& Image)
|
|
{
|
|
const QImage& Thumbnail = Image.Thumbnail;
|
|
const int Width = Thumbnail.width();
|
|
const int Height = Thumbnail.height();
|
|
|
|
int MinX = Width;
|
|
int MinY = Height;
|
|
int MaxX = 0;
|
|
int MaxY = 0;
|
|
|
|
for (int x = 0; x < Width; x++)
|
|
{
|
|
for (int y = 0; y < Height; y++)
|
|
{
|
|
if (qAlpha(Thumbnail.pixel(x, y)))
|
|
{
|
|
MinX = qMin(x, MinX);
|
|
MinY = qMin(y, MinY);
|
|
MaxX = qMax(x, MaxX);
|
|
MaxY = qMax(y, MaxY);
|
|
}
|
|
}
|
|
}
|
|
|
|
Image.Bounds = QRect(QPoint(MinX, MinY), QPoint(MaxX, MaxY));
|
|
};
|
|
|
|
QtConcurrent::blockingMap(Images, CalculateImageBounds);
|
|
|
|
if (KeepMissingParts)
|
|
{
|
|
for (lcPartsListImage& Image : Images)
|
|
if (Image.Bounds.isEmpty())
|
|
Image.Bounds = QRect(0, 0, qMin(64, ThumbnailSize), qMin(64, ThumbnailSize));
|
|
}
|
|
else
|
|
{
|
|
Images.erase(std::remove_if(Images.begin(), Images.end(), [](const lcPartsListImage& Image)
|
|
{
|
|
return Image.Bounds.isEmpty();
|
|
}), Images.end());
|
|
}
|
|
|
|
if (Images.empty())
|
|
{
|
|
QImage Image(1, 1, QImage::Format_ARGB32);
|
|
|
|
if (Image.isNull())
|
|
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
|
|
|
Image.fill(lcQColorFromRGBA(BackgroundColor));
|
|
return Image;
|
|
}
|
|
|
|
QImage DummyImage(16, 16, QImage::Format_ARGB32);
|
|
|
|
if (DummyImage.isNull())
|
|
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
|
|
|
QPainter DummyPainter(&DummyImage);
|
|
|
|
DummyPainter.setFont(Font);
|
|
QFontMetrics FontMetrics = DummyPainter.fontMetrics();
|
|
const int Ascent = FontMetrics.ascent();
|
|
|
|
int CurrentHeight = 0;
|
|
int ImageWidth = MaxWidth;
|
|
|
|
for (const lcPartsListImage& Image : Images)
|
|
CurrentHeight = qMax(Image.Bounds.height() + Ascent, CurrentHeight);
|
|
|
|
for (;;)
|
|
{
|
|
int CurrentWidth = 0;
|
|
int CurrentX = 0;
|
|
int CurrentY = 0;
|
|
int ColumnWidth = 0;
|
|
constexpr int Spacing = 20;
|
|
int NextHeightIncrease = INT_MAX;
|
|
|
|
for (lcPartsListImage& Image : Images)
|
|
{
|
|
if (CurrentY + Image.Bounds.height() + Ascent > CurrentHeight)
|
|
{
|
|
const int NeededSpace = Image.Bounds.height() + Ascent - (CurrentHeight - CurrentY);
|
|
NextHeightIncrease = qMin(NeededSpace, NextHeightIncrease);
|
|
|
|
CurrentY = 0;
|
|
CurrentX += ColumnWidth + Spacing;
|
|
ColumnWidth = 0;
|
|
}
|
|
|
|
Image.Position = QPoint(CurrentX, CurrentY);
|
|
CurrentY += Image.Bounds.height() + Ascent + Spacing;
|
|
CurrentWidth = qMax(CurrentWidth, CurrentX + Image.Bounds.width());
|
|
ColumnWidth = qMax(ColumnWidth, Image.Bounds.width());
|
|
}
|
|
|
|
if (CurrentWidth <= MaxWidth)
|
|
{
|
|
ImageWidth = CurrentWidth;
|
|
break;
|
|
}
|
|
|
|
CurrentHeight += NextHeightIncrease;
|
|
}
|
|
|
|
QImage PainterImage(ImageWidth + 40, CurrentHeight + 40, QImage::Format_ARGB32);
|
|
|
|
if (PainterImage.isNull())
|
|
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
|
|
|
PainterImage.fill(lcQColorFromRGBA(BackgroundColor));
|
|
|
|
QPainter Painter(&PainterImage);
|
|
Painter.setFont(Font);
|
|
Painter.setPen(TextColor);
|
|
|
|
for (const lcPartsListImage& Image : Images)
|
|
{
|
|
const QPoint Position = Image.Position + QPoint(20, 20);
|
|
Painter.drawImage(Position, Image.Thumbnail, Image.Bounds);
|
|
Painter.drawText(QPoint(Position.x(), Position.y() + Image.Bounds.height() + Ascent), QString::number(Image.Count) + 'x');
|
|
}
|
|
|
|
Painter.end();
|
|
|
|
return PainterImage;
|
|
}
|
|
|
|
lcResult<void> lcModel::SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Zoom, int Width, int Height, lcStep Start, lcStep End, lcImageBackground Background)
|
|
{
|
|
WaitForAssets();
|
|
|
|
std::unique_ptr<lcView> OutputView;
|
|
lcCamera* Camera = nullptr;
|
|
|
|
if (!gMainWindow || !gMainWindow->GetActiveView())
|
|
{
|
|
const lcStep CurrentStep = mCurrentStep;
|
|
|
|
OutputView = std::make_unique<lcView>(lcViewType::View, this);
|
|
OutputView->SetSize(Width, Height);
|
|
SetTemporaryStep(GetLastStep());
|
|
OutputView->ZoomExtents(lcGeometryBoundsMode::AvailableOnly, true);
|
|
SetTemporaryStep(CurrentStep);
|
|
|
|
if (!mActive)
|
|
CalculateStep(LC_STEP_MAX);
|
|
|
|
Camera = OutputView->GetCamera();
|
|
}
|
|
|
|
for (lcStep Step = Start; Step <= End; Step++)
|
|
{
|
|
QString FileName;
|
|
|
|
if (AddStepSuffix)
|
|
FileName = BaseName.arg(Step, 2, 10, QLatin1Char('0'));
|
|
else
|
|
FileName = BaseName;
|
|
|
|
const lcResult<QImage> Image = RenderStepImageWithReadyAssets(Zoom, Width, Height, Step, Camera, Background);
|
|
|
|
if (!Image)
|
|
return lcUnexpected(Image.error());
|
|
|
|
QImageWriter Writer(FileName);
|
|
|
|
if (Writer.format().isEmpty())
|
|
Writer.setFormat("png");
|
|
|
|
if (!Writer.write(Image.value()))
|
|
return lcUnexpected(tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString()));
|
|
}
|
|
|
|
return lcResult<void>();
|
|
}
|
|
|
|
void lcModel::RayTest(lcObjectRayTest& ObjectRayTest) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if ((Piece->IsSelected() || Piece->IsVisible(mCurrentStep)) && (!ObjectRayTest.IgnoreSelected || !Piece->IsSelected()))
|
|
Piece->RayTest(ObjectRayTest);
|
|
|
|
if (ObjectRayTest.PiecesOnly)
|
|
return;
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (Camera.get() != ObjectRayTest.ViewCamera && Camera->IsVisible() && (!ObjectRayTest.IgnoreSelected || !Camera->IsSelected()))
|
|
Camera->RayTest(ObjectRayTest);
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
if (Light->IsVisible() && (!ObjectRayTest.IgnoreSelected || !Light->IsSelected()))
|
|
Light->RayTest(ObjectRayTest);
|
|
}
|
|
|
|
void lcModel::BoxTest(lcObjectBoxTest& ObjectBoxTest) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected() || Piece->IsVisible(mCurrentStep))
|
|
Piece->BoxTest(ObjectBoxTest);
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (Camera.get() != ObjectBoxTest.ViewCamera && Camera->IsVisible())
|
|
Camera->BoxTest(ObjectBoxTest);
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
if (Light->IsVisible())
|
|
Light->BoxTest(ObjectBoxTest);
|
|
}
|
|
|
|
bool lcModel::SubModelMinIntersectDist(const lcVector3& WorldStart, const lcVector3& WorldEnd, float& MinDistance, lcPieceRayTest& PieceRayTest) const
|
|
{
|
|
bool MinIntersect = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
const lcMatrix44 InverseWorldMatrix = lcMatrix44AffineInverse(Piece->mModelWorld);
|
|
const lcVector3 Start = lcMul31(WorldStart, InverseWorldMatrix);
|
|
const lcVector3 End = lcMul31(WorldEnd, InverseWorldMatrix);
|
|
|
|
if (Piece->IsVisibleInSubModel())
|
|
{
|
|
if (Piece->MinIntersectDist(Start, End, MinDistance, PieceRayTest))
|
|
{
|
|
MinIntersect = true;
|
|
PieceRayTest.Transform = lcMul(PieceRayTest.Transform, Piece->mModelWorld);
|
|
}
|
|
}
|
|
}
|
|
|
|
return MinIntersect;
|
|
}
|
|
|
|
bool lcModel::SubModelBoxTest(const lcVector4 Planes[6]) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsVisibleInSubModel() && Piece->IntersectsPlanes(Piece->mModelWorld, Planes))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void lcModel::SubModelCompareBoundingBox(const lcMatrix44& WorldMatrix, lcVector3& Min, lcVector3& Max) const
|
|
{
|
|
const lcMesh* Mesh = mPieceData ? mPieceData->GetDisplayMesh() : nullptr;
|
|
|
|
if (Mesh)
|
|
{
|
|
lcVector3 Box[8];
|
|
lcGetBoxCorners(Mesh->mBoundingBox, Box);
|
|
|
|
for (const lcVector3& Corner : Box)
|
|
{
|
|
const lcVector3 Point = lcMul31(Corner, WorldMatrix);
|
|
Min = lcMin(Min, Point);
|
|
Max = lcMax(Max, Point);
|
|
}
|
|
}
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsVisibleInSubModel())
|
|
Piece->SubModelCompareBoundingBox(WorldMatrix, Min, Max);
|
|
}
|
|
|
|
void lcModel::SubModelAddBoundingBoxPoints(const lcMatrix44& WorldMatrix, std::vector<lcVector3>& Points, lcGeometryBoundsMode Mode) const
|
|
{
|
|
const lcMesh* Mesh = mPieceData ? mPieceData->GetDisplayMesh() : nullptr;
|
|
|
|
if (Mode == lcGeometryBoundsMode::AvailableOnly && mPieceData && mPieceData->mState != lcAssetState::Loaded)
|
|
Mesh = nullptr;
|
|
|
|
if (Mesh)
|
|
{
|
|
lcVector3 Box[8];
|
|
lcGetBoxCorners(Mesh->mBoundingBox, Box);
|
|
|
|
for (const lcVector3& Corner : Box)
|
|
Points.emplace_back(lcMul31(Corner, WorldMatrix));
|
|
}
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsVisibleInSubModel())
|
|
Piece->SubModelAddBoundingBoxPoints(WorldMatrix, Points, Mode);
|
|
}
|
|
|
|
void lcModel::AddSelectionHistory(std::function<void()> Callback)
|
|
{
|
|
std::unique_ptr<lcModelHistorySelect> ModelHistorySelect = std::make_unique<lcModelHistorySelect>();
|
|
|
|
ModelHistorySelect->SaveStartState(this);
|
|
|
|
Callback();
|
|
|
|
ModelHistorySelect->SaveEndState(this);
|
|
|
|
if (ModelHistorySelect->StateChanged())
|
|
mHistorySequence.emplace_back(std::move(ModelHistorySelect));
|
|
}
|
|
|
|
void lcModel::ClearSelection()
|
|
{
|
|
AddSelectionHistory([this](){ DeselectAllObjects(); });
|
|
}
|
|
|
|
void lcModel::SetFocus(lcObject* FocusObject, uint32_t FocusSection, lcSelectionMode SelectionMode)
|
|
{
|
|
AddSelectionHistory([this, FocusObject, FocusSection, SelectionMode](){ SetFocusedObject(FocusObject, FocusSection, SelectionMode); });
|
|
}
|
|
|
|
void lcModel::SetSelectionAndFocus(const std::vector<lcObject*>& Objects, lcObject* FocusObject, uint32_t FocusSection, lcSelectionMode SelectionMode)
|
|
{
|
|
AddSelectionHistory([this, &Objects, FocusObject, FocusSection, SelectionMode]()
|
|
{
|
|
DeselectAllObjects();
|
|
SetObjectsSelected(Objects, true);
|
|
SetFocusedObject(FocusObject, FocusSection, SelectionMode);
|
|
});
|
|
}
|
|
|
|
void lcModel::SelectAllPieces()
|
|
{
|
|
AddSelectionHistory([this]()
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsVisible(mCurrentStep))
|
|
Piece->SetSelected(true);
|
|
});
|
|
}
|
|
|
|
void lcModel::InvertPieceSelection()
|
|
{
|
|
AddSelectionHistory([this]()
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected() || Piece->IsVisible(mCurrentStep))
|
|
Piece->SetSelected(!Piece->IsSelected());
|
|
});
|
|
}
|
|
|
|
void lcModel::AddToSelection(const std::vector<lcObject*>& Objects)
|
|
{
|
|
AddSelectionHistory([this, &Objects](){ SetObjectsSelected(Objects, true); });
|
|
}
|
|
|
|
void lcModel::RemoveFromSelection(const std::vector<lcObject*>& Objects)
|
|
{
|
|
AddSelectionHistory([this, &Objects](){ SetObjectsSelected(Objects, false); });
|
|
}
|
|
|
|
void lcModel::LoadEditHistoryState(const lcModelHistoryEditState& HistoryState)
|
|
{
|
|
const std::unordered_map<uint32_t, lcGroup*> GroupsById = RestoreGroups(HistoryState.Groups);
|
|
RestorePieces(HistoryState.Pieces, GroupsById);
|
|
RestoreCameras(HistoryState.Cameras);
|
|
RestoreLights(HistoryState.Lights);
|
|
}
|
|
|
|
std::unordered_map<uint32_t, lcGroup*> lcModel::RestoreGroups(const lcObjectHistory<lcGroupHistoryState>& History)
|
|
{
|
|
const std::unordered_map<uint32_t, lcGroup*> GroupsById = History.RestoreObjects(mGroups);
|
|
|
|
// The lookup uses saved IDs, so a parent can be linked before its state is restored.
|
|
for (const lcGroupHistoryState& State : History.States)
|
|
{
|
|
const auto Group = GroupsById.find(static_cast<uint32_t>(State.Id));
|
|
const auto Parent = GroupsById.find(State.ParentId);
|
|
|
|
if (Group != GroupsById.end())
|
|
Group->second->SetHistoryState(State, Parent != GroupsById.end() ? Parent->second : nullptr);
|
|
}
|
|
|
|
return GroupsById;
|
|
}
|
|
|
|
void lcModel::RestorePieces(const lcObjectHistory<lcPieceHistoryState>& History, const std::unordered_map<uint32_t, lcGroup*>& GroupsById)
|
|
{
|
|
const std::unordered_map<uint32_t, lcPiece*> PiecesById = History.RestoreObjects(mPieces);
|
|
|
|
for (const lcPieceHistoryState& State : History.States)
|
|
{
|
|
const auto Piece = PiecesById.find(static_cast<uint32_t>(State.Id));
|
|
const auto Group = GroupsById.find(State.GroupId);
|
|
|
|
if (Piece != PiecesById.end())
|
|
Piece->second->SetHistoryState(State, mProject, Group != GroupsById.end() ? Group->second : nullptr);
|
|
}
|
|
|
|
// Model-list changes are not undoable, but older edit states can still name
|
|
// a submodel that was replaced. Do not restore references that now recurse.
|
|
mPieces.erase(std::remove_if(mPieces.begin(), mPieces.end(), [this](const std::unique_ptr<lcPiece>& Piece)
|
|
{
|
|
return Piece->mPieceData && Piece->mPieceData->IncludesModel(this);
|
|
}), mPieces.end());
|
|
}
|
|
|
|
void lcModel::RestoreCameras(const lcObjectHistory<lcCameraHistoryState>& History)
|
|
{
|
|
if (History.IsEmpty())
|
|
return;
|
|
|
|
if (History.Order)
|
|
{
|
|
const std::unordered_set<uint32_t> CameraIds(History.Order->begin(), History.Order->end());
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (CameraIds.find(static_cast<uint32_t>(Camera->GetId())) == CameraIds.end())
|
|
RemoveCameraFromViews(Camera.get());
|
|
}
|
|
|
|
const std::unordered_map<uint32_t, lcCamera*> CamerasById = History.RestoreObjects(mCameras);
|
|
|
|
for (const lcCameraHistoryState& State : History.States)
|
|
{
|
|
const auto Camera = CamerasById.find(static_cast<uint32_t>(State.Id));
|
|
|
|
if (Camera != CamerasById.end())
|
|
Camera->second->SetHistoryState(State);
|
|
}
|
|
}
|
|
|
|
void lcModel::RestoreLights(const lcObjectHistory<lcLightHistoryState>& History)
|
|
{
|
|
if (History.IsEmpty())
|
|
return;
|
|
|
|
const std::unordered_map<uint32_t, lcLight*> LightsById = History.RestoreObjects(mLights);
|
|
|
|
for (const lcLightHistoryState& State : History.States)
|
|
{
|
|
const auto Light = LightsById.find(static_cast<uint32_t>(State.Id));
|
|
|
|
if (Light != LightsById.end())
|
|
Light->second->SetHistoryState(State);
|
|
}
|
|
}
|
|
|
|
void lcModel::BeginEditHistory()
|
|
{
|
|
BeginMergeableEditHistory(lcModelHistoryEditMerge::None, lcObjectPropertyId::Count);
|
|
}
|
|
|
|
void lcModel::BeginMergeableEditHistory(lcModelHistoryEditMerge ModelHistoryEditMerge, lcObjectPropertyId PropertyId)
|
|
{
|
|
const lcModelHistoryEditMergeKey MergeKey = { ModelHistoryEditMerge, PropertyId, mCurrentStep, gMainWindow ? gMainWindow->GetAddKeys() : false };
|
|
std::unique_ptr<lcModelHistoryEdit> ModelHistoryEdit = std::make_unique<lcModelHistoryEdit>(MergeKey);
|
|
|
|
ModelHistoryEdit->SaveStartState(this);
|
|
|
|
mHistorySequence.emplace_back(std::move(ModelHistoryEdit));
|
|
}
|
|
|
|
void lcModel::EndEditHistory()
|
|
{
|
|
if (mHistorySequence.empty())
|
|
return;
|
|
|
|
lcModelHistoryEdit* ModelHistoryEdit = dynamic_cast<lcModelHistoryEdit*>(mHistorySequence.back().get());
|
|
|
|
if (!ModelHistoryEdit)
|
|
return;
|
|
|
|
ModelHistoryEdit->SaveEndState(this);
|
|
|
|
if (!ModelHistoryEdit->StateChanged())
|
|
mHistorySequence.pop_back();
|
|
}
|
|
|
|
void lcModel::LoadModelPropertiesState(const lcModelProperties& ModelProperties)
|
|
{
|
|
mProperties = ModelProperties;
|
|
|
|
if (gMainWindow)
|
|
gMainWindow->GetPreviewWidget()->UpdatePreview();
|
|
}
|
|
|
|
void lcModel::SetModelProperties(const lcModelProperties& ModelProperties)
|
|
{
|
|
std::unique_ptr<lcModelHistoryProperties> ModelHistoryProperties = std::make_unique<lcModelHistoryProperties>();
|
|
ModelHistoryProperties->SaveStartState(this);
|
|
|
|
LoadModelPropertiesState(ModelProperties);
|
|
|
|
ModelHistoryProperties->SaveEndState(this);
|
|
|
|
if (ModelHistoryProperties->StateChanged())
|
|
mHistorySequence.emplace_back(std::move(ModelHistoryProperties));
|
|
}
|
|
|
|
void lcModel::RunHistorySequence(const std::vector<std::unique_ptr<lcModelHistory>>& HistorySequence, bool Apply)
|
|
{
|
|
bool SelectionChanged = false;
|
|
bool EditChanged = false;
|
|
|
|
auto RunAction=[this, &SelectionChanged, &EditChanged](const lcModelHistory* ModelHistory, bool Apply)
|
|
{
|
|
if (!ModelHistory)
|
|
return;
|
|
|
|
if (Apply)
|
|
ModelHistory->LoadEndState(this);
|
|
else
|
|
ModelHistory->LoadStartState(this);
|
|
|
|
if (dynamic_cast<const lcModelHistorySelect*>(ModelHistory))
|
|
{
|
|
SelectionChanged = true;
|
|
}
|
|
else if (dynamic_cast<const lcModelHistoryEdit*>(ModelHistory))
|
|
{
|
|
EditChanged = true;
|
|
SetCurrentStep(mCurrentStep);
|
|
}
|
|
else if (dynamic_cast<const lcModelHistoryProperties*>(ModelHistory))
|
|
{
|
|
}
|
|
};
|
|
|
|
if (Apply)
|
|
{
|
|
for (auto ModelHistory = HistorySequence.begin(); ModelHistory != HistorySequence.end(); ++ModelHistory)
|
|
RunAction(ModelHistory->get(), true);
|
|
}
|
|
else
|
|
{
|
|
for (auto ModelHistory = HistorySequence.rbegin(); ModelHistory != HistorySequence.rend(); ++ModelHistory)
|
|
RunAction(ModelHistory->get(), false);
|
|
}
|
|
|
|
if (EditChanged)
|
|
gMainWindow->UpdateInUseCategory();
|
|
|
|
if (SelectionChanged)
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
|
|
gMainWindow->UpdateCurrentStep();
|
|
gMainWindow->UpdateTimeline(true, false);
|
|
|
|
UpdateAllViews();
|
|
if (EditChanged)
|
|
RefreshPreview();
|
|
}
|
|
|
|
void lcModel::BeginHistorySequence()
|
|
{
|
|
mHistorySequence.clear();
|
|
}
|
|
|
|
void lcModel::EndHistorySequence(const QString& Description)
|
|
{
|
|
if (mHistorySequence.empty())
|
|
return;
|
|
|
|
if (mIsPreview)
|
|
{
|
|
mHistorySequence.clear();
|
|
|
|
return;
|
|
}
|
|
|
|
bool EditChanged = false;
|
|
|
|
for (const std::unique_ptr<lcModelHistory>& ModelHistory : mHistorySequence)
|
|
{
|
|
if (dynamic_cast<const lcModelHistoryEdit*>(ModelHistory.get()))
|
|
{
|
|
EditChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool CanMerge = false;
|
|
|
|
if (mHistorySequence.size() == 1 && !mUndoHistory.empty() && mUndoHistory.front()->Revision != mSavedHistoryRevision && mUndoHistory.front()->HistorySequence.size() == 1)
|
|
CanMerge = mHistorySequence.front()->CanMergeWith(mUndoHistory.front()->HistorySequence.front().get());
|
|
|
|
if (!CanMerge)
|
|
{
|
|
std::unique_ptr<lcModelHistoryEntry> ModelHistoryEntry = std::make_unique<lcModelHistoryEntry>(lcModelHistoryEntry());
|
|
|
|
ModelHistoryEntry->Description = Description;
|
|
ModelHistoryEntry->Revision = mNextHistoryRevision++;
|
|
ModelHistoryEntry->HistorySequence = std::move(mHistorySequence);
|
|
|
|
mUndoHistory.insert(mUndoHistory.begin(), std::move(ModelHistoryEntry));
|
|
}
|
|
else
|
|
{
|
|
lcModelHistoryEdit* ModelHistoryEdit = dynamic_cast<lcModelHistoryEdit*>(mHistorySequence.front().get());
|
|
lcModelHistoryEntry* LastHistoryEntry = mUndoHistory.front().get();
|
|
lcModelHistoryEdit* LastModelHistoryEdit = dynamic_cast<lcModelHistoryEdit*>(LastHistoryEntry->HistorySequence.front().get());
|
|
|
|
LastModelHistoryEdit->MergeWith(ModelHistoryEdit);
|
|
|
|
mHistorySequence.clear();
|
|
}
|
|
|
|
mRedoHistory.clear();
|
|
|
|
gMainWindow->UpdateModified(mProject ? mProject->IsModified() : IsModified());
|
|
gMainWindow->UpdateUndoRedo(!mUndoHistory.empty() ? mUndoHistory.front()->Description : nullptr, !mRedoHistory.empty() ? mRedoHistory.front()->Description : nullptr);
|
|
gMainWindow->UpdateTimeline(true, false);
|
|
|
|
for (const std::unique_ptr<lcModelHistory>& ModelHistory : mUndoHistory.front()->HistorySequence)
|
|
{
|
|
if (dynamic_cast<const lcModelHistorySelect*>(ModelHistory.get()))
|
|
{
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateAllViews();
|
|
if (EditChanged)
|
|
RefreshPreview();
|
|
}
|
|
|
|
void lcModel::DiscardHistorySequence()
|
|
{
|
|
mHistorySequence.clear();
|
|
}
|
|
|
|
void lcModel::RevertHistorySequence()
|
|
{
|
|
RunHistorySequence(mHistorySequence, false);
|
|
|
|
mHistorySequence.clear();
|
|
}
|
|
|
|
bool lcModel::IsModified() const
|
|
{
|
|
return GetCurrentHistoryRevision() != mSavedHistoryRevision;
|
|
}
|
|
|
|
void lcModel::SetSaved()
|
|
{
|
|
mSavedHistoryRevision = GetCurrentHistoryRevision();
|
|
}
|
|
|
|
void lcModel::RemoveFirstUndoIfUnchanged()
|
|
{
|
|
if (mUndoHistory.empty())
|
|
return;
|
|
|
|
for (const std::unique_ptr<lcModelHistory>& ModelHistory : mUndoHistory.front()->HistorySequence)
|
|
if (ModelHistory->StateChanged())
|
|
return;
|
|
|
|
mUndoHistory.erase(mUndoHistory.begin());
|
|
|
|
gMainWindow->UpdateModified(mProject ? mProject->IsModified() : IsModified());
|
|
gMainWindow->UpdateUndoRedo(!mUndoHistory.empty() ? mUndoHistory.front()->Description : nullptr, !mRedoHistory.empty() ? mRedoHistory.front()->Description : nullptr);
|
|
}
|
|
|
|
quint64 lcModel::GetCurrentHistoryRevision() const
|
|
{
|
|
for (const std::unique_ptr<lcModelHistoryEntry>& UndoEntry : mUndoHistory)
|
|
if (UndoEntry->HistorySequence.size() != 1 || !dynamic_cast<lcModelHistorySelect*>(UndoEntry->HistorySequence.front().get()))
|
|
return UndoEntry->Revision;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void lcModel::SetActive(bool Active)
|
|
{
|
|
CalculateStep(Active ? mCurrentStep : LC_STEP_MAX);
|
|
mActive = Active;
|
|
}
|
|
|
|
void lcModel::CalculateStep(lcStep Step)
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
Piece->UpdatePosition(Step);
|
|
|
|
for (std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
Camera->UpdatePosition(Step);
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
Light->UpdatePosition(Step);
|
|
}
|
|
|
|
void lcModel::SetCurrentStep(lcStep Step)
|
|
{
|
|
mCurrentStep = Step;
|
|
CalculateStep(Step);
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
UpdateAllViews();
|
|
gMainWindow->UpdateCurrentStep();
|
|
}
|
|
|
|
void lcModel::ShowFirstStep()
|
|
{
|
|
if (mCurrentStep == 1)
|
|
return;
|
|
|
|
SetCurrentStep(1);
|
|
}
|
|
|
|
void lcModel::ShowLastStep()
|
|
{
|
|
const lcStep LastStep = GetLastStep();
|
|
|
|
if (mCurrentStep == LastStep)
|
|
return;
|
|
|
|
SetCurrentStep(LastStep);
|
|
}
|
|
|
|
void lcModel::ShowPreviousStep()
|
|
{
|
|
if (mCurrentStep == 1)
|
|
return;
|
|
|
|
SetCurrentStep(mCurrentStep - 1);
|
|
}
|
|
|
|
void lcModel::ShowNextStep()
|
|
{
|
|
if (mCurrentStep == LC_STEP_MAX)
|
|
return;
|
|
|
|
SetCurrentStep(mCurrentStep + 1);
|
|
}
|
|
|
|
lcStep lcModel::GetLastStep() const
|
|
{
|
|
lcStep Step = 1;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
Step = lcMax(Step, Piece->GetStepShow());
|
|
|
|
return Step;
|
|
}
|
|
|
|
void lcModel::InsertStep(lcStep Step)
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
Piece->InsertTime(Step, 1);
|
|
|
|
for (std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
Camera->InsertTime(Step, 1);
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
Light->InsertTime(Step, 1);
|
|
}
|
|
|
|
void lcModel::InsertStepAction(lcStep Step)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
InsertStep(Step);
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Insert Step"));
|
|
|
|
SetCurrentStep(mCurrentStep);
|
|
}
|
|
|
|
void lcModel::RemoveStepAction(lcStep Step)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
Piece->RemoveTime(Step, 1);
|
|
|
|
for (std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
Camera->RemoveTime(Step, 1);
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
Light->RemoveTime(Step, 1);
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Remove Step"));
|
|
|
|
SetCurrentStep(mCurrentStep);
|
|
}
|
|
|
|
lcGroup* lcModel::AddGroup(const QString& Prefix, lcGroup* Parent)
|
|
{
|
|
lcGroup* Group = new lcGroup();
|
|
mGroups.emplace_back(Group);
|
|
|
|
Group->mName = GetGroupName(Prefix);
|
|
Group->mGroup = Parent;
|
|
|
|
return Group;
|
|
}
|
|
|
|
lcGroup* lcModel::GetGroup(const QString& Name, bool CreateIfMissing)
|
|
{
|
|
for (const std::unique_ptr<lcGroup>& Group : mGroups)
|
|
if (Group->mName == Name)
|
|
return Group.get();
|
|
|
|
if (CreateIfMissing)
|
|
{
|
|
lcGroup* Group = new lcGroup();
|
|
Group->mName = Name;
|
|
mGroups.emplace_back(Group);
|
|
|
|
return Group;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void lcModel::RemoveGroup(lcGroup* Group)
|
|
{
|
|
for (std::vector<std::unique_ptr<lcGroup>>::iterator GroupIt = mGroups.begin(); GroupIt != mGroups.end(); GroupIt++)
|
|
{
|
|
if (GroupIt->get() == Group)
|
|
{
|
|
mGroups.erase(GroupIt);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void lcModel::GroupSelection()
|
|
{
|
|
if (!AnyPiecesSelected())
|
|
{
|
|
QMessageBox::information(gMainWindow, tr("Group Selection"), tr("No pieces selected."));
|
|
return;
|
|
}
|
|
|
|
lcGroupDialog Dialog(gMainWindow, GetGroupName(tr("Group #")), mGroups);
|
|
|
|
if (Dialog.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
lcGroup* NewGroup = GetGroup(Dialog.mName, true);
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected())
|
|
{
|
|
lcGroup* Group = Piece->GetTopGroup();
|
|
|
|
if (!Group)
|
|
Piece->SetGroup(NewGroup);
|
|
else if (Group != NewGroup)
|
|
Group->mGroup = NewGroup;
|
|
}
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Group"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::UngroupSelection()
|
|
{
|
|
if (!AnyPiecesSelected())
|
|
{
|
|
QMessageBox::information(gMainWindow, tr("Ungroup Selection"), tr("No pieces selected."));
|
|
return;
|
|
}
|
|
|
|
std::set<lcGroup*> SelectedGroups;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected())
|
|
{
|
|
lcGroup* Group = Piece->GetTopGroup();
|
|
|
|
if (SelectedGroups.insert(Group).second)
|
|
{
|
|
for (std::vector<std::unique_ptr<lcGroup>>::iterator GroupIt = mGroups.begin(); GroupIt != mGroups.end(); GroupIt++)
|
|
{
|
|
if (GroupIt->get() == Group)
|
|
{
|
|
GroupIt->release();
|
|
mGroups.erase(GroupIt);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SelectedGroups.empty())
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
QMessageBox::information(gMainWindow, tr("Ungroup Selection"), tr("No groups selected."));
|
|
|
|
return;
|
|
}
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
lcGroup* Group = Piece->GetGroup();
|
|
|
|
if (SelectedGroups.find(Group) != SelectedGroups.end())
|
|
Piece->SetGroup(nullptr);
|
|
}
|
|
|
|
for (const std::unique_ptr<lcGroup>& Group : mGroups)
|
|
if (SelectedGroups.find(Group->mGroup) != SelectedGroups.end())
|
|
Group->mGroup = nullptr;
|
|
|
|
for (lcGroup* Group : SelectedGroups)
|
|
delete Group;
|
|
|
|
RemoveEmptyGroups();
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Ungroup"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::AddSelectedPiecesToGroup()
|
|
{
|
|
lcGroup* Group = nullptr;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected())
|
|
{
|
|
Group = Piece->GetTopGroup();
|
|
if (Group)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!Group)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsFocused())
|
|
{
|
|
Piece->SetGroup(Group);
|
|
break;
|
|
}
|
|
}
|
|
|
|
RemoveEmptyGroups();
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Group"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::RemoveFocusPieceFromGroup()
|
|
{
|
|
bool Modified = false;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsFocused())
|
|
{
|
|
Piece->SetGroup(nullptr);
|
|
|
|
Modified = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
RemoveEmptyGroups();
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Ungroup"));
|
|
}
|
|
|
|
void lcModel::ShowEditGroupsDialog()
|
|
{
|
|
lcEditGroupsDialog Dialog(gMainWindow, this);
|
|
|
|
if (Dialog.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
std::function<void(const lcEditGroupsDialog::GroupInfo&, lcGroup*)> UpdateGroups=[this, &UpdateGroups](const lcEditGroupsDialog::GroupInfo& GroupInfo, lcGroup* ParentGroup)
|
|
{
|
|
lcGroup* Group = GroupInfo.Group;
|
|
|
|
if (!Group)
|
|
{
|
|
Group = new lcGroup();
|
|
mGroups.emplace_back(Group);
|
|
}
|
|
|
|
Group->mName = GroupInfo.Name;
|
|
Group->mGroup = ParentGroup;
|
|
|
|
for (const lcEditGroupsDialog::GroupInfo& ChildGroupInfo : GroupInfo.ChildGroups)
|
|
UpdateGroups(ChildGroupInfo, Group);
|
|
|
|
for (lcPiece* Piece : GroupInfo.ChildPieces)
|
|
Piece->SetGroup(Group);
|
|
};
|
|
|
|
lcEditGroupsDialog::GroupInfo GroupInfo = Dialog.GetGroups();
|
|
|
|
for (const lcEditGroupsDialog::GroupInfo& ChildGroupInfo : GroupInfo.ChildGroups)
|
|
UpdateGroups(ChildGroupInfo, nullptr);
|
|
|
|
for (lcPiece* Piece : GroupInfo.ChildPieces)
|
|
Piece->SetGroup(nullptr);
|
|
|
|
RemoveEmptyGroups();
|
|
|
|
EndEditHistory();
|
|
|
|
if (mHistorySequence.empty())
|
|
{
|
|
DiscardHistorySequence();
|
|
return;
|
|
}
|
|
|
|
ClearSelection();
|
|
|
|
EndHistorySequence(tr("Edit Groups"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
QString lcModel::GetGroupName(const QString& Prefix)
|
|
{
|
|
const qsizetype Length = Prefix.length();
|
|
int Max = 0;
|
|
|
|
for (const std::unique_ptr<lcGroup>& Group : mGroups)
|
|
{
|
|
const QString& Name = Group->mName;
|
|
|
|
if (Name.startsWith(Prefix))
|
|
{
|
|
bool Ok = false;
|
|
int GroupNumber = Name.mid(Length).toInt(&Ok);
|
|
if (Ok && GroupNumber > Max)
|
|
Max = GroupNumber;
|
|
}
|
|
}
|
|
|
|
return Prefix + QString::number(Max + 1);
|
|
}
|
|
|
|
void lcModel::RemoveEmptyGroups()
|
|
{
|
|
bool Removed;
|
|
|
|
do
|
|
{
|
|
Removed = false;
|
|
|
|
for (std::vector<std::unique_ptr<lcGroup>>::iterator GroupIt = mGroups.begin(); GroupIt != mGroups.end();)
|
|
{
|
|
lcGroup* Group = GroupIt->get();
|
|
int Ref = 0;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->GetGroup() == Group)
|
|
Ref++;
|
|
|
|
for (size_t ParentIdx = 0; ParentIdx < mGroups.size(); ParentIdx++)
|
|
if (mGroups[ParentIdx]->mGroup == Group)
|
|
Ref++;
|
|
|
|
if (Ref > 1)
|
|
{
|
|
GroupIt++;
|
|
continue;
|
|
}
|
|
|
|
if (Ref != 0)
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->GetGroup() == Group)
|
|
{
|
|
Piece->SetGroup(Group->mGroup);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (size_t ParentIdx = 0; ParentIdx < mGroups.size(); ParentIdx++)
|
|
{
|
|
if (mGroups[ParentIdx]->mGroup == Group)
|
|
{
|
|
mGroups[ParentIdx]->mGroup = Group->mGroup;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
GroupIt = mGroups.erase(GroupIt);
|
|
Removed = true;
|
|
}
|
|
}
|
|
while (Removed);
|
|
}
|
|
|
|
lcVector3 lcModel::SnapPosition(const lcVector3& Distance) const
|
|
{
|
|
lcVector3 NewDistance(Distance);
|
|
|
|
float SnapXY = gMainWindow->GetMoveXYSnap();
|
|
if (SnapXY != 0.0f)
|
|
{
|
|
int i = (int)(NewDistance[0] / SnapXY);
|
|
float Leftover = NewDistance[0] - (SnapXY * i);
|
|
|
|
if (Leftover > SnapXY / 2)
|
|
i++;
|
|
else if (Leftover < -SnapXY / 2)
|
|
i--;
|
|
|
|
NewDistance[0] = SnapXY * i;
|
|
|
|
i = (int)(NewDistance[1] / SnapXY);
|
|
Leftover = NewDistance[1] - (SnapXY * i);
|
|
|
|
if (Leftover > SnapXY / 2)
|
|
i++;
|
|
else if (Leftover < -SnapXY / 2)
|
|
i--;
|
|
|
|
NewDistance[1] = SnapXY * i;
|
|
}
|
|
|
|
float SnapZ = gMainWindow->GetMoveZSnap();
|
|
if (SnapZ != 0.0f)
|
|
{
|
|
int i = (int)(NewDistance[2] / SnapZ);
|
|
const float Leftover = NewDistance[2] - (SnapZ * i);
|
|
|
|
if (Leftover > SnapZ / 2)
|
|
i++;
|
|
else if (Leftover < -SnapZ / 2)
|
|
i--;
|
|
|
|
NewDistance[2] = SnapZ * i;
|
|
}
|
|
|
|
return NewDistance;
|
|
}
|
|
|
|
lcVector3 lcModel::SnapRotation(const lcVector3& Angles) const
|
|
{
|
|
const float AngleSnap = gMainWindow->GetAngleSnap();
|
|
lcVector3 NewAngles(Angles);
|
|
|
|
if (AngleSnap != 0.0f)
|
|
{
|
|
int Snap[3];
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
Snap[i] = (int)(Angles[i] / AngleSnap);
|
|
|
|
NewAngles = lcVector3((float)(AngleSnap * Snap[0]), (float)(AngleSnap * Snap[1]), (float)(AngleSnap * Snap[2]));
|
|
}
|
|
|
|
return NewAngles;
|
|
}
|
|
|
|
lcMatrix33 lcModel::GetRelativeRotation() const
|
|
{
|
|
if (gMainWindow->GetRelativeTransform())
|
|
{
|
|
const lcObject* Focus = GetFocusObject();
|
|
|
|
if (Focus)
|
|
{
|
|
if (Focus->IsPiece())
|
|
return ((lcPiece*)Focus)->GetRelativeRotation();
|
|
|
|
if (Focus->IsLight())
|
|
return ((lcLight*)Focus)->GetRelativeRotation();
|
|
}
|
|
}
|
|
|
|
return lcMatrix33Identity();
|
|
}
|
|
|
|
lcPiece* lcModel::AddPiece(lcPieceData* PieceData, quint32 Section)
|
|
{
|
|
if (!PieceData)
|
|
PieceData = gMainWindow->GetInsertPieceData().Get();
|
|
|
|
if (!PieceData)
|
|
return nullptr;
|
|
|
|
const lcPieceDataRef PieceDataRef(PieceData);
|
|
|
|
lcPiece* Last = mPieces.empty() ? nullptr : mPieces.back().get();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsFocused())
|
|
{
|
|
Last = Piece.get();
|
|
break;
|
|
}
|
|
}
|
|
|
|
lcGetPiecesLibrary()->EnsurePieceDataAssetsReady(PieceDataRef);
|
|
|
|
if (Last)
|
|
{
|
|
lcGetPiecesLibrary()->EnsurePieceDataAssetsReady(Last->mPieceData);
|
|
|
|
if (Last->mPieceData->GetSynthInfo())
|
|
lcGetPiecesLibrary()->WaitForSynthMeshes({ Last });
|
|
}
|
|
|
|
const lcBoundingBox& PieceDataBoundingBox = PieceData->GetBoundingBox();
|
|
lcPiece* Piece = nullptr;
|
|
|
|
auto CreatePiece=[this, &Piece](lcPieceData* PieceData, const lcMatrix44& WorldMatrix, int ColorIndex)
|
|
{
|
|
Piece = new lcPiece(PieceData);
|
|
Piece->Initialize(WorldMatrix, mCurrentStep);
|
|
Piece->SetColorIndex(ColorIndex);
|
|
AddPiece(Piece);
|
|
};
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
if (Last)
|
|
{
|
|
std::vector<lcInsertPieceInfo> TrainTracks;
|
|
|
|
if (PieceData->GetTrainTrackInfo() && Last->mPieceData->GetTrainTrackInfo())
|
|
{
|
|
quint32 FocusSection = Last->GetFocusSection();
|
|
|
|
if (FocusSection != LC_PIECE_SECTION_INVALID && FocusSection >= LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST)
|
|
Section = FocusSection;
|
|
|
|
if (!Last->IsFocused())
|
|
UpdateTrainTrackConnections(Last, false);
|
|
|
|
TrainTracks = lcTrainTrackInfo::GetInsertPieceInfo(Last, PieceData, nullptr, gMainWindow->mColorIndex, Section, true, std::nullopt);
|
|
|
|
for (const lcInsertPieceInfo& TrainTrack : TrainTracks)
|
|
CreatePiece(TrainTrack.PieceData, TrainTrack.Transform, TrainTrack.ColorIndex);
|
|
}
|
|
|
|
if (TrainTracks.empty())
|
|
{
|
|
const lcBoundingBox& LastBoundingBox = Last->GetBoundingBox();
|
|
lcVector3 Dist(0, 0, LastBoundingBox.Max.z - PieceDataBoundingBox.Min.z);
|
|
Dist = SnapPosition(Dist);
|
|
|
|
lcMatrix44 WorldMatrix = Last->mModelWorld;
|
|
WorldMatrix.SetTranslation(lcMul31(Dist, Last->mModelWorld));
|
|
|
|
CreatePiece(PieceData, WorldMatrix, gMainWindow->mColorIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lcMatrix44 WorldMatrix = lcMatrix44Translation(lcVector3(0.0f, 0.0f, -PieceDataBoundingBox.Min.z));
|
|
|
|
CreatePiece(PieceData, WorldMatrix, gMainWindow->mColorIndex);
|
|
}
|
|
|
|
if (!Piece)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
SetSelectionAndFocus(std::vector<lcObject*>(), Piece, LC_PIECE_SECTION_POSITION, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Add Piece"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
|
|
return Piece;
|
|
}
|
|
|
|
void lcModel::AddPiece(std::unique_ptr<lcPiece> Piece, size_t PieceIndex)
|
|
{
|
|
mPieces.insert(mPieces.begin() + PieceIndex, std::move(Piece));
|
|
}
|
|
|
|
size_t lcModel::AddPiece(lcPiece* Piece)
|
|
{
|
|
size_t PieceIndex;
|
|
|
|
for (PieceIndex = 0; PieceIndex < mPieces.size(); PieceIndex++)
|
|
{
|
|
if (mPieces[PieceIndex]->GetStepShow() > Piece->GetStepShow())
|
|
{
|
|
AddPiece(std::unique_ptr<lcPiece>(Piece), PieceIndex);
|
|
|
|
return PieceIndex;
|
|
}
|
|
}
|
|
|
|
AddPiece(std::unique_ptr<lcPiece>(Piece), PieceIndex);
|
|
|
|
return PieceIndex;
|
|
}
|
|
|
|
void lcModel::FocusNextTrainTrack()
|
|
{
|
|
const lcObject* Focus = GetFocusObject();
|
|
|
|
if (!Focus || !Focus->IsPiece())
|
|
return;
|
|
|
|
lcPiece* FocusPiece = (lcPiece*)Focus;
|
|
const lcTrainTrackInfo* TrainTrackInfo = FocusPiece->mPieceData->GetTrainTrackInfo();
|
|
|
|
if (!TrainTrackInfo)
|
|
return;
|
|
|
|
quint32 FocusSection = FocusPiece->GetFocusSection();
|
|
int ConnectionIndex = 0;
|
|
|
|
if (FocusSection != LC_PIECE_SECTION_INVALID && FocusSection != LC_PIECE_SECTION_POSITION)
|
|
{
|
|
ConnectionIndex = FocusSection - LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST;
|
|
ConnectionIndex = (ConnectionIndex + 1) % TrainTrackInfo->GetConnections().size();
|
|
}
|
|
|
|
BeginHistorySequence();
|
|
SetFocus(FocusPiece, LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST + ConnectionIndex, lcSelectionMode::Single);
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::FocusPreviousTrainTrack()
|
|
{
|
|
const lcObject* Focus = GetFocusObject();
|
|
|
|
if (!Focus || !Focus->IsPiece())
|
|
return;
|
|
|
|
lcPiece* FocusPiece = (lcPiece*)Focus;
|
|
const lcTrainTrackInfo* TrainTrackInfo = FocusPiece->mPieceData->GetTrainTrackInfo();
|
|
|
|
if (!TrainTrackInfo)
|
|
return;
|
|
|
|
quint32 FocusSection = FocusPiece->GetFocusSection();
|
|
int ConnectionIndex = 0;
|
|
|
|
if (FocusSection != LC_PIECE_SECTION_INVALID && FocusSection != LC_PIECE_SECTION_POSITION)
|
|
{
|
|
ConnectionIndex = FocusSection - LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST;
|
|
ConnectionIndex = (ConnectionIndex + static_cast<int>(TrainTrackInfo->GetConnections().size()) - 1) % TrainTrackInfo->GetConnections().size();
|
|
}
|
|
|
|
BeginHistorySequence();
|
|
SetFocus(FocusPiece, LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST + ConnectionIndex, lcSelectionMode::Single);
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::RotateFocusedTrainTrack(int Direction)
|
|
{
|
|
const lcObject* Focus = GetFocusObject();
|
|
|
|
if (!Focus || !Focus->IsPiece())
|
|
return;
|
|
|
|
lcPiece* FocusPiece = (lcPiece*)Focus;
|
|
const lcTrainTrackInfo* TrainTrackInfo = FocusPiece->mPieceData->GetTrainTrackInfo();
|
|
|
|
if (!TrainTrackInfo)
|
|
return;
|
|
|
|
quint32 FocusSection = FocusPiece->GetFocusSection();
|
|
std::optional<lcMatrix44> Transform;
|
|
int FirstConnectionIndex = 0, RotateConnectionIndex = 0, TracksConnected = 0;
|
|
|
|
for (int ConnectionIndex = 0; ConnectionIndex < static_cast<int>(TrainTrackInfo->GetConnections().size()); ConnectionIndex++)
|
|
{
|
|
if (FocusPiece->IsTrainTrackConnected(ConnectionIndex))
|
|
{
|
|
if (!TracksConnected)
|
|
FirstConnectionIndex = ConnectionIndex;
|
|
|
|
TracksConnected++;
|
|
}
|
|
}
|
|
|
|
if (FocusSection != LC_PIECE_SECTION_INVALID && FocusSection != LC_PIECE_SECTION_POSITION)
|
|
{
|
|
RotateConnectionIndex = FocusSection - LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST;
|
|
}
|
|
else
|
|
{
|
|
RotateConnectionIndex = FirstConnectionIndex;
|
|
|
|
if (TracksConnected > 1)
|
|
{
|
|
// todo: find most recent connection
|
|
}
|
|
}
|
|
|
|
lcMatrix44 ConnectionTransform = lcMul(TrainTrackInfo->GetConnections()[RotateConnectionIndex].Transform, FocusPiece->mModelWorld);
|
|
int NewConnectionIndex = (RotateConnectionIndex + Direction + static_cast<int>(TrainTrackInfo->GetConnections().size())) % TrainTrackInfo->GetConnections().size();
|
|
|
|
Transform = lcTrainTrackInfo::CalculateTransformToConnection(ConnectionTransform, FocusPiece->mPieceData.Get(), NewConnectionIndex);
|
|
|
|
if (!Transform)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
|
|
BeginEditHistory();
|
|
|
|
FocusPiece->SetPosition(Transform.value().GetTranslation(), mCurrentStep, gMainWindow->GetAddKeys());
|
|
FocusPiece->SetRotation(lcMatrix33(Transform.value()), mCurrentStep, gMainWindow->GetAddKeys());
|
|
FocusPiece->UpdatePosition(mCurrentStep);
|
|
|
|
EndEditHistory();
|
|
|
|
if ((FocusSection != LC_PIECE_SECTION_INVALID && FocusSection != LC_PIECE_SECTION_POSITION) || !TracksConnected)
|
|
SetFocus(FocusPiece, LC_PIECE_SECTION_TRAIN_TRACK_CONNECTION_FIRST + NewConnectionIndex, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Rotate"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::UpdateTrainTrackConnections(lcPiece* TrackPiece, bool IgnoreSelected) const
|
|
{
|
|
if (!TrackPiece)
|
|
return;
|
|
|
|
const lcTrainTrackInfo* TrainTrackInfo = TrackPiece->mPieceData->GetTrainTrackInfo();
|
|
|
|
if (!TrainTrackInfo)
|
|
return;
|
|
|
|
const int ConnectionCount = static_cast<int>(TrainTrackInfo->GetConnections().size());
|
|
std::vector<bool> Connections(ConnectionCount, false);
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece.get() == TrackPiece || !Piece->mPieceData->GetTrainTrackInfo() || (IgnoreSelected && Piece->IsSelected()))
|
|
continue;
|
|
|
|
for (int ConnectionIndex = 0; ConnectionIndex < ConnectionCount; ConnectionIndex++)
|
|
if (!Connections[ConnectionIndex] && lcTrainTrackInfo::GetPieceConnectionIndex(TrackPiece, ConnectionIndex, Piece.get()) != -1)
|
|
Connections[ConnectionIndex] = true;
|
|
}
|
|
|
|
TrackPiece->SetTrainTrackConnections(std::move(Connections));
|
|
}
|
|
|
|
void lcModel::UpdateSelectedPiecesTrainTrackConnections()
|
|
{
|
|
std::vector<lcPiece *> TrackPieces;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->mPieceData->GetTrainTrackInfo() && Piece->IsSelected())
|
|
TrackPieces.push_back(Piece.get());
|
|
|
|
for (lcPiece* TrackPiece : TrackPieces)
|
|
{
|
|
const lcTrainTrackInfo* TrainTrackInfo = TrackPiece->mPieceData->GetTrainTrackInfo();
|
|
const int ConnectionCount = static_cast<int>(TrainTrackInfo->GetConnections().size());
|
|
std::vector<bool> Connections(ConnectionCount, false);
|
|
|
|
for (const lcPiece* CheckPiece : TrackPieces)
|
|
{
|
|
if (CheckPiece == TrackPiece)
|
|
continue;
|
|
|
|
for (int ConnectionIndex = 0; ConnectionIndex < ConnectionCount; ConnectionIndex++)
|
|
if (!Connections[ConnectionIndex] && lcTrainTrackInfo::GetPieceConnectionIndex(TrackPiece, ConnectionIndex, CheckPiece) != -1)
|
|
Connections[ConnectionIndex] = true;
|
|
}
|
|
|
|
TrackPiece->SetTrainTrackConnections(std::move(Connections));
|
|
}
|
|
}
|
|
|
|
void lcModel::SetPreviewInsertPieceInfo(std::vector<lcInsertPieceInfo>&& PreviewInsertPieceInfo)
|
|
{
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
std::vector<lcPieceDataRef> RequiredPieceData;
|
|
std::vector<lcPreviewInsertPieceInfo> PreviewPieces;
|
|
|
|
RequiredPieceData.reserve(PreviewInsertPieceInfo.size());
|
|
PreviewPieces.reserve(PreviewInsertPieceInfo.size());
|
|
|
|
for (const lcInsertPieceInfo& InfoTransform : PreviewInsertPieceInfo)
|
|
{
|
|
PreviewPieces.emplace_back(InfoTransform);
|
|
Library->RequestPieceDataLoad(InfoTransform.PieceData, lcLoadPriority::Normal);
|
|
RequiredPieceData.push_back(PreviewPieces.back().PieceData);
|
|
}
|
|
|
|
Library->WaitForPieceData(RequiredPieceData);
|
|
mPreviewInsertPieceInfo = std::move(PreviewPieces);
|
|
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::DeleteSelectedObjects()
|
|
{
|
|
if (mIsPreview)
|
|
{
|
|
RemoveSelectedObjects();
|
|
|
|
return;
|
|
}
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = RemoveSelectedObjects();
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Delete"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
gMainWindow->UpdateInUseCategory();
|
|
}
|
|
|
|
void lcModel::ResetSelectedPiecesPivotPoint()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected())
|
|
Piece->ResetPivotPoint();
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Reset Pivot Point"));
|
|
}
|
|
|
|
void lcModel::RemoveSelectedObjectsKeyFrames()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected())
|
|
Piece->RemoveKeyFrames();
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (Camera->IsSelected())
|
|
Camera->RemoveKeyFrames();
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
if (Light->IsSelected())
|
|
Light->RemoveKeyFrames();
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Remove Keyframes"));
|
|
}
|
|
|
|
void lcModel::InsertControlPoint()
|
|
{
|
|
lcPiece* Piece = dynamic_cast<lcPiece*>(GetFocusObject());
|
|
|
|
if (!Piece)
|
|
return;
|
|
|
|
lcVector3 Start, End;
|
|
|
|
gMainWindow->GetActiveView()->GetRayUnderPointer(Start, End);
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = Piece->InsertControlPoint(Start, End);
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Add Control Point"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::RemoveFocusedControlPoint()
|
|
{
|
|
lcPiece* Piece = dynamic_cast<lcPiece*>(GetFocusObject());
|
|
|
|
if (!Piece)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = Piece->RemoveFocusedControlPoint();
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Remove Control Point"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::ShowSelectedPiecesEarlier()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
std::vector<lcPiece*> MovedPieces;
|
|
|
|
for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); )
|
|
{
|
|
lcPiece* Piece = PieceIt->get();
|
|
|
|
if (Piece->IsSelected())
|
|
{
|
|
lcStep Step = Piece->GetStepShow();
|
|
|
|
if (Step > 1)
|
|
{
|
|
Step--;
|
|
Piece->SetStepShow(Step);
|
|
|
|
MovedPieces.emplace_back(PieceIt->release());
|
|
PieceIt = mPieces.erase(PieceIt);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
PieceIt++;
|
|
}
|
|
|
|
if (MovedPieces.empty())
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
for (lcPiece* Piece : MovedPieces)
|
|
{
|
|
Piece->SetFileLine(-1);
|
|
AddPiece(Piece);
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Show Earlier"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::ShowSelectedPiecesLater()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
std::vector<lcPiece*> MovedPieces;
|
|
|
|
for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); )
|
|
{
|
|
lcPiece* Piece = PieceIt->get();
|
|
|
|
if (Piece->IsSelected())
|
|
{
|
|
lcStep Step = Piece->GetStepShow();
|
|
|
|
if (Step < LC_STEP_MAX)
|
|
{
|
|
Step++;
|
|
Piece->SetStepShow(Step);
|
|
|
|
MovedPieces.emplace_back(PieceIt->release());
|
|
PieceIt = mPieces.erase(PieceIt);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
PieceIt++;
|
|
}
|
|
|
|
if (MovedPieces.empty())
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
for (lcPiece* Piece : MovedPieces)
|
|
{
|
|
Piece->SetFileLine(-1);
|
|
AddPiece(Piece);
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Show Later"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::InsertStepAndMoveSelectedPieces(lcStep Step)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
InsertStep(Step);
|
|
|
|
std::vector<lcPiece*> MovedPieces;
|
|
|
|
for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); )
|
|
{
|
|
lcPiece* Piece = PieceIt->get();
|
|
|
|
if (Piece->IsSelected())
|
|
{
|
|
Piece->SetStepShow(Step);
|
|
|
|
MovedPieces.emplace_back(PieceIt->release());
|
|
PieceIt = mPieces.erase(PieceIt);
|
|
|
|
continue;
|
|
}
|
|
|
|
PieceIt++;
|
|
}
|
|
|
|
for (lcPiece* Piece : MovedPieces)
|
|
{
|
|
Piece->SetFileLine(-1);
|
|
AddPiece(Piece);
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Show Pieces"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::SetPieceSteps(const std::vector<std::pair<lcPiece*, lcStep>>& PieceSteps)
|
|
{
|
|
if (PieceSteps.size() != mPieces.size())
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = false;
|
|
|
|
for (size_t PieceIdx = 0; PieceIdx < PieceSteps.size(); PieceIdx++)
|
|
{
|
|
const std::pair<lcPiece*, lcStep>& PieceStep = PieceSteps[PieceIdx];
|
|
lcPiece* Piece = mPieces[PieceIdx].get();
|
|
|
|
if (Piece != PieceStep.first || Piece->GetStepShow() != PieceStep.second)
|
|
{
|
|
Piece = PieceStep.first;
|
|
const lcStep Step = PieceStep.second;
|
|
|
|
mPieces[PieceIdx].release();
|
|
mPieces[PieceIdx] = std::unique_ptr<lcPiece>(Piece);
|
|
Piece->SetStepShow(Step);
|
|
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (Modified)
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Change Step"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
else
|
|
{
|
|
DiscardHistorySequence();
|
|
}
|
|
}
|
|
|
|
void lcModel::RenamePieces(const std::vector<lcModelPieceRename>& Renames)
|
|
{
|
|
if (Renames.empty())
|
|
return;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
for (const lcModelPieceRename& Rename : Renames)
|
|
{
|
|
if (Piece->mPieceData != Rename.PieceData)
|
|
continue;
|
|
|
|
Piece->UpdateID();
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto RenameHistorySequence = [&Renames, ModelPieceData = mPieceData.Get()](std::vector<std::unique_ptr<lcModelHistory>>& HistorySequence)
|
|
{
|
|
for (const std::unique_ptr<lcModelHistory>& ModelHistory : HistorySequence)
|
|
ModelHistory->RenamePieces(Renames, ModelPieceData);
|
|
};
|
|
|
|
RenameHistorySequence(mHistorySequence);
|
|
|
|
for (const std::unique_ptr<lcModelHistoryEntry>& HistoryEntry : mUndoHistory)
|
|
RenameHistorySequence(HistoryEntry->HistorySequence);
|
|
|
|
for (const std::unique_ptr<lcModelHistoryEntry>& HistoryEntry : mRedoHistory)
|
|
RenameHistorySequence(HistoryEntry->HistorySequence);
|
|
}
|
|
|
|
void lcModel::MoveSelectionToModel(lcModel* Model)
|
|
{
|
|
if (!Model)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
std::vector<lcPiece*> Pieces;
|
|
lcPiece* ModelPiece = nullptr;
|
|
lcStep FirstStep = LC_STEP_MAX;
|
|
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
|
|
for (size_t PieceIndex = 0; PieceIndex < mPieces.size(); )
|
|
{
|
|
lcPiece* Piece = mPieces[PieceIndex].get();
|
|
|
|
if (Piece->IsSelected())
|
|
{
|
|
Piece->CompareBoundingBox(Min, Max);
|
|
mPieces[PieceIndex].release();
|
|
mPieces.erase(mPieces.begin() + PieceIndex);
|
|
Piece->SetGroup(nullptr); // todo: copy groups
|
|
Pieces.emplace_back(Piece);
|
|
FirstStep = qMin(FirstStep, Piece->GetStepShow());
|
|
|
|
if (!ModelPiece)
|
|
{
|
|
ModelPiece = new lcPiece(Model->mPieceData);
|
|
ModelPiece->SetColorIndex(gDefaultColor);
|
|
AddPiece(std::unique_ptr<lcPiece>(ModelPiece), PieceIndex);
|
|
PieceIndex++;
|
|
}
|
|
}
|
|
else
|
|
PieceIndex++;
|
|
}
|
|
|
|
if (Pieces.empty())
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
lcVector3 ModelCenter = (Min + Max) / 2.0f;
|
|
ModelCenter.z += (Min.z - Max.z) / 2.0f;
|
|
|
|
for (lcPiece* Piece : Pieces)
|
|
{
|
|
Piece->SetFileLine(-1);
|
|
Piece->SetStepShow(Piece->GetStepShow() - FirstStep + 1);
|
|
Piece->MoveSelected(Piece->GetStepShow(), false, -ModelCenter);
|
|
Model->AddPiece(Piece);
|
|
}
|
|
|
|
std::vector<lcModel*> UpdatedModels;
|
|
Model->UpdatePieceData(UpdatedModels);
|
|
if (ModelPiece)
|
|
{
|
|
ModelPiece->Initialize(lcMatrix44Translation(ModelCenter), FirstStep);
|
|
ModelPiece->UpdatePosition(mCurrentStep);
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
|
|
SetSelectionAndFocus(std::vector<lcObject*>(), ModelPiece, LC_PIECE_SECTION_POSITION, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Move to Model"));
|
|
}
|
|
|
|
void lcModel::InlineSelectedModels()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
std::vector<lcObject*> NewPieces;
|
|
bool Modified = false;
|
|
|
|
for (size_t PieceIndex = 0; PieceIndex < mPieces.size(); )
|
|
{
|
|
lcPiece* Piece = mPieces[PieceIndex].get();
|
|
|
|
if (!Piece->IsSelected() || !Piece->mPieceData->IsModel())
|
|
{
|
|
PieceIndex++;
|
|
continue;
|
|
}
|
|
|
|
mPieces[PieceIndex].release();
|
|
mPieces.erase(mPieces.begin() + PieceIndex);
|
|
|
|
lcModel* Model = Piece->mPieceData->GetModel();
|
|
|
|
Modified = true;
|
|
|
|
for (const std::unique_ptr<lcPiece>& ModelPiece : Model->mPieces)
|
|
{
|
|
lcPiece* NewPiece = new lcPiece(nullptr);
|
|
|
|
// todo: recreate in groups in the current model
|
|
|
|
int ColorIndex = ModelPiece->GetColorIndex();
|
|
|
|
if (ColorIndex == gDefaultColor)
|
|
ColorIndex = Piece->GetColorIndex();
|
|
|
|
NewPiece->SetPieceData(ModelPiece->mPieceData, ModelPiece->GetID(), true, true);
|
|
NewPiece->Initialize(lcMul(ModelPiece->mModelWorld, Piece->mModelWorld), Piece->GetStepShow());
|
|
NewPiece->SetColorIndex(ColorIndex);
|
|
NewPiece->UpdatePosition(mCurrentStep);
|
|
|
|
NewPieces.emplace_back(NewPiece);
|
|
AddPiece(std::unique_ptr<lcPiece>(NewPiece), PieceIndex);
|
|
PieceIndex++;
|
|
}
|
|
|
|
delete Piece;
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
QMessageBox::information(gMainWindow, tr("Inline Selected Models"), tr("No models selected."));
|
|
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
SetSelectionAndFocus(NewPieces, nullptr, 0, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Inline Model"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateInUseCategory();
|
|
}
|
|
|
|
void lcModel::RemoveCameraFromViews(lcCamera* Camera)
|
|
{
|
|
std::vector<lcView*> Views = lcView::GetModelViews(this);
|
|
|
|
for (lcView* View : Views)
|
|
if (Camera == View->GetCamera())
|
|
View->SetCamera(Camera, true);
|
|
}
|
|
|
|
bool lcModel::RemoveSelectedObjects()
|
|
{
|
|
bool RemovedPiece = false;
|
|
bool RemovedCamera = false;
|
|
bool RemovedLight = false;
|
|
|
|
for (std::vector<std::unique_ptr<lcPiece>>::iterator PieceIt = mPieces.begin(); PieceIt != mPieces.end(); )
|
|
{
|
|
lcPiece* Piece = PieceIt->get();
|
|
|
|
if (Piece->IsSelected())
|
|
{
|
|
RemovedPiece = true;
|
|
PieceIt = mPieces.erase(PieceIt);
|
|
}
|
|
else
|
|
PieceIt++;
|
|
}
|
|
|
|
for (std::vector<std::unique_ptr<lcCamera>>::iterator CameraIt = mCameras.begin(); CameraIt != mCameras.end(); )
|
|
{
|
|
lcCamera* Camera = CameraIt->get();
|
|
|
|
if (Camera->IsSelected())
|
|
{
|
|
RemoveCameraFromViews(Camera);
|
|
|
|
RemovedCamera = true;
|
|
CameraIt = mCameras.erase(CameraIt);
|
|
}
|
|
else
|
|
CameraIt++;
|
|
}
|
|
|
|
for (std::vector<std::unique_ptr<lcLight>>::iterator LightIt = mLights.begin(); LightIt != mLights.end(); )
|
|
{
|
|
lcLight* Light = LightIt->get();
|
|
|
|
if (Light->IsSelected())
|
|
{
|
|
RemovedLight = true;
|
|
LightIt = mLights.erase(LightIt);
|
|
}
|
|
else
|
|
LightIt++;
|
|
}
|
|
|
|
RemoveEmptyGroups();
|
|
|
|
return RemovedPiece || RemovedCamera || RemovedLight;
|
|
}
|
|
|
|
void lcModel::MoveSelectedObjects(const lcVector3& PieceDistance, const lcVector3& ObjectDistance, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge)
|
|
{
|
|
if (Flags.testFlag(lcModelTransformFlag::Checkpoint))
|
|
{
|
|
BeginHistorySequence();
|
|
BeginMergeableEditHistory(ModelHistoryEditMerge, lcObjectPropertyId::Count);
|
|
}
|
|
|
|
bool Moved = false;
|
|
lcMatrix33 RelativeRotation;
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::Relative))
|
|
RelativeRotation = GetRelativeRotation();
|
|
else
|
|
RelativeRotation = lcMatrix33Identity();
|
|
|
|
if (PieceDistance.LengthSquared() >= 0.001f)
|
|
{
|
|
lcVector3 TransformedPieceDistance = lcMul(PieceDistance, RelativeRotation);
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::RotatePivotPoint))
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsFocused())
|
|
{
|
|
Piece->MovePivotPoint(TransformedPieceDistance);
|
|
Moved = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected())
|
|
{
|
|
if (gMainWindow->GetSeparateTransform())
|
|
TransformedPieceDistance = lcMul(PieceDistance, Piece->GetRelativeRotation());
|
|
|
|
Piece->MoveSelected(mCurrentStep, gMainWindow->GetAddKeys(), TransformedPieceDistance);
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
Moved = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ObjectDistance.LengthSquared() >= 0.001f && !Flags.testFlag(lcModelTransformFlag::RotatePivotPoint))
|
|
{
|
|
const lcVector3 TransformedObjectDistance = lcMul(ObjectDistance, RelativeRotation);
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
{
|
|
if (Camera->IsSelected())
|
|
{
|
|
Camera->MoveSelected(mCurrentStep, gMainWindow->GetAddKeys(), TransformedObjectDistance);
|
|
Camera->UpdatePosition(mCurrentStep);
|
|
Moved = true;
|
|
}
|
|
}
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
{
|
|
if (Light->IsSelected())
|
|
{
|
|
Light->MoveSelected(mCurrentStep, gMainWindow->GetAddKeys(), TransformedObjectDistance, Flags.testFlag(lcModelTransformFlag::FirstMove));
|
|
Light->UpdatePosition(mCurrentStep);
|
|
Moved = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Moved)
|
|
{
|
|
if (Flags.testFlag(lcModelTransformFlag::Checkpoint))
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::Checkpoint))
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Move"));
|
|
|
|
RemoveFirstUndoIfUnchanged();
|
|
}
|
|
|
|
UpdateAllViews();
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::RotateSelectedObjects(const lcVector3& Angles, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge)
|
|
{
|
|
if (Angles.LengthSquared() < 0.001f)
|
|
return;
|
|
|
|
lcMatrix33 RotationMatrix = lcMatrix33Identity();
|
|
|
|
if (Angles[0] != 0.0f)
|
|
RotationMatrix = lcMul(lcMatrix33RotationX(Angles[0] * LC_DTOR), RotationMatrix);
|
|
|
|
if (Angles[1] != 0.0f)
|
|
RotationMatrix = lcMul(lcMatrix33RotationY(Angles[1] * LC_DTOR), RotationMatrix);
|
|
|
|
if (Angles[2] != 0.0f)
|
|
RotationMatrix = lcMul(lcMatrix33RotationZ(Angles[2] * LC_DTOR), RotationMatrix);
|
|
|
|
RotateSelectedObjects(RotationMatrix, Flags, ModelHistoryEditMerge);
|
|
}
|
|
|
|
void lcModel::RotateSelectedObjects(const lcMatrix33& InputRotationMatrix, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge)
|
|
{
|
|
if (Flags.testFlag(lcModelTransformFlag::Checkpoint))
|
|
{
|
|
BeginHistorySequence();
|
|
BeginMergeableEditHistory(ModelHistoryEditMerge, lcObjectPropertyId::Count);
|
|
}
|
|
|
|
lcMatrix33 RotationMatrix = InputRotationMatrix;
|
|
bool Rotated = false;
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::RotatePivotPoint))
|
|
{
|
|
lcObject* Focus = GetFocusObject();
|
|
|
|
if (Focus && Focus->IsPiece())
|
|
{
|
|
((lcPiece*)Focus)->RotatePivotPoint(RotationMatrix);
|
|
Rotated = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int SelectionFlags;
|
|
std::vector<lcObject*> Selection;
|
|
lcObject* Focus;
|
|
|
|
GetSelectionInformation(&SelectionFlags, Selection, &Focus);
|
|
Q_UNUSED(SelectionFlags);
|
|
Q_UNUSED(Focus);
|
|
|
|
if (!gMainWindow->GetSeparateTransform())
|
|
{
|
|
lcVector3 Center;
|
|
lcMatrix33 RelativeRotation;
|
|
|
|
GetMoveRotateTransform(Center, RelativeRotation);
|
|
|
|
lcMatrix33 WorldToFocusMatrix;
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::Relative))
|
|
{
|
|
WorldToFocusMatrix = lcMatrix33AffineInverse(RelativeRotation);
|
|
RotationMatrix = lcMul(RotationMatrix, RelativeRotation);
|
|
}
|
|
else
|
|
WorldToFocusMatrix = lcMatrix33Identity();
|
|
|
|
for (lcObject* Object : Selection)
|
|
{
|
|
if (Object->IsPiece())
|
|
{
|
|
lcPiece* Piece = (lcPiece*)Object;
|
|
|
|
Piece->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
Rotated = true;
|
|
}
|
|
else if (Object->IsCamera())
|
|
{
|
|
lcCamera* Camera = (lcCamera*)Object;
|
|
|
|
Camera->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Camera->UpdatePosition(mCurrentStep);
|
|
Rotated = true;
|
|
}
|
|
else if (Object->IsLight())
|
|
{
|
|
lcLight* Light = (lcLight*)Object;
|
|
|
|
Light->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Light->UpdatePosition(mCurrentStep);
|
|
Rotated = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (lcObject* Object : Selection)
|
|
{
|
|
if (Object->IsPiece())
|
|
{
|
|
lcPiece* Piece = (lcPiece*)Object;
|
|
|
|
const lcVector3 Center = Piece->GetRotationCenter();
|
|
lcMatrix33 WorldToFocusMatrix;
|
|
lcMatrix33 RelativeRotationMatrix;
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::Relative))
|
|
{
|
|
const lcMatrix33 RelativeRotation = Piece->GetRelativeRotation();
|
|
WorldToFocusMatrix = lcMatrix33AffineInverse(RelativeRotation);
|
|
RelativeRotationMatrix = lcMul(RotationMatrix, RelativeRotation);
|
|
}
|
|
else
|
|
{
|
|
WorldToFocusMatrix = lcMatrix33Identity();
|
|
RelativeRotationMatrix = RotationMatrix;
|
|
}
|
|
|
|
Piece->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RelativeRotationMatrix, Center, WorldToFocusMatrix);
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
Rotated = true;
|
|
}
|
|
else if (Object->IsCamera())
|
|
{
|
|
lcCamera* Camera = (lcCamera*)Object;
|
|
|
|
const lcVector3 Center = Camera->GetRotationCenter();
|
|
lcMatrix33 WorldToFocusMatrix;
|
|
lcMatrix33 RelativeRotationMatrix;
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::Relative))
|
|
{
|
|
const lcMatrix33 RelativeRotation = Camera->GetRelativeRotation();
|
|
WorldToFocusMatrix = lcMatrix33AffineInverse(RelativeRotation);
|
|
RelativeRotationMatrix = lcMul(RotationMatrix, RelativeRotation);
|
|
}
|
|
else
|
|
{
|
|
WorldToFocusMatrix = lcMatrix33Identity();
|
|
RelativeRotationMatrix = RotationMatrix;
|
|
}
|
|
|
|
Camera->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Camera->UpdatePosition(mCurrentStep);
|
|
Rotated = true;
|
|
}
|
|
else if (Object->IsLight())
|
|
{
|
|
lcLight* Light = (lcLight*)Object;
|
|
|
|
const lcVector3 Center = Light->GetRotationCenter();
|
|
lcMatrix33 WorldToFocusMatrix;
|
|
lcMatrix33 RelativeRotationMatrix;
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::Relative))
|
|
{
|
|
const lcMatrix33 RelativeRotation = Light->GetRelativeRotation();
|
|
WorldToFocusMatrix = lcMatrix33AffineInverse(RelativeRotation);
|
|
RelativeRotationMatrix = lcMul(RotationMatrix, RelativeRotation);
|
|
}
|
|
else
|
|
{
|
|
WorldToFocusMatrix = lcMatrix33Identity();
|
|
RelativeRotationMatrix = RotationMatrix;
|
|
}
|
|
|
|
Light->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Light->UpdatePosition(mCurrentStep);
|
|
Rotated = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Rotated)
|
|
{
|
|
if (Flags.testFlag(lcModelTransformFlag::Checkpoint))
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
if (Flags.testFlag(lcModelTransformFlag::Checkpoint))
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Rotate"));
|
|
|
|
RemoveFirstUndoIfUnchanged();
|
|
}
|
|
|
|
UpdateAllViews();
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::ScaleSelectedPieces(const float Scale)
|
|
{
|
|
if (Scale < 0.001f)
|
|
return;
|
|
|
|
lcObject* Focus = GetFocusObject();
|
|
if (!Focus || !Focus->IsPiece())
|
|
return;
|
|
|
|
lcPiece* Piece = (lcPiece*)Focus;
|
|
const quint32 Section = Piece->GetFocusSection();
|
|
|
|
if (Section >= LC_PIECE_SECTION_CONTROL_POINT_FIRST && Section <= LC_PIECE_SECTION_CONTROL_POINT_LAST)
|
|
{
|
|
const int ControlPointIndex = Section - LC_PIECE_SECTION_CONTROL_POINT_FIRST;
|
|
Piece->SetControlPointScale(ControlPointIndex, Scale);
|
|
}
|
|
}
|
|
|
|
void lcModel::TransformSelectedObjects(lcTransformType TransformType, const lcVector3& Transform)
|
|
{
|
|
switch (TransformType)
|
|
{
|
|
case lcTransformType::AbsoluteTranslation:
|
|
MoveSelectedObjects(Transform, lcModelTransformFlag::Checkpoint | lcModelTransformFlag::FirstMove, lcModelHistoryEditMerge::None);
|
|
break;
|
|
|
|
case lcTransformType::RelativeTranslation:
|
|
MoveSelectedObjects(Transform, lcModelTransformFlag::Relative | lcModelTransformFlag::Checkpoint | lcModelTransformFlag::FirstMove, lcModelHistoryEditMerge::None);
|
|
break;
|
|
|
|
case lcTransformType::AbsoluteRotation:
|
|
RotateSelectedObjects(Transform, lcModelTransformFlag::Checkpoint, lcModelHistoryEditMerge::None);
|
|
break;
|
|
|
|
case lcTransformType::RelativeRotation:
|
|
RotateSelectedObjects(Transform, lcModelTransformFlag::Relative | lcModelTransformFlag::Checkpoint, lcModelHistoryEditMerge::None);
|
|
break;
|
|
|
|
case lcTransformType::Count:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void lcModel::SetObjectsKeyFrame(const std::vector<lcObject*>& Objects, lcObjectPropertyId PropertyId, bool KeyFrame)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = false;
|
|
|
|
for (lcObject* Object : Objects)
|
|
{
|
|
Modified |= Object->SetKeyFrame(PropertyId, mCurrentStep, KeyFrame);
|
|
Object->UpdatePosition(mCurrentStep);
|
|
}
|
|
|
|
if (Modified)
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(KeyFrame ? tr("Add Keyframe") : tr("Remove Keyframe"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
else
|
|
{
|
|
DiscardHistorySequence();
|
|
}
|
|
}
|
|
|
|
void lcModel::SetSelectedPiecesColorIndex(int ColorIndex)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected() && Piece->GetColorIndex() != ColorIndex)
|
|
{
|
|
Piece->SetColorIndex(ColorIndex);
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (Modified)
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Paint"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
}
|
|
else
|
|
{
|
|
DiscardHistorySequence();
|
|
}
|
|
}
|
|
|
|
void lcModel::SetSelectedPiecesStepShow(lcStep Step)
|
|
{
|
|
std::vector<lcPiece*> MovedPieces;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); )
|
|
{
|
|
lcPiece* Piece = PieceIt->get();
|
|
|
|
if (Piece->IsSelected() && Piece->GetStepShow() != Step)
|
|
{
|
|
Piece->SetStepShow(Step);
|
|
|
|
MovedPieces.emplace_back(PieceIt->release());
|
|
PieceIt = mPieces.erase(PieceIt);
|
|
continue;
|
|
}
|
|
|
|
PieceIt++;
|
|
}
|
|
|
|
if (MovedPieces.empty())
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
for (lcPiece* Piece : MovedPieces)
|
|
{
|
|
Piece->SetFileLine(-1);
|
|
AddPiece(Piece);
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Set Show Step"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::SetSelectedPiecesStepHide(lcStep Step)
|
|
{
|
|
bool Modified = false;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected() && Piece->GetStepHide() != Step)
|
|
{
|
|
Piece->SetStepHide(Step);
|
|
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Set Hide Step"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::SetCameraProjection(lcCamera* Camera, lcCameraProjection CameraProjection)
|
|
{
|
|
if (Camera->GetProjection() == CameraProjection)
|
|
return;
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
}
|
|
|
|
Camera->SetProjection(CameraProjection);
|
|
Camera->UpdatePosition(mCurrentStep);
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Change Projection"));
|
|
}
|
|
|
|
UpdateAllViews();
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
}
|
|
|
|
void lcModel::SetObjectsProperty(const std::vector<lcObject*>& Objects, lcObjectPropertyId PropertyId, QVariant Value)
|
|
{
|
|
const bool PropertyEditActive = mActivePropertyEdit == PropertyId;
|
|
|
|
if (!PropertyEditActive)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginMergeableEditHistory(lcModelHistoryEditMerge::PropertiesEdit, PropertyId);
|
|
}
|
|
|
|
bool Modified = false;
|
|
|
|
for (lcObject* Object : Objects)
|
|
{
|
|
bool ObjectModified = Object->SetPropertyValue(PropertyId, mCurrentStep, gMainWindow->GetAddKeys(), Value);
|
|
|
|
if (ObjectModified)
|
|
{
|
|
Object->UpdatePosition(mCurrentStep);
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
if (!PropertyEditActive)
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
if (!PropertyEditActive)
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(lcObject::GetCheckpointString(PropertyId));
|
|
|
|
RemoveFirstUndoIfUnchanged();
|
|
}
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
|
|
// todo: fix hacky timeline update
|
|
if (PropertyId == lcObjectPropertyId::PieceId || PropertyId == lcObjectPropertyId::PieceColor)
|
|
{
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
}
|
|
|
|
// todo: fix hacky category update
|
|
if (PropertyId == lcObjectPropertyId::PieceId)
|
|
{
|
|
gMainWindow->UpdateInUseCategory();
|
|
}
|
|
}
|
|
|
|
void lcModel::BeginPropertyEdit(lcObjectPropertyId PropertyId)
|
|
{
|
|
if (mActivePropertyEdit != lcObjectPropertyId::Count)
|
|
EndPropertyEdit(mActivePropertyEdit, true);
|
|
|
|
mActivePropertyEdit = PropertyId;
|
|
BeginHistorySequence();
|
|
BeginMergeableEditHistory(lcModelHistoryEditMerge::PropertiesEdit, PropertyId);
|
|
}
|
|
|
|
void lcModel::EndPropertyEdit(lcObjectPropertyId PropertyId, bool Accept)
|
|
{
|
|
if (mActivePropertyEdit != PropertyId)
|
|
return;
|
|
|
|
mActivePropertyEdit = lcObjectPropertyId::Count;
|
|
|
|
if (!Accept)
|
|
{
|
|
RevertHistorySequence();
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
switch (PropertyId)
|
|
{
|
|
case lcObjectPropertyId::ObjectPositionX:
|
|
case lcObjectPropertyId::ObjectPositionY:
|
|
case lcObjectPropertyId::ObjectPositionZ:
|
|
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:
|
|
EndHistorySequence(tr("Move"));
|
|
break;
|
|
|
|
case lcObjectPropertyId::ObjectRotationX:
|
|
case lcObjectPropertyId::ObjectRotationY:
|
|
case lcObjectPropertyId::ObjectRotationZ:
|
|
EndHistorySequence(tr("Rotate"));
|
|
break;
|
|
|
|
default:
|
|
EndHistorySequence(lcObject::GetCheckpointString(PropertyId));
|
|
break;
|
|
}
|
|
|
|
RemoveFirstUndoIfUnchanged();
|
|
}
|
|
|
|
bool lcModel::AnyPiecesSelected() const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool lcModel::AnyObjectsSelected() const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected())
|
|
return true;
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (Camera->IsSelected())
|
|
return true;
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
if (Light->IsSelected())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
lcObject* lcModel::GetFocusObject() const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsFocused())
|
|
return Piece.get();
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
if (Camera->IsFocused())
|
|
return Camera.get();
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
if (Light->IsFocused())
|
|
return Light.get();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
lcModel* lcModel::GetFirstSelectedSubmodel() const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected() && Piece->mPieceData->IsModel())
|
|
return Piece->mPieceData->GetModel();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void lcModel::GetSubModels(std::set<lcModel*>& SubModels) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->mPieceData->IsModel())
|
|
{
|
|
lcModel* SubModel = Piece->mPieceData->GetModel();
|
|
|
|
SubModels.insert(SubModel);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool lcModel::GetMoveRotateTransform(lcVector3& Center, lcMatrix33& RelativeRotation) const
|
|
{
|
|
const bool Relative = gMainWindow->GetRelativeTransform();
|
|
int NumSelected = 0;
|
|
|
|
Center = lcVector3(0.0f, 0.0f, 0.0f);
|
|
RelativeRotation = lcMatrix33Identity();
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (!Piece->IsSelected())
|
|
continue;
|
|
|
|
if (Piece->IsFocused() && Relative)
|
|
{
|
|
Center = Piece->GetRotationCenter();
|
|
RelativeRotation = Piece->GetRelativeRotation();
|
|
return true;
|
|
}
|
|
|
|
Center += Piece->mModelWorld.GetTranslation();
|
|
NumSelected++;
|
|
}
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
{
|
|
if (!Camera->IsSelected())
|
|
continue;
|
|
|
|
if (Camera->IsFocused() && Relative)
|
|
{
|
|
Center = Camera->GetSectionPosition(Camera->GetFocusSection());
|
|
// RelativeRotation = Piece->GetRelativeRotation();
|
|
return true;
|
|
}
|
|
|
|
Center += Camera->GetSectionPosition(LC_CAMERA_SECTION_POSITION);
|
|
Center += Camera->GetSectionPosition(LC_CAMERA_SECTION_TARGET);
|
|
Center += Camera->GetSectionPosition(LC_CAMERA_SECTION_UPVECTOR);
|
|
NumSelected += 3;
|
|
}
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
{
|
|
if (!Light->IsSelected())
|
|
continue;
|
|
|
|
if (Light->IsFocused())
|
|
{
|
|
Center = Light->GetRotationCenter();
|
|
|
|
if (Relative)
|
|
RelativeRotation = Light->GetRelativeRotation();
|
|
|
|
return true;
|
|
}
|
|
|
|
Center += Light->GetSectionPosition(LC_LIGHT_SECTION_POSITION);
|
|
NumSelected++;
|
|
}
|
|
|
|
if (NumSelected)
|
|
{
|
|
Center /= NumSelected;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool lcModel::CanRotateSelection() const
|
|
{
|
|
int Flags;
|
|
std::vector<lcObject*> Selection;
|
|
lcObject* Focus;
|
|
|
|
GetSelectionInformation(&Flags, Selection, &Focus);
|
|
|
|
if (Flags & LC_SEL_PIECE)
|
|
{
|
|
if ((Flags & (LC_SEL_CAMERA | LC_SEL_LIGHT)) == 0)
|
|
return true;
|
|
}
|
|
|
|
if ((Flags & (LC_SEL_PIECE | LC_SEL_CAMERA)) == 0)
|
|
{
|
|
if (Focus && Focus->IsLight())
|
|
{
|
|
lcLight* Light = (lcLight*)Focus;
|
|
|
|
return (Light->GetAllowedTransforms() & LC_OBJECT_TRANSFORM_ROTATE_XYZ) != 0;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool lcModel::GetPieceFocusOrSelectionCenter(lcVector3& Center) const
|
|
{
|
|
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
lcPiece* Selected = nullptr;
|
|
int NumSelected = 0;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsFocused())
|
|
{
|
|
Center = Piece->mModelWorld.GetTranslation();
|
|
return true;
|
|
}
|
|
|
|
if (Piece->IsSelected())
|
|
{
|
|
Piece->CompareBoundingBox(Min, Max);
|
|
Selected = Piece.get();
|
|
NumSelected++;
|
|
}
|
|
}
|
|
|
|
if (NumSelected == 1)
|
|
Center = Selected->mModelWorld.GetTranslation();
|
|
else if (NumSelected)
|
|
Center = (Min + Max) / 2.0f;
|
|
else
|
|
Center = lcVector3(0.0f, 0.0f, 0.0f);
|
|
|
|
return NumSelected != 0;
|
|
}
|
|
|
|
lcVector3 lcModel::GetSelectionOrModelCenter() const
|
|
{
|
|
lcVector3 Center;
|
|
|
|
if (!GetSelectionCenter(Center))
|
|
{
|
|
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
|
|
if (GetVisiblePiecesBoundingBox(Min, Max))
|
|
Center = (Min + Max) / 2.0f;
|
|
else
|
|
Center = lcVector3(0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
return Center;
|
|
}
|
|
|
|
bool lcModel::GetFocusPosition(lcVector3& Position) const
|
|
{
|
|
const lcObject* FocusObject = GetFocusObject();
|
|
|
|
if (FocusObject)
|
|
{
|
|
Position = FocusObject->GetSectionPosition(FocusObject->GetFocusSection());
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Position = lcVector3(0.0f, 0.0f, 0.0f);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool lcModel::GetSelectionCenter(lcVector3& Center) const
|
|
{
|
|
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
lcPiece* SelectedPiece = nullptr;
|
|
bool SinglePieceSelected = true;
|
|
bool Selected = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected())
|
|
{
|
|
Piece->CompareBoundingBox(Min, Max);
|
|
Selected = true;
|
|
|
|
if (!SelectedPiece)
|
|
SelectedPiece = Piece.get();
|
|
else
|
|
SinglePieceSelected = false;
|
|
}
|
|
}
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
{
|
|
if (Camera->IsSelected())
|
|
{
|
|
Camera->CompareBoundingBox(Min, Max);
|
|
Selected = true;
|
|
SinglePieceSelected = false;
|
|
}
|
|
}
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
{
|
|
if (Light->IsSelected())
|
|
{
|
|
Light->CompareBoundingBox(Min, Max);
|
|
Selected = true;
|
|
SinglePieceSelected = false;
|
|
}
|
|
}
|
|
|
|
if (SelectedPiece && SinglePieceSelected)
|
|
Center = SelectedPiece->GetSectionPosition(LC_PIECE_SECTION_POSITION);
|
|
else if (Selected)
|
|
Center = (Min + Max) / 2.0f;
|
|
else
|
|
Center = lcVector3(0.0f, 0.0f, 0.0f);
|
|
|
|
return Selected;
|
|
}
|
|
|
|
lcBoundingBox lcModel::GetAllPiecesBoundingBox() const
|
|
{
|
|
lcBoundingBox Box;
|
|
|
|
if (!mPieces.empty())
|
|
{
|
|
Box.Min = lcVector3(FLT_MAX, FLT_MAX, FLT_MAX);
|
|
Box.Max = lcVector3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
Piece->CompareBoundingBox(Box.Min, Box.Max);
|
|
}
|
|
else
|
|
Box.Min = Box.Max = lcVector3(0.0f, 0.0f, 0.0f);
|
|
|
|
return Box;
|
|
}
|
|
|
|
bool lcModel::GetVisiblePiecesBoundingBox(lcVector3& Min, lcVector3& Max) const
|
|
{
|
|
bool Valid = false;
|
|
Min = lcVector3(FLT_MAX, FLT_MAX, FLT_MAX);
|
|
Max = lcVector3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected() || Piece->IsVisible(mCurrentStep))
|
|
{
|
|
Piece->CompareBoundingBox(Min, Max);
|
|
Valid = true;
|
|
}
|
|
}
|
|
|
|
return Valid;
|
|
}
|
|
|
|
std::vector<lcVector3> lcModel::GetPiecesBoundingBoxPoints(lcGeometryBoundsMode Mode) const
|
|
{
|
|
std::vector<lcVector3> Points;
|
|
const lcMesh* Mesh = mPieceData ? mPieceData->GetDisplayMesh() : nullptr;
|
|
|
|
if (Mode == lcGeometryBoundsMode::AvailableOnly && mPieceData && mPieceData->mState != lcAssetState::Loaded)
|
|
Mesh = nullptr;
|
|
|
|
if (Mesh)
|
|
{
|
|
lcVector3 Box[8];
|
|
lcGetBoxCorners(Mesh->mBoundingBox, Box);
|
|
|
|
for (const lcVector3& Point : Box)
|
|
Points.emplace_back(Point);
|
|
}
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsSelected() || Piece->IsVisible(mCurrentStep))
|
|
Piece->SubModelAddBoundingBoxPoints(lcMatrix44Identity(), Points, Mode);
|
|
|
|
return Points;
|
|
}
|
|
|
|
void lcModel::GetPartsList(int DefaultColorIndex, bool ScanSubModels, bool AddSubModels, lcPartsList& PartsList) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (!Piece->IsVisibleInSubModel())
|
|
continue;
|
|
|
|
int ColorIndex = Piece->GetColorIndex();
|
|
|
|
if (ColorIndex == gDefaultColor)
|
|
ColorIndex = DefaultColorIndex;
|
|
|
|
Piece->mPieceData->GetPartsList(ColorIndex, ScanSubModels, AddSubModels, PartsList);
|
|
}
|
|
}
|
|
|
|
void lcModel::GetPartsListForStep(lcStep Step, int DefaultColorIndex, lcPartsList& PartsList, bool Cumulative) const
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Cumulative ? Piece->GetStepShow() > Step : Piece->GetStepShow() != Step || Piece->IsHidden())
|
|
continue;
|
|
|
|
int ColorIndex = Piece->GetColorIndex();
|
|
|
|
if (ColorIndex == gDefaultColor)
|
|
ColorIndex = DefaultColorIndex;
|
|
|
|
Piece->mPieceData->GetPartsList(ColorIndex, false, true, PartsList);
|
|
}
|
|
}
|
|
|
|
void lcModel::GetModelParts(const lcMatrix44& WorldMatrix, int DefaultColorIndex, std::vector<lcModelPartsEntry>& ModelParts) const
|
|
{
|
|
if (mHasDirectGeometry && mPieceData->GetMesh())
|
|
ModelParts.emplace_back(lcModelPartsEntry{ WorldMatrix, mPieceData.Get(), mPieceData->GetMesh(), DefaultColorIndex });
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
Piece->GetModelParts(WorldMatrix, DefaultColorIndex, ModelParts);
|
|
}
|
|
|
|
void lcModel::GetSelectionInformation(int* Flags, std::vector<lcObject*>& Selection, lcObject** Focus) const
|
|
{
|
|
*Flags = 0;
|
|
*Focus = nullptr;
|
|
|
|
if (mPieces.empty())
|
|
*Flags |= LC_SEL_NO_PIECES;
|
|
else
|
|
{
|
|
lcGroup* Group = nullptr;
|
|
bool First = true;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected())
|
|
{
|
|
Selection.emplace_back(Piece.get());
|
|
|
|
if (Piece->IsFocused())
|
|
*Focus = Piece.get();
|
|
|
|
if (Piece->mPieceData->IsModel())
|
|
*Flags |= LC_SEL_MODEL_SELECTED;
|
|
|
|
if (Piece->IsHidden())
|
|
*Flags |= LC_SEL_HIDDEN | LC_SEL_HIDDEN_SELECTED;
|
|
else
|
|
*Flags |= LC_SEL_VISIBLE_SELECTED;
|
|
|
|
*Flags |= LC_SEL_PIECE | LC_SEL_SELECTED;
|
|
|
|
if (Piece->AreTrainTrackConnectionsVisible())
|
|
*Flags |= LC_SEL_TRAIN_TRACK_VISIBLE;
|
|
|
|
if (Piece->CanAddControlPoint())
|
|
*Flags |= LC_SEL_CAN_ADD_CONTROL_POINT;
|
|
|
|
if (Piece->CanRemoveControlPoint())
|
|
*Flags |= LC_SEL_CAN_REMOVE_CONTROL_POINT;
|
|
|
|
if (Piece->GetGroup() != nullptr)
|
|
{
|
|
*Flags |= LC_SEL_GROUPED;
|
|
if (Piece->IsFocused())
|
|
*Flags |= LC_SEL_FOCUS_GROUPED;
|
|
}
|
|
|
|
if (First)
|
|
{
|
|
Group = Piece->GetGroup();
|
|
First = false;
|
|
}
|
|
else
|
|
{
|
|
if (Group != Piece->GetGroup())
|
|
*Flags |= LC_SEL_CAN_GROUP;
|
|
else
|
|
if (Group == nullptr)
|
|
*Flags |= LC_SEL_CAN_GROUP;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*Flags |= LC_SEL_UNSELECTED;
|
|
|
|
if (Piece->IsHidden())
|
|
*Flags |= LC_SEL_HIDDEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
{
|
|
if (Camera->IsSelected())
|
|
{
|
|
Selection.emplace_back(Camera.get());
|
|
*Flags |= LC_SEL_SELECTED | LC_SEL_CAMERA;
|
|
|
|
if (Camera->IsFocused())
|
|
*Focus = Camera.get();
|
|
}
|
|
}
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
{
|
|
if (Light->IsSelected())
|
|
{
|
|
Selection.emplace_back(Light.get());
|
|
*Flags |= LC_SEL_SELECTED | LC_SEL_LIGHT;
|
|
|
|
if (Light->IsFocused())
|
|
*Focus = Light.get();
|
|
}
|
|
}
|
|
}
|
|
|
|
void lcModel::ClearSelectionAction()
|
|
{
|
|
BeginHistorySequence();
|
|
ClearSelection();
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::SetSelectionAndFocusAction(const std::vector<lcObject*>& Objects, lcObject* Focus, quint32 Section, lcSelectionMode SelectionMode)
|
|
{
|
|
BeginHistorySequence();
|
|
SetSelectionAndFocus(Objects, Focus, Section, SelectionMode);
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::FocusOrDeselectObjectAction(lcObject* Object, uint32_t Section, lcSelectionMode SelectionMode)
|
|
{
|
|
BeginHistorySequence();
|
|
|
|
if (Object)
|
|
{
|
|
if (!Object->IsFocused(Section))
|
|
SetFocus(Object, Section, SelectionMode);
|
|
else
|
|
SetFocus(nullptr, ~0U, SelectionMode);
|
|
}
|
|
else
|
|
{
|
|
SetFocus(nullptr, 0, SelectionMode);
|
|
}
|
|
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::SelectAllPiecesAction()
|
|
{
|
|
BeginHistorySequence();
|
|
SelectAllPieces();
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::InvertPieceSelectionAction()
|
|
{
|
|
BeginHistorySequence();
|
|
InvertPieceSelection();
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::AddToSelectionAction(const std::vector<lcObject*>& Objects)
|
|
{
|
|
BeginHistorySequence();
|
|
AddToSelection(Objects);
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::RemoveFromSelectionAction(const std::vector<lcObject*>& Objects)
|
|
{
|
|
BeginHistorySequence();
|
|
RemoveFromSelection(Objects);
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
std::vector<lcObject*> lcModel::GetSelectionModePieces(lcSelectionMode SelectionMode, const lcPiece* SelectedPiece) const
|
|
{
|
|
const lcPieceData* PieceData = SelectedPiece->mPieceData.Get();
|
|
const int ColorIndex = SelectedPiece->GetColorIndex();
|
|
std::vector<lcObject*> Pieces;
|
|
|
|
switch (SelectionMode)
|
|
{
|
|
case lcSelectionMode::Single:
|
|
break;
|
|
|
|
case lcSelectionMode::Piece:
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsVisible(mCurrentStep) && Piece->mPieceData == PieceData && Piece.get() != SelectedPiece)
|
|
Pieces.emplace_back(Piece.get());
|
|
break;
|
|
|
|
case lcSelectionMode::Color:
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsVisible(mCurrentStep) && Piece->GetColorIndex() == ColorIndex && Piece.get() != SelectedPiece)
|
|
Pieces.emplace_back(Piece.get());
|
|
break;
|
|
|
|
case lcSelectionMode::PieceColor:
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (Piece->IsVisible(mCurrentStep) && Piece->mPieceData == PieceData && Piece->GetColorIndex() == ColorIndex && Piece.get() != SelectedPiece)
|
|
Pieces.emplace_back(Piece.get());
|
|
break;
|
|
}
|
|
|
|
return Pieces;
|
|
}
|
|
|
|
void lcModel::DeselectAllObjects()
|
|
{
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
Piece->SetSelected(false);
|
|
|
|
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
|
|
Camera->SetSelected(false);
|
|
|
|
for (const std::unique_ptr<lcLight>& Light : mLights)
|
|
Light->SetSelected(false);
|
|
}
|
|
|
|
void lcModel::SetFocusedObject(lcObject* FocusObject, uint32_t FocusSection, lcSelectionMode SelectionMode)
|
|
{
|
|
lcObject* PreviousFocus = GetFocusObject();
|
|
|
|
if (PreviousFocus)
|
|
PreviousFocus->SetFocused(PreviousFocus->GetFocusSection(), false);
|
|
|
|
if (FocusObject)
|
|
{
|
|
lcPiece* Piece = dynamic_cast<lcPiece*>(FocusObject);
|
|
|
|
if (Piece)
|
|
{
|
|
if (!Piece->IsSelected())
|
|
SetObjectsSelected({ Piece }, true);
|
|
|
|
std::vector<lcObject*> Pieces = GetSelectionModePieces(SelectionMode, Piece);
|
|
|
|
SetObjectsSelected(Pieces, true);
|
|
}
|
|
|
|
FocusObject->SetFocused(FocusSection, true);
|
|
}
|
|
}
|
|
|
|
void lcModel::SetObjectsSelected(const std::vector<lcObject*>& Objects, bool Selected)
|
|
{
|
|
for (lcObject* Object : Objects)
|
|
{
|
|
if (Object->IsSelected() == Selected)
|
|
continue;
|
|
|
|
Object->SetSelected(Selected);
|
|
|
|
if (Object->IsPiece())
|
|
{
|
|
lcPiece* Piece = dynamic_cast<lcPiece*>(Object);
|
|
|
|
SelectGroup(Piece->GetTopGroup(), Selected);
|
|
}
|
|
}
|
|
}
|
|
|
|
void lcModel::SelectGroup(lcGroup* TopGroup, bool Select)
|
|
{
|
|
if (!TopGroup)
|
|
return;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
if (!Piece->IsSelected() && (Piece->GetTopGroup() == TopGroup))
|
|
Piece->SetSelected(Select);
|
|
}
|
|
|
|
void lcModel::HideSelectedPieces()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected() && !Piece->IsHidden())
|
|
{
|
|
Piece->SetHidden(true);
|
|
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
ClearSelection();
|
|
|
|
EndHistorySequence(tr("Hide Pieces"));
|
|
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::HideUnselectedPieces()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (!Piece->IsSelected() && !Piece->IsHidden())
|
|
{
|
|
Piece->SetHidden(true);
|
|
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Hide Pieces"));
|
|
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::UnhideSelectedPieces()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsSelected() && Piece->IsHidden())
|
|
{
|
|
Piece->SetHidden(false);
|
|
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Unhide Pieces"));
|
|
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::UnhideAllPieces()
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
bool Modified = false;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (Piece->IsHidden())
|
|
{
|
|
Piece->SetHidden(false);
|
|
|
|
Modified = true;
|
|
}
|
|
}
|
|
|
|
if (!Modified)
|
|
{
|
|
DiscardHistorySequence();
|
|
|
|
return;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Unhide Pieces"));
|
|
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::FindReplacePiece(bool SearchForward, bool FindAll, bool Replace)
|
|
{
|
|
if (mPieces.empty())
|
|
return;
|
|
|
|
const lcFindReplaceParams& Params = lcView::GetFindReplaceParams();
|
|
|
|
const bool ReplacePieceData = Replace && Params.ReplacePieceData;
|
|
const bool ReplaceColor = Replace && lcGetColorCode(Params.ReplaceColorIndex) != LC_COLOR_NOCOLOR;
|
|
|
|
// Check if we are supposed to actually replace something
|
|
const bool Replacing = (ReplaceColor || ReplacePieceData);
|
|
|
|
auto PieceMatches = [&Params](const lcPiece* Piece)
|
|
{
|
|
if (Params.FindPieceData && Params.FindPieceData != Piece->mPieceData)
|
|
return false;
|
|
|
|
if (!Params.FindString.isEmpty() && !lcstrcasestr(Piece->mPieceData->m_strDescription, Params.FindString.toLatin1()))
|
|
return false;
|
|
|
|
return (lcGetColorCode(Params.FindColorIndex) == LC_COLOR_NOCOLOR) || (Piece->GetColorIndex() == Params.FindColorIndex);
|
|
};
|
|
|
|
auto ReplacePiece = [&Params, ReplacePieceData, ReplaceColor](lcPiece* Piece)
|
|
{
|
|
if (ReplaceColor)
|
|
Piece->SetColorIndex(Params.ReplaceColorIndex);
|
|
|
|
if (ReplacePieceData)
|
|
Piece->SetPieceData(lcPieceDataRef(Params.ReplacePieceData), QString(), true, true);
|
|
};
|
|
|
|
size_t StartIndex = mPieces.size() - 1;
|
|
int ReplacedCount = 0;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
if (!FindAll)
|
|
{
|
|
// We have to find the currently focused piece, in order to find next/prev match and (optionally) to replace it
|
|
lcPiece* const FocusedPiece = dynamic_cast<lcPiece*>(GetFocusObject());
|
|
|
|
if (FocusedPiece)
|
|
{
|
|
for (size_t PieceIndex = 0; PieceIndex < mPieces.size(); PieceIndex++)
|
|
{
|
|
if (FocusedPiece == mPieces[PieceIndex].get())
|
|
{
|
|
StartIndex = PieceIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Replacing && PieceMatches(FocusedPiece))
|
|
{
|
|
ReplacePiece(FocusedPiece);
|
|
ReplacedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t CurrentIndex = StartIndex;
|
|
lcPiece* Focus = nullptr;
|
|
std::vector<lcObject*> Selection;
|
|
|
|
for (;;)
|
|
{
|
|
if (SearchForward)
|
|
{
|
|
CurrentIndex++;
|
|
|
|
if (CurrentIndex >= mPieces.size())
|
|
CurrentIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
if (CurrentIndex == 0)
|
|
CurrentIndex = mPieces.size();
|
|
|
|
CurrentIndex--;
|
|
}
|
|
|
|
lcPiece* Current = mPieces[CurrentIndex].get();
|
|
|
|
if (Current->IsVisible(mCurrentStep) && PieceMatches(Current))
|
|
{
|
|
if (FindAll)
|
|
{
|
|
Selection.emplace_back(Current);
|
|
if (Replacing)
|
|
{
|
|
ReplacePiece(Current);
|
|
ReplacedCount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Focus = Current;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (CurrentIndex == StartIndex)
|
|
break;
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
if (FindAll)
|
|
SetSelectionAndFocus(Selection, nullptr, 0, lcSelectionMode::Single);
|
|
else
|
|
SetSelectionAndFocus(std::vector<lcObject*>(), Focus, LC_PIECE_SECTION_POSITION, lcSelectionMode::Single);
|
|
|
|
if (ReplacedCount)
|
|
{
|
|
EndHistorySequence(tr("Replace Pieces", nullptr, ReplacedCount));
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
}
|
|
else
|
|
EndHistorySequence(tr("Selection"));
|
|
}
|
|
|
|
void lcModel::UndoAction()
|
|
{
|
|
if (mUndoHistory.empty())
|
|
return;
|
|
|
|
std::unique_ptr<lcModelHistoryEntry> Undo = std::move(mUndoHistory.front());
|
|
|
|
RunHistorySequence(Undo->HistorySequence, false);
|
|
|
|
mUndoHistory.erase(mUndoHistory.begin());
|
|
mRedoHistory.insert(mRedoHistory.begin(), std::move(Undo));
|
|
|
|
gMainWindow->UpdateModified(mProject ? mProject->IsModified() : IsModified());
|
|
gMainWindow->UpdateUndoRedo(!mUndoHistory.empty() ? mUndoHistory.front()->Description : nullptr, !mRedoHistory.empty() ? mRedoHistory.front()->Description : nullptr);
|
|
}
|
|
|
|
void lcModel::RedoAction()
|
|
{
|
|
if (mRedoHistory.empty())
|
|
return;
|
|
|
|
std::unique_ptr<lcModelHistoryEntry> Redo = std::move(mRedoHistory.front());
|
|
|
|
RunHistorySequence(Redo->HistorySequence, true);
|
|
|
|
mRedoHistory.erase(mRedoHistory.begin());
|
|
mUndoHistory.insert(mUndoHistory.begin(), std::move(Redo));
|
|
|
|
gMainWindow->UpdateModified(mProject ? mProject->IsModified() : IsModified());
|
|
gMainWindow->UpdateUndoRedo(!mUndoHistory.empty() ? mUndoHistory.front()->Description : nullptr, !mRedoHistory.empty() ? mRedoHistory.front()->Description : nullptr);
|
|
}
|
|
|
|
void lcModel::BeginMouseTool(lcTool Tool, lcView* View)
|
|
{
|
|
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:
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
break;
|
|
|
|
case lcTool::Eraser:
|
|
case lcTool::Paint:
|
|
case lcTool::ColorPicker:
|
|
break;
|
|
|
|
case lcTool::Pan:
|
|
case lcTool::Zoom:
|
|
case lcTool::RotateView:
|
|
case lcTool::Roll:
|
|
if (!View->GetCamera()->IsSimple())
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
}
|
|
break;
|
|
|
|
case lcTool::ZoomRegion:
|
|
break;
|
|
|
|
case lcTool::Count:
|
|
break;
|
|
}
|
|
|
|
mMouseToolDistance = lcVector3(0.0f, 0.0f, 0.0f);
|
|
mMouseToolMoveStartPosition = lcVector3(0.0f, 0.0f, 0.0f);
|
|
mMouseToolMoveEndPosition = lcVector3(0.0f, 0.0f, 0.0f);
|
|
mMouseToolMovePositionValid = false;
|
|
mMouseToolMoveUsesFocus = false;
|
|
|
|
if (Tool == lcTool::Move)
|
|
{
|
|
lcObject* Focus = GetFocusObject();
|
|
|
|
if (Focus && Focus->GetFocusSection() != LC_OBJECT_SECTION_INVALID)
|
|
{
|
|
mMouseToolMoveStartPosition = Focus->GetSectionPosition(Focus->GetFocusSection());
|
|
mMouseToolMovePositionValid = true;
|
|
mMouseToolMoveUsesFocus = true;
|
|
}
|
|
else
|
|
{
|
|
lcMatrix33 RelativeRotation;
|
|
mMouseToolMovePositionValid = GetMoveRotateTransform(mMouseToolMoveStartPosition, RelativeRotation);
|
|
}
|
|
|
|
mMouseToolMoveEndPosition = mMouseToolMoveStartPosition;
|
|
}
|
|
|
|
mMouseToolFirstMove = true;
|
|
}
|
|
|
|
void lcModel::EndMouseTool(lcTool Tool, lcView* View, bool Accept)
|
|
{
|
|
if (!Accept)
|
|
{
|
|
RevertHistorySequence();
|
|
return;
|
|
}
|
|
|
|
const lcCamera* Camera = View->GetCamera();
|
|
|
|
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:
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Move"));
|
|
break;
|
|
|
|
case lcTool::Rotate:
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Rotate"));
|
|
break;
|
|
|
|
case lcTool::Eraser:
|
|
case lcTool::Paint:
|
|
case lcTool::ColorPicker:
|
|
break;
|
|
|
|
case lcTool::Zoom:
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Zoom"));
|
|
}
|
|
break;
|
|
|
|
case lcTool::Pan:
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Pan"));
|
|
}
|
|
break;
|
|
|
|
case lcTool::RotateView:
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Orbit"));
|
|
}
|
|
break;
|
|
|
|
case lcTool::Roll:
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Roll"));
|
|
}
|
|
break;
|
|
|
|
case lcTool::ZoomRegion:
|
|
break;
|
|
|
|
case lcTool::Count:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool lcModel::InsertPieceToolClicked()
|
|
{
|
|
if (mPreviewInsertPieceInfo.empty())
|
|
return false;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
lcPiece* Piece = nullptr;
|
|
|
|
for (const lcPreviewInsertPieceInfo& InsertPieceInfo : mPreviewInsertPieceInfo)
|
|
{
|
|
Piece = new lcPiece(InsertPieceInfo.PieceData);
|
|
|
|
Piece->Initialize(InsertPieceInfo.Transform, mCurrentStep);
|
|
Piece->SetColorIndex(InsertPieceInfo.ColorIndex);
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
|
|
AddPiece(Piece);
|
|
}
|
|
|
|
SetPreviewInsertPieceInfo(std::vector<lcInsertPieceInfo>());
|
|
|
|
EndEditHistory();
|
|
|
|
SetSelectionAndFocus(std::vector<lcObject*>(), Piece, LC_PIECE_SECTION_POSITION, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Add Piece"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateInUseCategory();
|
|
|
|
UpdateTrainTrackConnections(Piece, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
void lcModel::InsertCameraToolClicked(const lcVector3& Position)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
lcCamera* Camera = new lcCamera(false, Position, GetSelectionOrModelCenter());
|
|
|
|
Camera->CreateName(mCameras);
|
|
mCameras.emplace_back(Camera);
|
|
|
|
EndEditHistory();
|
|
|
|
SetSelectionAndFocus(std::vector<lcObject*>(), Camera, LC_CAMERA_SECTION_POSITION, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Add Camera"));
|
|
}
|
|
|
|
void lcModel::InsertLightToolClicked(const lcVector3& Position, lcLightType LightType)
|
|
{
|
|
QString ActionName;
|
|
|
|
switch (LightType)
|
|
{
|
|
case lcLightType::Point:
|
|
ActionName = tr("Add Point Light");
|
|
break;
|
|
|
|
case lcLightType::Spot:
|
|
ActionName = tr("Add Spotlight");
|
|
break;
|
|
|
|
case lcLightType::Directional:
|
|
ActionName = tr("Add Directional Light");
|
|
break;
|
|
|
|
case lcLightType::Area:
|
|
ActionName = tr("Add Area Light");
|
|
break;
|
|
|
|
case lcLightType::Count:
|
|
return;
|
|
}
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
lcLight* Light = new lcLight(Position, LightType);
|
|
|
|
Light->CreateName(mLights);
|
|
mLights.emplace_back(Light);
|
|
|
|
EndEditHistory();
|
|
|
|
SetSelectionAndFocus(std::vector<lcObject*>(), Light, LC_LIGHT_SECTION_POSITION, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(ActionName);
|
|
}
|
|
|
|
void lcModel::UpdateMoveTool(const lcVector3& Distance, bool AllowRelative, bool AlternateButtonDrag)
|
|
{
|
|
const lcVector3 PieceDistance = SnapPosition(Distance) - SnapPosition(mMouseToolDistance);
|
|
const lcVector3 ObjectDistance = Distance - mMouseToolDistance;
|
|
|
|
lcModelTransformFlags Flags = (AllowRelative ? lcModelTransformFlag::Relative : lcModelTransformFlag::None) | (AlternateButtonDrag ? lcModelTransformFlag::RotatePivotPoint : lcModelTransformFlag::None) | (mMouseToolFirstMove ? lcModelTransformFlag::FirstMove : lcModelTransformFlag::None);
|
|
MoveSelectedObjects(PieceDistance, ObjectDistance, Flags, lcModelHistoryEditMerge::None);
|
|
|
|
if (mMouseToolMovePositionValid)
|
|
{
|
|
if (mMouseToolMoveUsesFocus)
|
|
{
|
|
lcObject* Focus = GetFocusObject();
|
|
|
|
if (Focus && Focus->GetFocusSection() != LC_OBJECT_SECTION_INVALID)
|
|
mMouseToolMoveEndPosition = Focus->GetSectionPosition(Focus->GetFocusSection());
|
|
else
|
|
mMouseToolMovePositionValid = false;
|
|
}
|
|
else
|
|
{
|
|
lcMatrix33 RelativeRotation;
|
|
mMouseToolMovePositionValid = GetMoveRotateTransform(mMouseToolMoveEndPosition, RelativeRotation);
|
|
}
|
|
}
|
|
|
|
mMouseToolDistance = Distance;
|
|
mMouseToolFirstMove = false;
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::UpdateFreeMoveTool(lcPiece* MousePiece, const lcMatrix44& StartTransform, const lcMatrix44& NewTransform, bool IsConnection, bool AlternateButtonDrag)
|
|
{
|
|
lcMatrix33 NewRotation = IsConnection ? lcMatrix33(NewTransform) : lcMatrix33(StartTransform);
|
|
lcMatrix33 CurrentRotation = lcMatrix33(MousePiece->mModelWorld);
|
|
|
|
if (!lcMatrix33Similar(CurrentRotation, NewRotation))
|
|
{
|
|
const lcVector3 Distance = NewTransform.GetTranslation() - MousePiece->mModelWorld.GetTranslation();
|
|
|
|
lcMatrix33 RotationMatrix = lcMul(lcMatrix33AffineInverse(CurrentRotation), NewRotation);
|
|
|
|
const lcVector3& Center = NewTransform.GetTranslation();
|
|
lcMatrix33 RelativeRotation = MousePiece->GetRelativeRotation();
|
|
|
|
lcMatrix33 WorldToFocusMatrix = lcMatrix33AffineInverse(RelativeRotation);
|
|
// Rotate() expects the rotation in the focus frame.
|
|
RotationMatrix = lcMul(RelativeRotation, RotationMatrix);
|
|
|
|
int Flags;
|
|
std::vector<lcObject*> Selection;
|
|
lcObject* Focus;
|
|
|
|
GetSelectionInformation(&Flags, Selection, &Focus);
|
|
|
|
for (lcObject* Object : Selection)
|
|
{
|
|
if (Object->IsPiece())
|
|
{
|
|
lcPiece* Piece = (lcPiece*)Object;
|
|
|
|
Piece->MoveSelected(mCurrentStep, gMainWindow->GetAddKeys(), Distance);
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
|
|
Piece->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
}
|
|
else if (Object->IsCamera())
|
|
{
|
|
lcCamera* Camera = (lcCamera*)Object;
|
|
|
|
Camera->MoveSelected(mCurrentStep, gMainWindow->GetAddKeys(), Distance);
|
|
Camera->UpdatePosition(mCurrentStep);
|
|
|
|
Camera->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Camera->UpdatePosition(mCurrentStep);
|
|
}
|
|
else if (Object->IsLight())
|
|
{
|
|
lcLight* Light = (lcLight*)Object;
|
|
|
|
Light->MoveSelected(mCurrentStep, gMainWindow->GetAddKeys(), Distance, mMouseToolFirstMove);
|
|
Light->UpdatePosition(mCurrentStep);
|
|
|
|
Light->Rotate(mCurrentStep, gMainWindow->GetAddKeys(), RotationMatrix, Center, WorldToFocusMatrix);
|
|
Light->UpdatePosition(mCurrentStep);
|
|
}
|
|
}
|
|
|
|
mMouseToolDistance = NewTransform.GetTranslation() - StartTransform.GetTranslation();
|
|
mMouseToolFirstMove = false;
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
UpdateAllViews();
|
|
}
|
|
else
|
|
{
|
|
lcVector3 Distance = NewTransform.GetTranslation() - StartTransform.GetTranslation();
|
|
|
|
UpdateMoveTool(Distance, false, AlternateButtonDrag);
|
|
}
|
|
}
|
|
|
|
void lcModel::UpdateRotateTool(const lcVector3& Axis, float Angle, bool AlternateButtonDrag, bool Relative)
|
|
{
|
|
const lcVector3 Angles(Angle, Angle, Angle);
|
|
const float Delta = SnapRotation(Angles)[0] - SnapRotation(mMouseToolDistance)[0];
|
|
|
|
if (Delta != 0.0f)
|
|
RotateSelectedObjects(lcMatrix33FromAxisAngle(Axis, Delta * LC_DTOR), (Relative ? lcModelTransformFlag::Relative : lcModelTransformFlag::None) | (AlternateButtonDrag ? lcModelTransformFlag::RotatePivotPoint : lcModelTransformFlag::None), lcModelHistoryEditMerge::None);
|
|
|
|
mMouseToolDistance = Angles;
|
|
mMouseToolFirstMove = false;
|
|
}
|
|
|
|
void lcModel::UpdateRotateTool(const lcMatrix33& RotationMatrix, bool AlternateButtonDrag, bool Relative)
|
|
{
|
|
RotateSelectedObjects(RotationMatrix, (Relative ? lcModelTransformFlag::Relative : lcModelTransformFlag::None) | (AlternateButtonDrag ? lcModelTransformFlag::RotatePivotPoint : lcModelTransformFlag::None), lcModelHistoryEditMerge::None);
|
|
mMouseToolFirstMove = false;
|
|
}
|
|
|
|
void lcModel::UpdateScaleTool(const float Scale)
|
|
{
|
|
ScaleSelectedPieces(Scale);
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::EraserToolClicked(lcObject* Object)
|
|
{
|
|
if (!Object)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
switch (Object->GetType())
|
|
{
|
|
case lcObjectType::Piece:
|
|
for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); ++PieceIt)
|
|
if (PieceIt->get() == Object)
|
|
{
|
|
mPieces.erase(PieceIt);
|
|
RemoveEmptyGroups();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case lcObjectType::Camera:
|
|
{
|
|
std::vector<lcView*> Views = lcView::GetModelViews(this);
|
|
|
|
for (lcView* View : Views)
|
|
{
|
|
lcCamera* Camera = View->GetCamera();
|
|
|
|
if (Camera == Object)
|
|
View->SetCamera(Camera, true);
|
|
}
|
|
|
|
for (std::vector<std::unique_ptr<lcCamera>>::iterator CameraIt = mCameras.begin(); CameraIt != mCameras.end(); CameraIt++)
|
|
{
|
|
if (CameraIt->get() == Object)
|
|
{
|
|
mCameras.erase(CameraIt);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case lcObjectType::Light:
|
|
for (std::vector<std::unique_ptr<lcLight>>::iterator LightIt = mLights.begin(); LightIt != mLights.end(); LightIt++)
|
|
{
|
|
if (LightIt->get() == Object)
|
|
{
|
|
mLights.erase(LightIt);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Delete"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
}
|
|
|
|
void lcModel::PaintToolClicked(lcObject* Object)
|
|
{
|
|
if (!Object || !Object->IsPiece())
|
|
return;
|
|
|
|
lcPiece* Piece = (lcPiece*)Object;
|
|
|
|
if (Piece->GetColorIndex() != gMainWindow->mColorIndex)
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
Piece->SetColorIndex(gMainWindow->mColorIndex);
|
|
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Paint"));
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
gMainWindow->UpdateTimeline(false, true);
|
|
}
|
|
}
|
|
|
|
void lcModel::ColorPickerToolClicked(const lcObject* Object)
|
|
{
|
|
if (!Object || !Object->IsPiece())
|
|
return;
|
|
|
|
const lcPiece* Piece = (lcPiece*)Object;
|
|
|
|
gMainWindow->SetColorIndex(Piece->GetColorIndex());
|
|
}
|
|
|
|
void lcModel::UpdateZoomTool(lcCamera* Camera, float Mouse)
|
|
{
|
|
Camera->Zoom(Mouse - mMouseToolDistance.x, mCurrentStep, gMainWindow->GetAddKeys());
|
|
mMouseToolDistance.x = Mouse;
|
|
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::UpdatePanTool(lcCamera* Camera, const lcVector3& Distance)
|
|
{
|
|
Camera->Pan(Distance, mCurrentStep, gMainWindow->GetAddKeys());
|
|
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::UpdateOrbitTool(lcCamera* Camera, float MouseX, float MouseY)
|
|
{
|
|
lcVector3 Center;
|
|
GetSelectionCenter(Center);
|
|
|
|
Camera->Orbit(MouseX - mMouseToolDistance.x, MouseY - mMouseToolDistance.y, Center, mCurrentStep, gMainWindow->GetAddKeys());
|
|
mMouseToolDistance.x = MouseX;
|
|
mMouseToolDistance.y = MouseY;
|
|
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::UpdateRollTool(lcCamera* Camera, float Mouse)
|
|
{
|
|
Camera->Roll(Mouse - mMouseToolDistance.x, mCurrentStep, gMainWindow->GetAddKeys());
|
|
mMouseToolDistance.x = Mouse;
|
|
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::ZoomRegionToolClicked(lcView* View, float AspectRatio, const lcVector3& Position, const lcVector3& TargetPosition, const lcVector3* Corners)
|
|
{
|
|
lcCamera* Camera = View->GetCamera();
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
}
|
|
|
|
Camera->ZoomRegion(AspectRatio, Position, TargetPosition, Corners, mCurrentStep, gMainWindow->GetAddKeys());
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Zoom"));
|
|
}
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
UpdateAllViews();
|
|
}
|
|
|
|
void lcModel::LookAt(lcCamera* Camera)
|
|
{
|
|
lcVector3 Center;
|
|
|
|
if (!GetSelectionCenter(Center))
|
|
{
|
|
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
|
|
if (GetVisiblePiecesBoundingBox(Min, Max))
|
|
Center = (Min + Max) / 2.0f;
|
|
else
|
|
Center = lcVector3(0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
}
|
|
|
|
Camera->Center(Center, mCurrentStep, gMainWindow->GetAddKeys());
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
UpdateAllViews();
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Look At"));
|
|
}
|
|
}
|
|
|
|
void lcModel::MoveCamera(lcCamera* Camera, const lcVector3& Direction)
|
|
{
|
|
if (!Camera->IsSimple())
|
|
{
|
|
BeginHistorySequence();
|
|
BeginMergeableEditHistory(lcModelHistoryEditMerge::KeyboardMoveCamera, lcObjectPropertyId::Count);
|
|
}
|
|
|
|
Camera->MoveRelative(Direction, mCurrentStep, gMainWindow->GetAddKeys());
|
|
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
UpdateAllViews();
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Move"));
|
|
}
|
|
}
|
|
|
|
void lcModel::ZoomExtents(lcCamera* Camera, float Aspect, const lcMatrix44& WorldMatrix, lcGeometryBoundsMode Mode, bool AdjustFarPlane)
|
|
{
|
|
std::vector<lcVector3> Points = GetPiecesBoundingBoxPoints(Mode);
|
|
|
|
if (Points.empty())
|
|
return;
|
|
|
|
lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
|
|
for (lcVector3& Point : Points)
|
|
{
|
|
Point = lcMul31(Point, WorldMatrix);
|
|
|
|
Min = lcMin(Point, Min);
|
|
Max = lcMax(Point, Max);
|
|
}
|
|
|
|
const lcVector3 Center = (Min + Max) / 2.0f;
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
}
|
|
|
|
Camera->ZoomExtents(Aspect, Center, Points, mCurrentStep, gMainWindow ? gMainWindow->GetAddKeys() : false, AdjustFarPlane);
|
|
|
|
if (!mIsPreview && gMainWindow)
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
|
|
UpdateAllViews();
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Zoom Extents"));
|
|
}
|
|
}
|
|
|
|
void lcModel::Zoom(lcCamera* Camera, float Amount)
|
|
{
|
|
if (!Camera->IsSimple())
|
|
{
|
|
BeginHistorySequence();
|
|
BeginMergeableEditHistory(lcModelHistoryEditMerge::KeyboardZoom, lcObjectPropertyId::Count);
|
|
}
|
|
|
|
Camera->Zoom(Amount, mCurrentStep, gMainWindow->GetAddKeys());
|
|
|
|
if (!mIsPreview)
|
|
gMainWindow->UpdateSelectedObjects(false);
|
|
|
|
UpdateAllViews();
|
|
|
|
if (!Camera->IsSimple())
|
|
{
|
|
EndEditHistory();
|
|
EndHistorySequence(tr("Zoom"));
|
|
}
|
|
}
|
|
|
|
void lcModel::ShowPropertiesDialog()
|
|
{
|
|
lcPropertiesDialogOptions Options;
|
|
|
|
Options.Properties = mProperties;
|
|
Options.BoundingBox = GetAllPiecesBoundingBox();
|
|
|
|
GetPartsList(gDefaultColor, true, false, Options.PartsList);
|
|
|
|
lcPropertiesDialog Dialog(gMainWindow, &Options);
|
|
if (Dialog.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
if (mProperties == Options.Properties)
|
|
return;
|
|
|
|
BeginHistorySequence();
|
|
SetModelProperties(Options.Properties);
|
|
EndHistorySequence(tr("Change Model Properties"));
|
|
}
|
|
|
|
void lcModel::ShowArrayDialog()
|
|
{
|
|
lcVector3 Center;
|
|
|
|
if (!GetPieceFocusOrSelectionCenter(Center))
|
|
{
|
|
QMessageBox::information(gMainWindow, tr("Piece Array"), tr("No pieces selected."));
|
|
return;
|
|
}
|
|
|
|
lcArrayDialog Dialog(gMainWindow);
|
|
|
|
if (Dialog.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
if (Dialog.mCounts[0] * Dialog.mCounts[1] * Dialog.mCounts[2] < 2)
|
|
{
|
|
QMessageBox::information(gMainWindow, tr("Piece Array"), tr("Array only has 1 element or less. No pieces added."));
|
|
return;
|
|
}
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
std::vector<lcObject*> NewPieces;
|
|
|
|
for (int Step1 = 0; Step1 < Dialog.mCounts[0]; Step1++)
|
|
{
|
|
for (int Step2 = 0; Step2 < Dialog.mCounts[1]; Step2++)
|
|
{
|
|
for (int Step3 = (Step1 == 0 && Step2 == 0) ? 1 : 0; Step3 < Dialog.mCounts[2]; Step3++)
|
|
{
|
|
lcMatrix44 ModelWorld;
|
|
lcVector3 Position;
|
|
|
|
lcVector3 RotationAngles = Dialog.mRotations[0] * Step1 + Dialog.mRotations[1] * Step2 + Dialog.mRotations[2] * Step3;
|
|
const lcVector3 Offset = Dialog.mOffsets[0] * Step1 + Dialog.mOffsets[1] * Step2 + Dialog.mOffsets[2] * Step3;
|
|
|
|
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
|
|
{
|
|
if (!Piece->IsSelected())
|
|
continue;
|
|
|
|
ModelWorld = Piece->mModelWorld;
|
|
|
|
ModelWorld.r[3] -= lcVector4(Center, 0.0f);
|
|
ModelWorld = lcMul(ModelWorld, lcMatrix44RotationX(RotationAngles[0] * LC_DTOR));
|
|
ModelWorld = lcMul(ModelWorld, lcMatrix44RotationY(RotationAngles[1] * LC_DTOR));
|
|
ModelWorld = lcMul(ModelWorld, lcMatrix44RotationZ(RotationAngles[2] * LC_DTOR));
|
|
ModelWorld.r[3] += lcVector4(Center, 0.0f);
|
|
|
|
Position = lcVector3(ModelWorld.r[3].x, ModelWorld.r[3].y, ModelWorld.r[3].z);
|
|
ModelWorld.SetTranslation(Position + Offset);
|
|
|
|
lcPiece* NewPiece = new lcPiece(nullptr);
|
|
NewPiece->SetPieceData(Piece->mPieceData, Piece->GetID(), true, true);
|
|
NewPiece->Initialize(ModelWorld, mCurrentStep);
|
|
NewPiece->SetColorIndex(Piece->GetColorIndex());
|
|
|
|
NewPieces.emplace_back(NewPiece);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t PieceIdx = 0; PieceIdx < NewPieces.size(); PieceIdx++)
|
|
{
|
|
lcPiece* Piece = (lcPiece*)NewPieces[PieceIdx];
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
AddPiece(Piece);
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
AddToSelection(NewPieces);
|
|
|
|
EndHistorySequence(tr("Piece Array"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
}
|
|
|
|
void lcModel::ShowMinifigDialog()
|
|
{
|
|
lcMinifigDialog Dialog(gMainWindow);
|
|
|
|
if (Dialog.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
gMainWindow->GetActiveView()->MakeCurrent();
|
|
|
|
BeginHistorySequence();
|
|
BeginEditHistory();
|
|
|
|
lcGroup* Group = AddGroup(tr("Minifig #"), nullptr);
|
|
std::vector<lcObject*> Pieces;
|
|
lcMinifig& Minifig = Dialog.mMinifigWizard->mMinifig;
|
|
|
|
Pieces.reserve(LC_MFW_NUMITEMS);
|
|
|
|
for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
|
|
{
|
|
if (!Minifig.PieceData[PartIndex])
|
|
continue;
|
|
|
|
lcPiece* Piece = new lcPiece(Minifig.PieceData[PartIndex]);
|
|
|
|
Piece->Initialize(Minifig.Matrices[PartIndex], mCurrentStep);
|
|
Piece->SetColorIndex(Minifig.ColorIndices[PartIndex]);
|
|
Piece->SetGroup(Group);
|
|
AddPiece(Piece);
|
|
Piece->UpdatePosition(mCurrentStep);
|
|
|
|
Pieces.emplace_back(Piece);
|
|
}
|
|
|
|
EndEditHistory();
|
|
|
|
SetSelectionAndFocus(Pieces, nullptr, 0, lcSelectionMode::Single);
|
|
|
|
EndHistorySequence(tr("Add Minifig"));
|
|
|
|
gMainWindow->UpdateTimeline(false, false);
|
|
}
|
|
|
|
void lcModel::SetMinifig(const lcMinifig& Minifig)
|
|
{
|
|
DeleteModel();
|
|
|
|
std::vector<lcObject*> Pieces;
|
|
Pieces.reserve(LC_MFW_NUMITEMS);
|
|
|
|
for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
|
|
{
|
|
if (!Minifig.PieceData[PartIdx])
|
|
continue;
|
|
|
|
lcPiece* Piece = new lcPiece(Minifig.PieceData[PartIdx]);
|
|
|
|
Piece->Initialize(Minifig.Matrices[PartIdx], 1);
|
|
Piece->SetColorIndex(Minifig.ColorIndices[PartIdx]);
|
|
AddPiece(Piece);
|
|
Piece->UpdatePosition(1);
|
|
|
|
Pieces.emplace_back(Piece);
|
|
}
|
|
}
|
|
|
|
void lcModel::SetPreviewPieceData(lcPieceData* PieceData, int ColorIndex)
|
|
{
|
|
DeleteModel();
|
|
|
|
lcPiece* Piece = new lcPiece(PieceData);
|
|
|
|
Piece->Initialize(lcMatrix44Identity(), 1);
|
|
Piece->SetColorIndex(ColorIndex);
|
|
AddPiece(Piece);
|
|
Piece->UpdatePosition(1);
|
|
|
|
mCurrentStep = LC_STEP_MAX;
|
|
CalculateStep(LC_STEP_MAX);
|
|
}
|
|
|
|
void lcModel::UpdateInterface()
|
|
{
|
|
if (!gMainWindow)
|
|
return;
|
|
|
|
gMainWindow->UpdateTimeline(true, false);
|
|
gMainWindow->UpdateUndoRedo(!mUndoHistory.empty() ? mUndoHistory[0]->Description : nullptr, !mRedoHistory.empty() ? mRedoHistory[0]->Description : nullptr);
|
|
gMainWindow->UpdatePaste(!gApplication->mClipboard.isEmpty());
|
|
gMainWindow->UpdateCategories();
|
|
gMainWindow->UpdateTitle();
|
|
gMainWindow->SetTool(gMainWindow->GetTool());
|
|
|
|
gMainWindow->UpdateSelectedObjects(true);
|
|
gMainWindow->SetTransformType(gMainWindow->GetTransformType());
|
|
gMainWindow->UpdateLockSnap();
|
|
gMainWindow->UpdateSnap();
|
|
gMainWindow->UpdateModels();
|
|
gMainWindow->UpdateShadingMode();
|
|
gMainWindow->UpdateCurrentStep();
|
|
gMainWindow->UpdateSelectionMode();
|
|
}
|