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

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

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

534 lines
15 KiB
C++

#include "lc_global.h"
#include "minifig.h"
#include "lc_colors.h"
#include "pieceinf.h"
#include "lc_model.h"
#include "lc_library.h"
#include "lc_application.h"
#include "lc_file.h"
#include "lc_profile.h"
const char* MinifigWizard::mSectionNames[LC_MFW_NUMITEMS] =
{
"HATS", // LC_MFW_HATS
"HATS2", // LC_MFW_HATS2
"HEAD", // LC_MFW_HEAD
"NECK", // LC_MFW_NECK
"BODY", // LC_MFW_BODY
"BODY2", // LC_MFW_BODY2
"BODY3", // LC_MFW_BODY3
"RARM", // LC_MFW_RARM
"LARM", // LC_MFW_LARM
"RHAND", // LC_MFW_RHAND
"LHAND", // LC_MFW_LHAND
"RHANDA", // LC_MFW_RHANDA
"LHANDA", // LC_MFW_LHANDA
"RLEG", // LC_MFW_RLEG
"LLEG", // LC_MFW_LLEG
"RLEGA", // LC_MFW_RLEGA
"LLEGA", // LC_MFW_LLEGA
};
MinifigWizard::MinifigWizard()
: mModel(new lcModel(QString(), nullptr, false))
{
LoadSettings();
LoadTemplates();
lcMinifigTemplate& Template = mTemplates[GetDefaultTemplateName()];
const int ColorCodes[LC_MFW_NUMITEMS] = { 4, 7, 14, 7, 1, 0, 7, 4, 4, 14, 14, 7, 7, 0, 0, 7, 7 };
const char* const Pieces[LC_MFW_NUMITEMS] = { "3624.dat", "", "3626bp01.dat", "", "973.dat", "3815.dat", "", "3819.dat", "3818.dat", "3820.dat", "3820.dat", "", "", "3817.dat", "3816.dat", "", "" };
for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
{
Template.Parts[PartIndex] = Pieces[PartIndex];
Template.ColorCodes[PartIndex] = ColorCodes[PartIndex];
Template.Angles[PartIndex] = 0.0f;
}
for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
{
mMinifig.Parts[PartIndex] = nullptr;
mMinifig.ColorIndices[PartIndex] = gDefaultColor;
mMinifig.Angles[PartIndex] = 0.0f;
mMinifig.Matrices[PartIndex] = lcMatrix44Identity();
}
}
MinifigWizard::~MinifigWizard()
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
for (int i = 0; i < LC_MFW_NUMITEMS; i++)
if (mMinifig.Parts[i])
Library->ReleasePieceInfo(mMinifig.Parts[i]); // todo: don't call ReleasePieceInfo here because it may release textures and they need a GL context current
SaveTemplates();
}
QString MinifigWizard::GetDefaultTemplateName()
{
return QCoreApplication::translate("MinifigWizard", "Default");
}
void MinifigWizard::LoadSettings()
{
QString CustomSettingsPath = lcGetProfileString(LC_PROFILE_MINIFIG_SETTINGS);
if (!CustomSettingsPath.isEmpty())
{
lcDiskFile DiskSettings(CustomSettingsPath);
if (DiskSettings.Open(QIODevice::ReadOnly))
{
ParseSettings(DiskSettings);
return;
}
}
lcDiskFile MinifigFile(":/resources/minifig.ini");
if (MinifigFile.Open(QIODevice::ReadOnly))
ParseSettings(MinifigFile);
}
void MinifigWizard::ParseSettings(lcFile& Settings)
{
for (int SectionIndex = 0; SectionIndex < LC_MFW_NUMITEMS; SectionIndex++)
{
std::vector<lcMinifigPieceInfo>& InfoArray = mSettings[SectionIndex];
InfoArray.clear();
Settings.Seek(0, SEEK_SET);
char Line[1024];
bool FoundSection = false;
const char* SectionName = mSectionNames[SectionIndex];
const size_t SectionNameLength = strlen(SectionName);
while (Settings.ReadLine(Line, sizeof(Line)))
{
if (Line[0] == '[' && !strncmp(Line + 1, SectionName, SectionNameLength) && Line[SectionNameLength + 1] == ']')
{
FoundSection = true;
break;
}
}
if (!FoundSection)
{
lcMinifigPieceInfo MinifigInfo;
strncpy(MinifigInfo.Description, "None", sizeof(MinifigInfo.Description));
MinifigInfo.Description[sizeof(MinifigInfo.Description)-1] = 0;
MinifigInfo.Offset = lcMatrix44Identity();
MinifigInfo.Info = nullptr;
InfoArray.emplace_back(std::move(MinifigInfo));
continue;
}
while (Settings.ReadLine(Line, sizeof(Line)))
{
if (Line[0] == '[')
break;
char* DescriptionStart = strchr(Line, '"');
if (!DescriptionStart)
continue;
DescriptionStart++;
char* DescriptionEnd = strchr(DescriptionStart, '"');
if (!DescriptionEnd)
continue;
*DescriptionEnd = 0;
DescriptionEnd++;
char* NameStart = strchr(DescriptionEnd, '"');
if (!NameStart)
continue;
NameStart++;
char* NameEnd = strchr(NameStart, '"');
if (!NameEnd)
continue;
*NameEnd = 0;
NameEnd++;
PieceInfo* Info = lcGetPiecesLibrary()->FindPiece(NameStart, nullptr, false, false);
if (!Info && *NameStart)
continue;
float Mat[12];
int Flags;
if (sscanf(NameEnd, "%d %g %g %g %g %g %g %g %g %g %g %g %g",
&Flags, &Mat[0], &Mat[1], &Mat[2], &Mat[3], &Mat[4], &Mat[5], &Mat[6],
&Mat[7], &Mat[8], &Mat[9], &Mat[10], &Mat[11]) != 13)
continue;
lcMatrix44 Offset = lcMatrix44Identity();
float* OffsetMatrix = &Offset[0][0];
OffsetMatrix[0] = Mat[0];
OffsetMatrix[8] = -Mat[1];
OffsetMatrix[4] = Mat[2];
OffsetMatrix[2] = -Mat[3];
OffsetMatrix[10] = Mat[4];
OffsetMatrix[6] = -Mat[5];
OffsetMatrix[1] = Mat[6];
OffsetMatrix[9] = -Mat[7];
OffsetMatrix[5] = Mat[8];
OffsetMatrix[12] = Mat[9];
OffsetMatrix[14] = -Mat[10];
OffsetMatrix[13] = Mat[11];
lcMinifigPieceInfo MinifigInfo;
strncpy(MinifigInfo.Description, DescriptionStart, sizeof(MinifigInfo.Description));
MinifigInfo.Description[sizeof(MinifigInfo.Description)-1] = 0;
MinifigInfo.Offset = Offset;
MinifigInfo.Info = Info;
InfoArray.emplace_back(std::move(MinifigInfo));
}
}
}
void MinifigWizard::LoadTemplate(const QString& TemplateName)
{
const std::map<QString, lcMinifigTemplate>::iterator& TemplateIt = mTemplates.find(TemplateName);
if (TemplateIt == mTemplates.end())
return;
const lcMinifigTemplate& Template = TemplateIt->second;
lcPiecesLibrary* Library = lcGetPiecesLibrary();
for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
{
mMinifig.ColorIndices[PartIndex] = lcGetColorIndex(Template.ColorCodes[PartIndex]);
mMinifig.Angles[PartIndex] = Template.Angles[PartIndex];
mMinifig.Matrices[PartIndex] = lcMatrix44Identity();
if (mMinifig.Parts[PartIndex])
Library->ReleasePieceInfo(mMinifig.Parts[PartIndex]);
PieceInfo* Info = Library->FindPiece(Template.Parts[PartIndex].toLatin1(), nullptr, false, false);
mMinifig.Parts[PartIndex] = Info;
if (Info)
Library->LoadPieceInfo(Info, lcPieceLoadPriority::Normal);
}
std::vector<PieceInfo*> Required;
for (PieceInfo* Info : mMinifig.Parts)
if (Info)
Required.push_back(Info);
Library->WaitForPieces(Required);
Calculate();
}
void MinifigWizard::SaveTemplate(const QString& TemplateName, const lcMinifigTemplate& Template)
{
mTemplates[TemplateName] = Template;
}
void MinifigWizard::DeleteTemplate(const QString& TemplateName)
{
mTemplates.erase(TemplateName);
}
void MinifigWizard::AddTemplatesJson(const QByteArray& TemplateData)
{
QJsonDocument Document = QJsonDocument::fromJson(TemplateData);
QJsonObject RootObject = Document.object();
int Version = RootObject["Version"].toInt(0);
QJsonObject TemplatesObject;
if (Version > 0)
TemplatesObject = RootObject["Templates"].toObject();
else
TemplatesObject = RootObject;
for (QJsonObject::const_iterator ElementIt = TemplatesObject.constBegin(); ElementIt != TemplatesObject.constEnd(); ElementIt++)
{
if (!ElementIt.value().isObject())
continue;
QJsonObject TemplateObject = ElementIt.value().toObject();
lcMinifigTemplate Template;
for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
{
QJsonObject PartObject = TemplateObject.value(QLatin1String(mSectionNames[PartIdx])).toObject();
Template.Parts[PartIdx] = PartObject["Id"].toString();
Template.ColorCodes[PartIdx] = PartObject["Color"].toInt();
Template.Angles[PartIdx] = PartObject["Angle"].toDouble();
}
mTemplates.emplace(ElementIt.key(), std::move(Template));
}
}
QByteArray MinifigWizard::GetTemplatesJson() const
{
QJsonObject TemplatesObject;
for (const auto& TemplateEntry : mTemplates)
{
const lcMinifigTemplate& Template = TemplateEntry.second;
QJsonObject TemplateObject;
for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
{
QJsonObject PartObject;
PartObject["Id"] = Template.Parts[PartIdx];
PartObject["Color"] = Template.ColorCodes[PartIdx];
PartObject["Angle"] = Template.Angles[PartIdx];
TemplateObject[QLatin1String(mSectionNames[PartIdx])] = PartObject;
}
TemplatesObject[TemplateEntry.first] = TemplateObject;
}
QJsonObject RootObject;
RootObject["Templates"] = TemplatesObject;
RootObject["Version"] = 1;
return QJsonDocument(RootObject).toJson();
}
void MinifigWizard::LoadTemplates()
{
mTemplates.clear();
QSettings Settings;
Settings.beginGroup("Minifig");
QByteArray TemplateData = Settings.value("Templates").toByteArray();
AddTemplatesJson(TemplateData);
}
void MinifigWizard::SaveTemplates()
{
QSettings Settings;
Settings.beginGroup("Minifig");
Settings.setValue("Templates", GetTemplatesJson());
}
void MinifigWizard::Calculate()
{
float HeadOffset = 0.0f;
lcMatrix44 Root, Mat, Mat2;
PieceInfo** Parts = mMinifig.Parts;
const float* Angles = mMinifig.Angles;
lcMatrix44* Matrices = mMinifig.Matrices;
const bool DroidTorso = Parts[LC_MFW_BODY] && !qstricmp(Parts[LC_MFW_BODY]->mFileName, "30375.dat");
const bool SkeletonTorso = Parts[LC_MFW_BODY] && !qstricmp(Parts[LC_MFW_BODY]->mFileName, "6260.dat");
if (Parts[LC_MFW_BODY3])
Root = lcMatrix44Translation(lcVector3(0, 0, 74.0f));
else
Root = lcMatrix44Translation(lcVector3(0, 0, 72.0f));
Matrices[LC_MFW_BODY] = lcMul(mSettings[LC_MFW_BODY][GetSelectionIndex(LC_MFW_BODY)].Offset, Root);
if (Parts[LC_MFW_NECK])
{
Matrices[LC_MFW_NECK] = lcMul(mSettings[LC_MFW_NECK][GetSelectionIndex(LC_MFW_NECK)].Offset, Root);
HeadOffset = 0.08f;
}
if (Parts[LC_MFW_HEAD])
{
Mat = lcMatrix44RotationZ(-LC_DTOR * Angles[LC_MFW_HEAD]);
Mat.SetTranslation(lcVector3(0.0f, 0.0f, 24.0f + HeadOffset));
Mat = lcMul(mSettings[LC_MFW_HEAD][GetSelectionIndex(LC_MFW_HEAD)].Offset, Mat);
Matrices[LC_MFW_HEAD] = lcMul(Mat, Root);
}
if (Parts[LC_MFW_HATS])
{
Mat = lcMatrix44RotationZ(-LC_DTOR * Angles[LC_MFW_HATS]);
Mat = lcMul(mSettings[LC_MFW_HATS][GetSelectionIndex(LC_MFW_HATS)].Offset, Mat);
Matrices[LC_MFW_HATS] = lcMul(Mat, Matrices[LC_MFW_HEAD]);
}
if (Parts[LC_MFW_HATS2])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_HATS2]);
Mat = lcMul(mSettings[LC_MFW_HATS2][GetSelectionIndex(LC_MFW_HATS2)].Offset, Mat);
Matrices[LC_MFW_HATS2] = lcMul(Mat, Matrices[LC_MFW_HATS]);
}
if (Parts[LC_MFW_RARM])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_RARM]);
if (DroidTorso || SkeletonTorso)
Mat2 = lcMatrix44Identity();
else
Mat2 = lcMatrix44RotationY(-LC_DTOR * 9.791f);
Mat2.SetTranslation(lcVector3(15.552f, 0, -8.88f));
Mat = lcMul(mSettings[LC_MFW_RARM][GetSelectionIndex(LC_MFW_RARM)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Matrices[LC_MFW_RARM] = lcMul(Mat, Root);
}
if (Parts[LC_MFW_RHAND])
{
Mat = lcMatrix44RotationY(-LC_DTOR * Angles[LC_MFW_RHAND]);
Mat2 = lcMatrix44RotationX(LC_DTOR * 45);
Mat = lcMul(mSettings[LC_MFW_RHAND][GetSelectionIndex(LC_MFW_RHAND)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Mat.SetTranslation(lcVector3(5.0f, -10.0f, -19.0f));
Matrices[LC_MFW_RHAND] = lcMul(Mat, Matrices[LC_MFW_RARM]);
}
if (Parts[LC_MFW_RHANDA])
{
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_RHANDA]);
Mat.SetTranslation(lcVector3(0, -9.25f, 0));
Mat = lcMul(mSettings[LC_MFW_RHANDA][GetSelectionIndex(LC_MFW_RHANDA)].Offset, Mat);
Mat = lcMul(Mat, lcMatrix44RotationX(LC_DTOR * 15.0f));
Matrices[LC_MFW_RHANDA] = lcMul(Mat, Matrices[LC_MFW_RHAND]);
}
if (Parts[LC_MFW_LARM])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_LARM]);
if (DroidTorso || SkeletonTorso)
Mat2 = lcMatrix44Identity();
else
Mat2 = lcMatrix44RotationY(LC_DTOR * 9.791f);
Mat2.SetTranslation(lcVector3(-15.552f, 0.0f, -8.88f));
Mat = lcMul(mSettings[LC_MFW_LARM][GetSelectionIndex(LC_MFW_LARM)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Matrices[LC_MFW_LARM] = lcMul(Mat, Root);
}
if (Parts[LC_MFW_LHAND])
{
Mat = lcMatrix44RotationY(-LC_DTOR * Angles[LC_MFW_LHAND]);
Mat2 = lcMatrix44RotationX(LC_DTOR * 45);
Mat = lcMul(mSettings[LC_MFW_LHAND][GetSelectionIndex(LC_MFW_LHAND)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Mat.SetTranslation(lcVector3(-5.0f, -10.0f, -19.0f));
Matrices[LC_MFW_LHAND] = lcMul(Mat, Matrices[LC_MFW_LARM]);
}
if (Parts[LC_MFW_LHANDA])
{
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_LHANDA]);
Mat.SetTranslation(lcVector3(0, -9.25f, 0));
Mat = lcMul(mSettings[LC_MFW_LHANDA][GetSelectionIndex(LC_MFW_LHANDA)].Offset, Mat);
Mat = lcMul(Mat, lcMatrix44RotationX(LC_DTOR * 15.0f));
Matrices[LC_MFW_LHANDA] = lcMul(Mat, Matrices[LC_MFW_LHAND]);
}
if (Parts[LC_MFW_BODY2])
{
Mat = lcMatrix44Identity();
Mat.SetTranslation(lcVector3(0, 0, -32.0f));
Mat = lcMul(mSettings[LC_MFW_BODY2][GetSelectionIndex(LC_MFW_BODY2)].Offset, Mat);
Matrices[LC_MFW_BODY2] = lcMul(Mat, Root);
}
if (Parts[LC_MFW_BODY3])
{
Mat = lcMatrix44Identity();
Mat.SetTranslation(lcVector3(0, 0, -32.0f));
Mat = lcMul(mSettings[LC_MFW_BODY3][GetSelectionIndex(LC_MFW_BODY3)].Offset, Mat);
Matrices[LC_MFW_BODY3] = lcMul(Mat, Root);
}
if (Parts[LC_MFW_RLEG])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_RLEG]);
Mat.SetTranslation(lcVector3(0, 0, -44.0f));
Mat = lcMul(mSettings[LC_MFW_RLEG][GetSelectionIndex(LC_MFW_RLEG)].Offset, Mat);
Matrices[LC_MFW_RLEG] = lcMul(Mat, Root);
}
if (Parts[LC_MFW_RLEGA])
{
const lcVector3 Center(-10.0f, -1.0f, -28.0f);
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_RLEGA]);
Mat2 = mSettings[LC_MFW_RLEGA][GetSelectionIndex(LC_MFW_RLEGA)].Offset;
Mat2.SetTranslation(lcMul31(-Center, Mat2));
Mat = lcMul(Mat2, Mat);
Mat.SetTranslation(lcMul31(Center, Mat2));
Matrices[LC_MFW_RLEGA] = lcMul(Mat, Matrices[LC_MFW_RLEG]);
}
if (Parts[LC_MFW_LLEG])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_LLEG]);
Mat.SetTranslation(lcVector3(0, 0, -44.0f));
Mat = lcMul(mSettings[LC_MFW_LLEG][GetSelectionIndex(LC_MFW_LLEG)].Offset, Mat);
Matrices[LC_MFW_LLEG] = lcMul(Mat, Root);
}
if (Parts[LC_MFW_LLEGA])
{
const lcVector3 Center(10.0f, -1.0f, -28.0f);
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_LLEGA]);
Mat2 = mSettings[LC_MFW_LLEGA][GetSelectionIndex(LC_MFW_LLEGA)].Offset;
Mat2.SetTranslation(lcMul31(-Center, Mat2));
Mat = lcMul(Mat2, Mat);
Mat.SetTranslation(lcMul31(Center, Mat2));
Matrices[LC_MFW_LLEGA] = lcMul(Mat, Matrices[LC_MFW_LLEG]);
}
mModel->SetMinifig(mMinifig);
}
int MinifigWizard::GetSelectionIndex(int Type) const
{
const std::vector<lcMinifigPieceInfo>& InfoArray = mSettings[Type];
for (size_t Index = 0; Index < InfoArray.size(); Index++)
if (InfoArray[Index].Info == mMinifig.Parts[Type])
return (int)Index;
return 0;
}
void MinifigWizard::SetPieceInfo(int Type, PieceInfo* Info)
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
if (mMinifig.Parts[Type])
Library->ReleasePieceInfo(mMinifig.Parts[Type]);
mMinifig.Parts[Type] = Info;
if (mMinifig.Parts[Type])
Library->LoadPieceInfo(mMinifig.Parts[Type], lcPieceLoadPriority::Blocking);
Calculate();
}
void MinifigWizard::SetColorIndex(int Type, int ColorIndex)
{
mMinifig.ColorIndices[Type] = ColorIndex;
Calculate();
}
void MinifigWizard::SetAngle(int Type, float Angle)
{
mMinifig.Angles[Type] = Angle;
Calculate();
}