mirror of
https://github.com/leozide/leocad
synced 2024-12-25 21:58:23 +01:00
496 lines
14 KiB
C++
496 lines
14 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();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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::LoadDefault()
|
|
{
|
|
LC_ARRAY_SIZE_CHECK(MinifigWizard::mSectionNames, LC_MFW_NUMITEMS);
|
|
|
|
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", "", "" };
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
|
|
for (int i = 0; i < LC_MFW_NUMITEMS; i++)
|
|
{
|
|
mMinifig.Colors[i] = lcGetColorIndex(ColorCodes[i]);
|
|
mMinifig.Angles[i] = 0.0f;
|
|
mMinifig.Matrices[i] = lcMatrix44Identity();
|
|
|
|
PieceInfo* Info = Library->FindPiece(Pieces[i], nullptr, false, false);
|
|
mMinifig.Parts[i] = Info;
|
|
if (Info)
|
|
Library->LoadPieceInfo(Info, false, true);
|
|
}
|
|
|
|
Library->WaitForLoadQueue();
|
|
Calculate();
|
|
}
|
|
|
|
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::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.Colors[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.Colors[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::SetSelectionIndex(int Type, int Index)
|
|
{
|
|
lcPiecesLibrary* Library = lcGetPiecesLibrary();
|
|
|
|
if (mMinifig.Parts[Type])
|
|
Library->ReleasePieceInfo(mMinifig.Parts[Type]);
|
|
|
|
mMinifig.Parts[Type] = mSettings[Type][Index].Info;
|
|
|
|
if (mMinifig.Parts[Type])
|
|
Library->LoadPieceInfo(mMinifig.Parts[Type], true, true);
|
|
|
|
Calculate();
|
|
}
|
|
|
|
void MinifigWizard::SetColor(int Type, int Color)
|
|
{
|
|
mMinifig.Colors[Type] = Color;
|
|
|
|
Calculate();
|
|
}
|
|
|
|
void MinifigWizard::SetAngle(int Type, float Angle)
|
|
{
|
|
mMinifig.Angles[Type] = Angle;
|
|
|
|
Calculate();
|
|
}
|