mirror of
https://github.com/HarbourMasters/SpaghettiKart
synced 2026-07-09 14:56:07 -04:00
Interpolate hm intro (#228)
* Interpolation ApplyMatrixTransformations * Fix scenefile saving and add skybox export --------- Co-authored-by: MegaMech <7255464+MegaMech@users.noreply.github.com>
This commit is contained in:
+2
-1
@@ -68,7 +68,8 @@ s32 gIsMirrorMode = 0;
|
||||
f32 vtxStretchY = 1.0f;
|
||||
Lights1 D_800DC610[] = {
|
||||
gdSPDefLights1(175, 175, 175, 255, 255, 255, 0, 0, 120),
|
||||
gdSPDefLights1(115, 115, 115, 255, 255, 255, 0, 0, 120),
|
||||
//! @todo impl lighting in custom track origin value 115 instead of 209. Hack fix for lighting for now
|
||||
gdSPDefLights1(209, 209, 209, 255, 255, 255, 0, 0, 120),
|
||||
};
|
||||
UNUSED s32 pad_800029B0 = 0x80000000;
|
||||
s16 gCreditsCourseId = COURSE_LUIGI_RACEWAY;
|
||||
|
||||
+14
-1
@@ -3,6 +3,7 @@
|
||||
#include "HM_Intro.h"
|
||||
|
||||
#include "port/Game.h"
|
||||
#include "port/interpolation/FrameInterpolation.h"
|
||||
|
||||
extern "C" {
|
||||
#include "main.h"
|
||||
@@ -64,7 +65,7 @@ void HarbourMastersIntro::HM_InitIntro() {
|
||||
0x60, 20, 10, 0x49, 0x49, 0x49
|
||||
);
|
||||
|
||||
gEditor.AddObject("lus", &lusPos, &lusRot, &lusScale, nullptr, 1, Editor::GameObject::CollisionType::BOUNDING_BOX, 10, &DespawnValue, -1);
|
||||
//gEditor.AddObject("lus", &lusPos, &lusRot, &lusScale, nullptr, 1, Editor::GameObject::CollisionType::BOUNDING_BOX, 10, &DespawnValue, -1);
|
||||
}
|
||||
|
||||
void HarbourMastersIntro::HM_TickIntro() {
|
||||
@@ -115,6 +116,7 @@ void HarbourMastersIntro::SpagBob(FVector& pos, IRotator& rot, f32 bobAmp, f32 b
|
||||
}
|
||||
|
||||
void HarbourMastersIntro::HM_DrawIntro() {
|
||||
FrameInterpolation_ShouldInterpolateFrame(false);
|
||||
HarbourMastersIntro::Setup();
|
||||
|
||||
gSPMatrix(gDisplayListHead++, &gGfxPool->mtxScreen, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION);
|
||||
@@ -122,21 +124,28 @@ void HarbourMastersIntro::HM_DrawIntro() {
|
||||
gDPSetRenderMode(gDisplayListHead++, G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2);
|
||||
gDPSetEnvColor(gDisplayListHead++, 0x00, 0x00, 0x00, 0x00);
|
||||
|
||||
FrameInterpolation_RecordOpenChild("spag_ship", 0);
|
||||
Mat4 mtx_spaghettiShip;
|
||||
ApplyMatrixTransformations(mtx_spaghettiShip, _pos, _rot, _scale);
|
||||
render_set_position(mtx_spaghettiShip, 0);
|
||||
gSPDisplayList(gDisplayListHead++, ship1_spag1_mesh);
|
||||
FrameInterpolation_RecordCloseChild();
|
||||
|
||||
FrameInterpolation_RecordOpenChild("ship2", 0);
|
||||
Mat4 mtx_ship2;
|
||||
ApplyMatrixTransformations(mtx_ship2, _ship2Pos, _ship2Rot, _scale);
|
||||
render_set_position(mtx_ship2, 0);
|
||||
gSPDisplayList(gDisplayListHead++, ship2_SoH_mesh);
|
||||
FrameInterpolation_RecordCloseChild();
|
||||
|
||||
FrameInterpolation_RecordOpenChild("ship3", 0);
|
||||
Mat4 mtx_ship3;
|
||||
ApplyMatrixTransformations(mtx_ship3, _shipPos, _shipRot, _scale);
|
||||
render_set_position(mtx_ship3, 0);
|
||||
gSPDisplayList(gDisplayListHead++, ship3_2Ship_mesh);
|
||||
FrameInterpolation_RecordCloseChild();
|
||||
|
||||
FrameInterpolation_RecordOpenChild("hm_geo", 0);
|
||||
Mat4 mtx_geo;
|
||||
ApplyMatrixTransformations(mtx_geo, _hPos, _hRot, _hScale);
|
||||
render_set_position(mtx_geo, 0);
|
||||
@@ -146,13 +155,17 @@ void HarbourMastersIntro::HM_DrawIntro() {
|
||||
gSPDisplayList(gDisplayListHead++, castle_map_002_mesh);
|
||||
gSPDisplayList(gDisplayListHead++, road_map_001_mesh);
|
||||
gSPDisplayList(gDisplayListHead++, water_water1_mesh);
|
||||
FrameInterpolation_RecordCloseChild();
|
||||
|
||||
FrameInterpolation_RecordOpenChild("hm_lus", 0);
|
||||
Mat4 lusMtx;
|
||||
ApplyMatrixTransformations(lusMtx, lusPos, lusRot, lusScale);
|
||||
render_set_position(lusMtx, 0);
|
||||
gSPDisplayList(gDisplayListHead++, (Gfx*)"__OTR__hmintro/poweredbylus");
|
||||
FrameInterpolation_RecordCloseChild();
|
||||
|
||||
HarbourMastersIntro::Sync();
|
||||
FrameInterpolation_ShouldInterpolateFrame(true);
|
||||
}
|
||||
|
||||
void HarbourMastersIntro::Setup() {
|
||||
|
||||
@@ -74,6 +74,7 @@ void ApplyMatrixTransformations(Mat4 mtx, FVector pos, IRotator rot, FVector sca
|
||||
f32 sine1, cosine1;
|
||||
f32 sine2, cosine2;
|
||||
f32 sine3, cosine3;
|
||||
FrameInterpolation_ApplyMatrixTransformations((Mat4*)mtx, pos, rot, scale);
|
||||
|
||||
// Compute the sine and cosine of the orientation (Euler angles)
|
||||
sine1 = sins(rot.pitch);
|
||||
|
||||
@@ -23,12 +23,13 @@ extern "C" {
|
||||
#include "render_courses.h"
|
||||
#include "collision.h"
|
||||
#include "actors.h"
|
||||
#include "math_util.h"
|
||||
extern StaffGhost* d_mario_raceway_staff_ghost;
|
||||
}
|
||||
|
||||
Course::Course() {
|
||||
// Props.Name = "Course Name";
|
||||
// Props.DebugName = "CName";
|
||||
Props.SetText(Props.Name, "Blank Track", sizeof(Props.Name));
|
||||
Props.SetText(Props.DebugName, "blnktrck", sizeof(Props.DebugName));
|
||||
Props.SetText(Props.CourseLength, "100m", sizeof(Props.CourseLength));
|
||||
// Props.Cup = FLOWER_CUP;
|
||||
// Props.CupIndex = 3;
|
||||
@@ -134,7 +135,7 @@ void Course::LoadO2R(std::string trackPath) {
|
||||
}
|
||||
}
|
||||
|
||||
// Load stock track
|
||||
// Load stock and o2r tracks
|
||||
void Course::Load() {
|
||||
|
||||
// Load from O2R
|
||||
@@ -150,6 +151,8 @@ void Course::Load() {
|
||||
ParseCourseSections(sections, size);
|
||||
func_80295C6C();
|
||||
Props.WaterLevel = gCourseMinY - 10.0f;
|
||||
} else {
|
||||
printf("Course.cpp: Custom track sections are invalid\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -337,7 +340,7 @@ void Course::Render(struct UnkStruct_800DC5EC* arg0) {
|
||||
if (!TrackSectionsPtr.empty()) {
|
||||
gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH);
|
||||
gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING);
|
||||
// set_track_light_direction(D_800DC610, D_802B87D4, 0, 1);
|
||||
set_track_light_direction(D_800DC610, D_802B87D4, 0, 1);
|
||||
gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
|
||||
gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH);
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ typedef struct Properties {
|
||||
j["Name"] = Name ? Name : "";
|
||||
j["DebugName"] = DebugName ? DebugName : "";
|
||||
j["CourseLength"] = CourseLength ? CourseLength : "";
|
||||
j["AIBehaviour"] = AIBehaviour ? AIBehaviour : "";
|
||||
//j["AIBehaviour"] = AIBehaviour ? AIBehaviour : "";
|
||||
j["LakituTowType"] = LakituTowType;
|
||||
j["AIMaximumSeparation"] = AIMaximumSeparation;
|
||||
j["AIMinimumSeparation"] = AIMinimumSeparation;
|
||||
@@ -133,10 +133,22 @@ typedef struct Properties {
|
||||
j["MinimapFinishlineY"] = Minimap.FinishlineY;
|
||||
j["MinimapColour"] = {static_cast<int>(Minimap.Colour.r), static_cast<int>(Minimap.Colour.g), static_cast<int>(Minimap.Colour.b)};
|
||||
// SkyboxColors - assuming SkyboxColors can be serialized similarly
|
||||
// j["Skybox"] = Skybox; // Implement your serialization logic here
|
||||
|
||||
#define TO_INT(value) static_cast<int>(value)
|
||||
j["Skybox"] = {
|
||||
TO_INT(Skybox.TopRight.r), TO_INT(Skybox.TopRight.g), TO_INT(Skybox.TopRight.b),
|
||||
TO_INT(Skybox.BottomRight.r), TO_INT(Skybox.BottomRight.g), TO_INT(Skybox.BottomRight.b),
|
||||
TO_INT(Skybox.BottomLeft.r), TO_INT(Skybox.BottomLeft.g), TO_INT(Skybox.BottomLeft.b),
|
||||
TO_INT(Skybox.TopLeft.r), TO_INT(Skybox.TopLeft.g), TO_INT(Skybox.TopLeft.b),
|
||||
TO_INT(Skybox.FloorTopRight.r), TO_INT(Skybox.FloorTopRight.g), TO_INT(Skybox.FloorTopRight.b),
|
||||
TO_INT(Skybox.FloorBottomRight.r), TO_INT(Skybox.FloorBottomRight.g), TO_INT(Skybox.FloorBottomRight.b),
|
||||
TO_INT(Skybox.FloorBottomLeft.r), TO_INT(Skybox.FloorBottomLeft.g), TO_INT(Skybox.FloorBottomLeft.b),
|
||||
TO_INT(Skybox.FloorTopLeft.r), TO_INT(Skybox.FloorTopLeft.g), TO_INT(Skybox.FloorTopLeft.b)
|
||||
};
|
||||
j["Sequence"] = static_cast<int>(Sequence);
|
||||
|
||||
j["WaterLevel"] = static_cast<float>(WaterLevel);
|
||||
#undef CAST_TO_INT
|
||||
|
||||
return j;
|
||||
}
|
||||
@@ -156,7 +168,7 @@ typedef struct Properties {
|
||||
strncpy(CourseLength, j.at("CourseLength").get<std::string>().c_str(), sizeof(CourseLength) - 1);
|
||||
CourseLength[sizeof(CourseLength) - 1] = '\0'; // Ensure null termination
|
||||
|
||||
AIBehaviour = j.at("AIBehaviour").get<std::string>().c_str();
|
||||
//AIBehaviour = j.at("AIBehaviour").get<std::string>().c_str();
|
||||
LakituTowType = j.at("LakituTowType").get<int>();
|
||||
|
||||
AIMaximumSeparation = j.at("AIMaximumSeparation").get<float>();
|
||||
@@ -215,6 +227,39 @@ typedef struct Properties {
|
||||
Minimap.Colour.g = j.at("MinimapColour")[1].get<uint8_t>();
|
||||
Minimap.Colour.b = j.at("MinimapColour")[2].get<uint8_t>();
|
||||
//textures = nullptr; // Deserialize textures if present
|
||||
|
||||
Skybox.TopRight.r = j.at("Skybox")[0].get<uint8_t>();
|
||||
Skybox.TopRight.g = j.at("Skybox")[1].get<uint8_t>();
|
||||
Skybox.TopRight.b = j.at("Skybox")[2].get<uint8_t>();
|
||||
|
||||
Skybox.BottomRight.r = j.at("Skybox")[3].get<uint8_t>();
|
||||
Skybox.BottomRight.g = j.at("Skybox")[4].get<uint8_t>();
|
||||
Skybox.BottomRight.b = j.at("Skybox")[5].get<uint8_t>();
|
||||
|
||||
Skybox.BottomLeft.r = j.at("Skybox")[6].get<uint8_t>();
|
||||
Skybox.BottomLeft.g = j.at("Skybox")[7].get<uint8_t>();
|
||||
Skybox.BottomLeft.b = j.at("Skybox")[8].get<uint8_t>();
|
||||
|
||||
Skybox.TopLeft.r = j.at("Skybox")[9].get<uint8_t>();
|
||||
Skybox.TopLeft.g = j.at("Skybox")[10].get<uint8_t>();
|
||||
Skybox.TopLeft.b = j.at("Skybox")[11].get<uint8_t>();
|
||||
|
||||
Skybox.FloorTopRight.r = j.at("Skybox")[12].get<uint8_t>();
|
||||
Skybox.FloorTopRight.g = j.at("Skybox")[13].get<uint8_t>();
|
||||
Skybox.FloorTopRight.b = j.at("Skybox")[14].get<uint8_t>();
|
||||
|
||||
Skybox.FloorBottomRight.r = j.at("Skybox")[15].get<uint8_t>();
|
||||
Skybox.FloorBottomRight.g = j.at("Skybox")[16].get<uint8_t>();
|
||||
Skybox.FloorBottomRight.b = j.at("Skybox")[17].get<uint8_t>();
|
||||
|
||||
Skybox.FloorBottomLeft.r = j.at("Skybox")[18].get<uint8_t>();
|
||||
Skybox.FloorBottomLeft.g = j.at("Skybox")[19].get<uint8_t>();
|
||||
Skybox.FloorBottomLeft.b = j.at("Skybox")[20].get<uint8_t>();
|
||||
|
||||
Skybox.FloorTopLeft.r = j.at("Skybox")[21].get<uint8_t>();
|
||||
Skybox.FloorTopLeft.g = j.at("Skybox")[22].get<uint8_t>();
|
||||
Skybox.FloorTopLeft.b = j.at("Skybox")[23].get<uint8_t>();
|
||||
|
||||
Sequence = static_cast<MusicSeq>(j.at("Sequence").get<int>());
|
||||
WaterLevel = j.at("WaterLevel").get<float>();
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Editor {
|
||||
void LoadLevel(std::shared_ptr<Ship::Archive> archive, Course* course, std::string sceneFile) {
|
||||
SceneFile = sceneFile;
|
||||
|
||||
if (archive) {
|
||||
if (archive && (course != nullptr)) {
|
||||
auto initData = std::make_shared<Ship::ResourceInitData>();
|
||||
initData->Parent = archive;
|
||||
initData->Format = RESOURCE_FORMAT_BINARY;
|
||||
|
||||
+2
-5
@@ -141,7 +141,8 @@ void CustomEngineInit() {
|
||||
gWorldInstance.AddCup(gBattleCup);
|
||||
|
||||
/* Set default course; mario raceway */
|
||||
SelectMarioRaceway();
|
||||
//SelectMarioRaceway(); // This results in a nullptr
|
||||
gWorldInstance.CurrentCourse = mario;
|
||||
gWorldInstance.CurrentCup = gMushroomCup;
|
||||
gWorldInstance.CurrentCup->CursorPosition = 3;
|
||||
gWorldInstance.CupIndex = 0;
|
||||
@@ -601,10 +602,6 @@ void* GetCourse(void) {
|
||||
return gWorldInstance.CurrentCourse;
|
||||
}
|
||||
|
||||
void SetCourseByClass(void* course) {
|
||||
gWorldInstance.CurrentCourse = (Course*) course;
|
||||
}
|
||||
|
||||
struct Actor* CM_GetActor(size_t index) {
|
||||
if (index < gWorldInstance.Actors.size()) {
|
||||
AActor* actor = gWorldInstance.Actors[index];
|
||||
|
||||
@@ -143,8 +143,6 @@ void* GetCourse(void);
|
||||
|
||||
void SetCourseById(s32 course);
|
||||
|
||||
void SetCourseByClass(void* course);
|
||||
|
||||
struct Actor* CM_GetActor(size_t index);
|
||||
void CM_DeleteActor(size_t index);
|
||||
struct Actor* CM_AddBaseActor();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "FrameInterpolation.h"
|
||||
#include "matrix.h"
|
||||
#include "engine/Matrix.h"
|
||||
#include "src/engine/CoreMath.h"
|
||||
|
||||
extern "C" {
|
||||
#include "math_util.h"
|
||||
@@ -85,6 +86,7 @@ enum class Op {
|
||||
SetTranslateRotate,
|
||||
SetTextMatrix,
|
||||
SetMatrixPosRotScaleXY,
|
||||
SetApplyMatrixTransformations,
|
||||
};
|
||||
|
||||
typedef pair<const void*, uintptr_t> label;
|
||||
@@ -108,6 +110,13 @@ union Data {
|
||||
u8 mode;
|
||||
} matrix_mult;
|
||||
|
||||
struct {
|
||||
Mat4* matrix;
|
||||
FVector pos;
|
||||
IRotator rot;
|
||||
FVector scale;
|
||||
} matrix_applytransformations;
|
||||
|
||||
struct {
|
||||
Mat4* matrix;
|
||||
Vec3fInterp b;
|
||||
@@ -578,6 +587,13 @@ struct InterpolateCtx {
|
||||
mtxf_translation_x_y_rotate_z_scale_x_y(*gInterpolationMatrix, tmp32[0], tmp32[1], tmp_vec3s[0], tmp_vec3f[0]);
|
||||
break;
|
||||
}
|
||||
case Op::SetApplyMatrixTransformations: {
|
||||
lerp_vec3f(&tmp_vec3f, (Vec3f*)&old_op.matrix_applytransformations.pos, (Vec3f*)&new_op.matrix_applytransformations.pos);
|
||||
lerp_vec3s(&tmp_vec3s, (int16_t*)&old_op.matrix_applytransformations.rot, (int16_t*)&new_op.matrix_applytransformations.rot);
|
||||
lerp_vec3f(&tmp_vec3f2, (Vec3f*)&old_op.matrix_applytransformations.scale, (Vec3f*)&new_op.matrix_applytransformations.scale);
|
||||
|
||||
ApplyMatrixTransformations(*gInterpolationMatrix, *(FVector*)&tmp_vec3f, *(IRotator*)&tmp_vec3s, *(FVector*)&tmp_vec3f2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -694,6 +710,12 @@ void FrameInterpolation_RecordMatrixMult(Mat4* matrix, MtxF* mf, u8 mode) {
|
||||
append(Op::MatrixMult).matrix_mult = { matrix, *mf, mode };
|
||||
}
|
||||
|
||||
void FrameInterpolation_ApplyMatrixTransformations(Mat4* matrix, FVector pos, IRotator rot, FVector scale) {
|
||||
if (!is_recording)
|
||||
return;
|
||||
append(Op::SetApplyMatrixTransformations).matrix_applytransformations = { matrix, pos, rot, scale };
|
||||
}
|
||||
|
||||
void FrameInterpolation_RecordMatrixTranslate(Mat4* matrix, Vec3f b) {
|
||||
if (!is_recording)
|
||||
return;
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "src/engine/CoreMath.h"
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
|
||||
std::unordered_map<Mtx*, MtxF> FrameInterpolation_Interpolate(float step);
|
||||
void FrameInterpolation_ApplyMatrixTransformations(Mat4* matrix, FVector pos, IRotator rot, FVector scale);
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -44,9 +44,10 @@ ResourceFactoryXMLTrackSectionsV0::ReadResource(std::shared_ptr<Ship::File> file
|
||||
if (!FileHasValidFormatAndReader(file, initData)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
auto section = std::make_shared<TrackSectionsO2RClass>(initData);
|
||||
auto child = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement("TrackSections");
|
||||
auto child =
|
||||
std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement()->FirstChildElement();
|
||||
|
||||
while (child != nullptr) {
|
||||
std::string childName = std::string(child->Name());
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Editor {
|
||||
if (!track.SceneFile.empty()) { // has scene file
|
||||
std::string label = fmt::format("{}##{}", track.Name, i_track);
|
||||
if (ImGui::Button(label.c_str())) {
|
||||
SetCourseByClass(track.course);
|
||||
gWorldInstance.CurrentCourse = track.course;
|
||||
gGamestateNext = RACING;
|
||||
SetSceneFile(track.Archive, track.SceneFile);
|
||||
break;
|
||||
@@ -136,7 +136,7 @@ namespace Editor {
|
||||
std::string label = fmt::format("{} {}", ICON_FA_EXCLAMATION_TRIANGLE, track.Name);
|
||||
if (ImGui::Button(label.c_str())) {
|
||||
track.SceneFile = track.Dir + "/scene.json";
|
||||
SetCourseByClass(track.course);
|
||||
gWorldInstance.CurrentCourse = track.invalidTrack.get();
|
||||
SetSceneFile(track.Archive, track.SceneFile);
|
||||
SaveLevel();
|
||||
Refresh = true;
|
||||
@@ -240,10 +240,10 @@ namespace Editor {
|
||||
|
||||
auto course = std::make_unique<Course>();
|
||||
course->LoadO2R(dir);
|
||||
gWorldInstance.Courses.push_back(std::move(course));
|
||||
LoadLevel(archive, course.get(), sceneFile);
|
||||
LoadMinimap(archive, course.get(), minimapFile);
|
||||
Tracks.push_back({course.get(), sceneFile, name, dir, archive});
|
||||
Tracks.push_back({nullptr, course.get(), sceneFile, name, dir, archive});
|
||||
gWorldInstance.Courses.push_back(std::move(course));
|
||||
} else { // The track does not have a valid scene file
|
||||
const std::string file = dir + "/data_track_sections";
|
||||
|
||||
@@ -256,9 +256,8 @@ namespace Editor {
|
||||
course->Id = (std::string("mods:") + name).c_str();
|
||||
course->Props.SetText(course->Props.Name, name.c_str(), sizeof(course->Props.Name));
|
||||
course->Props.SetText(course->Props.DebugName, name.c_str(), sizeof(course->Props.Name));
|
||||
|
||||
auto archive = manager->GetArchiveFromFile(file);
|
||||
Tracks.push_back({course.get(), "", name, dir, archive});
|
||||
Tracks.push_back({std::move(course), nullptr, "", name, dir, archive});
|
||||
} else {
|
||||
printf("ContentBrowser.cpp: Track '%s' missing required track files. Cannot add to game\n Missing %s/data_track_sections file\n", name.c_str(), dir.c_str());
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ public:
|
||||
~ContentBrowserWindow();
|
||||
|
||||
struct Tracks {
|
||||
Course* course;
|
||||
std::unique_ptr<Course> invalidTrack; // If not nullptr, user needs to create a scene file for this track.
|
||||
Course* course; // A valid custom track. Used to reset the Courses array on a file system refresh.
|
||||
std::string SceneFile;
|
||||
std::string Name;
|
||||
std::string Dir; // Directory
|
||||
|
||||
Reference in New Issue
Block a user