Fix some import bugs / add support for separated model + anim file

This commit is contained in:
2026-06-22 01:37:16 +02:00
parent 7e5536a02f
commit 6f679e7329
10 changed files with 1514 additions and 34 deletions
+3
View File
@@ -5,6 +5,7 @@
#include <destrum/Scene/SceneManager.h>
#include "destrum/Graphics/Resources/Cubemap.h"
#include "destrum/ObjectModel/GameObject.h"
class LightKeeper final : public App {
public:
@@ -28,6 +29,8 @@ private:
MaterialID testMaterialID;
std::unique_ptr<CubeMap> skyboxCubemap;
GameObject* capybara = nullptr;
};
#endif //LIGHTKEEPER_H
+215 -34
View File
@@ -10,10 +10,15 @@
#include "destrum/Components/Spinner.h"
#include "destrum/Components/OrbitAndSpin.h"
#include "destrum/ObjectModel/GameObject.h"
#include "destrum/Util/ModelLoader.h"
#include "destrum/Util/ModelDoc.h"
#include "destrum/Components/Animator.h"
LightKeeper::LightKeeper(): App(), renderer(meshCache, materialCache) {
#include <filesystem>
#include <string>
#include "destrum/Util/ModelDocUtils.h"
LightKeeper::LightKeeper() : App(), renderer(meshCache, materialCache) {
}
LightKeeper::~LightKeeper() {
@@ -26,23 +31,46 @@ void LightKeeper::customInit() {
const float aspectRatio = static_cast<float>(m_params.renderSize.x) / static_cast<float>(m_params.renderSize.y);
camera.setAspectRatio(aspectRatio);
ModelDoc::LoadOptions staticModelOptions{};
staticModelOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
staticModelOptions.loadMaterials = true;
staticModelOptions.loadSkeleton = false;
staticModelOptions.loadAnimations = false;
auto kittyModel = ModelDoc::LoadModel(
AssetFS::GetInstance().GetFullPath("game://kitty.glb").generic_string(),
staticModelOptions
);
ModelDocUtils::LogModelDocSummary(kittyModel, "kitty.glb");
const auto &kittyPrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(kittyModel, "game://kitty.glb");
testMesh = kittyPrimitive.mesh;
testMesh.name = "Test Mesh";
auto list_of_models = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://kitty.glb").generic_string());
testMesh = list_of_models[0];
testMeshID = meshCache.addMesh(gfxDevice, testMesh);
spdlog::info("TestMesh uploaded with id: {}", testMeshID);
auto testimgID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://kitty.png"));
spdlog::info("Test image loaded with id: {}", testimgID);
const auto testTexturePath = ModelDocUtils::PickTexturePath(
kittyModel,
kittyPrimitive,
AssetFS::GetInstance().GetFullPath("game://kitty.png")
);
const auto testimgID = gfxDevice.loadImageFromFile(testTexturePath);
spdlog::info("Test image loaded from '{}' with id: {}", testTexturePath.generic_string(), testimgID);
testMaterialID = materialCache.addMaterial(gfxDevice, {
.baseColor = glm::vec3(1.f),
.baseColor = ModelDocUtils::GetImportedBaseColor(
kittyModel, kittyPrimitive),
.diffuseTexture = testimgID,
.name = ModelDocUtils::GetImportedMaterialName(
kittyModel, kittyPrimitive, "TestMaterial"),
});
spdlog::info("Test material created with id: {}", testMaterialID);
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
auto& scene = SceneManager::GetInstance().CreateScene("Main");
auto &scene = SceneManager::GetInstance().CreateScene("Main");
// auto testCube = std::make_shared<GameObject>("TestCube");
// auto meshComp = testCube->AddComponent<MeshRendererComponent>();
@@ -77,8 +105,6 @@ void LightKeeper::customInit() {
globeRoot->AddComponent<Spinner>(glm::vec3(0, 1, 0), 1.0f); // spin around Y, rad/sec
scene.Add(globeRoot);
// scene.Add(testCube);
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg");
@@ -97,55 +123,89 @@ void LightKeeper::customInit() {
renderer.setSkyboxTexture(skyboxCubemap->GetCubeMapImageID());
//
const auto planeObj = std::make_shared<GameObject>("GroundPlane");
const auto planeMeshComp = planeObj->AddComponent<MeshRendererComponent>();
const auto planeModel = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://plane.glb").generic_string());
const auto planeMeshID = meshCache.addMesh(gfxDevice, planeModel[0]);
const auto planeTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://grass.png"));
auto planeModel = ModelDoc::LoadModel(
AssetFS::GetInstance().GetFullPath("game://plane.glb").generic_string(),
staticModelOptions
);
ModelDocUtils::LogModelDocSummary(planeModel, "plane.glb");
const auto &planePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(planeModel, "game://plane.glb");
const auto planeMeshID = meshCache.addMesh(gfxDevice, planePrimitive.mesh);
const auto planeTexturePath = ModelDocUtils::PickTexturePath(
planeModel,
planePrimitive,
AssetFS::GetInstance().GetFullPath("game://grass.png")
);
const auto planeTextureID = gfxDevice.loadImageFromFile(planeTexturePath);
const auto planeMaterialID = materialCache.addMaterial(gfxDevice, {
.baseColor = glm::vec3(1.f),
.textureFilteringMode = TextureFilteringMode::Nearest,
.diffuseTexture = planeTextureID,
.name = "GroundPlaneMaterial",
});
.baseColor = ModelDocUtils::GetImportedBaseColor(
planeModel, planePrimitive),
.textureFilteringMode = TextureFilteringMode::Nearest,
.diffuseTexture = planeTextureID,
.name = ModelDocUtils::GetImportedMaterialName(
planeModel, planePrimitive, "GroundPlaneMaterial"),
});
planeMeshComp->SetMeshID(planeMeshID);
planeMeshComp->SetMaterialID(planeMaterialID);
planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.0f, 0.f));
planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f));
scene.Add(planeObj);
// At the bottom of customInit(), replace the incomplete CharObj block:
const auto CharObj = std::make_shared<GameObject>("Character");
capybara = CharObj.get();
auto charModel = ModelLoader::LoadSkinnedModel(
AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/source/capybara.fbx").generic_string()
ModelDoc::LoadOptions characterModelOptions{};
characterModelOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
characterModelOptions.loadMaterials = true;
characterModelOptions.loadSkeleton = true;
characterModelOptions.loadAnimations = true;
auto charModel = ModelDoc::LoadModel(
AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/source/capybara.fbx").generic_string(),
characterModelOptions
);
ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx");
const auto &charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
charModel,
"engine://cotw-capybara-male/source/capybara.fbx"
);
const auto charMeshID = meshCache.addMesh(gfxDevice, charModel.meshes[0]);
const auto charMeshID = meshCache.addMesh(gfxDevice, charPrimitive.mesh);
const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.png"));
const auto charTexturePath = ModelDocUtils::PickTexturePath(
charModel,
charPrimitive,
AssetFS::GetInstance().GetFullPath(
"engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.pngballs")
);
const auto charTextureID = gfxDevice.loadImageFromFile(charTexturePath);
// const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
const auto charMaterialID = materialCache.addMaterial(gfxDevice, {
.baseColor = glm::vec3(1.f),
.diffuseTexture = charTextureID,
.name = "CharacterMaterial",
});
.baseColor = ModelDocUtils::GetImportedBaseColor(
charModel, charPrimitive),
.diffuseTexture = charTextureID,
.name = ModelDocUtils::GetImportedMaterialName(
charModel, charPrimitive, "CharacterMaterial"),
});
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
charMeshComp->SetMeshID(charMeshID);
charMeshComp->SetMaterialID(charMaterialID);
const auto animator = CharObj->AddComponent<Animator>();
animator->setSkeleton(std::move(charModel.skeleton));
std::string firstAnimationName;
for (auto& clip : charModel.animations) {
for (auto &clip: charModel.animations) {
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
if (firstAnimationName.empty())
@@ -159,11 +219,132 @@ void LightKeeper::customInit() {
animator->play(firstAnimationName);
}
animator->play("capybara_canter_fwd_01|capybara_walk_fwd_01");
animator->play("capybara_canter_fwd_01|capybara_canter_fwd_01|run");
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
scene.Add(CharObj);
ModelDoc::LoadOptions options{};
options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
auto model = ModelDoc::LoadModel(
AssetFS::GetInstance()
.GetFullPath("engine://multiple_object_test.fbx")
.generic_string(),
options
);
ModelDocUtils::LogModelDocSummary(model, "engine://multiple_object_test.fbx");
auto root = std::make_shared<GameObject>("MultiMeshModel");
root->GetTransform().SetWorldPosition(glm::vec3(0.1f, 0.1f, 0.1f));
scene.Add(root);
for (std::size_t i = 0; i < model.primitives.size(); ++i) {
const auto &primitive = model.primitives[i];
const auto meshID = meshCache.addMesh(gfxDevice, primitive.mesh);
const auto texturePath = ModelDocUtils::PickTexturePath(
model,
primitive,
AssetFS::GetInstance().GetFullPath("engine://textures/white.png")
);
const auto textureID = gfxDevice.loadImageFromFile(texturePath);
const auto materialID = materialCache.addMaterial(gfxDevice, {
.baseColor = ModelDocUtils::GetImportedBaseColor(
model, primitive),
.diffuseTexture = textureID,
.name = ModelDocUtils::GetImportedMaterialName(
model,
primitive,
primitive.mesh.name + "_Material"
),
});
auto part = std::make_shared<GameObject>(
primitive.mesh.name.empty()
? "Primitive_" + std::to_string(i)
: primitive.mesh.name
);
auto meshComp = part->AddComponent<MeshRendererComponent>();
meshComp->SetMeshID(meshID);
meshComp->SetMaterialID(materialID);
scene.Add(part);
}
{
const auto CharObj = std::make_shared<GameObject>("Character");
ModelDoc::LoadOptions characterOptions{};
characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
characterOptions.loadMaterials = true;
characterOptions.loadSkeleton = true;
characterOptions.loadAnimations = false;
auto charModel = ModelDoc::LoadModel(
AssetFS::GetInstance()
.GetFullPath("engine://characterMedium.fbx")
.generic_string(),
characterOptions
);
const auto &charPrimitive =
ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
charModel,
"engine://characterMedium.fbx"
);
const auto charMeshID = meshCache.addMesh(gfxDevice, charPrimitive.mesh);
const auto charTextureID = gfxDevice.loadImageFromFile(
AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png")
);
const auto charMaterialID = materialCache.addMaterial(gfxDevice, {
.baseColor = ModelDocUtils::GetImportedBaseColor(
charModel, charPrimitive),
.diffuseTexture = charTextureID,
.name = ModelDocUtils::GetImportedMaterialName(
charModel,
charPrimitive,
"CharacterMaterial"
),
});
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
charMeshComp->SetMeshID(charMeshID);
charMeshComp->SetMaterialID(charMaterialID);
const auto animator = CharObj->AddComponent<Animator>();
animator->setSkeleton(charModel.skeleton);
auto runClips = ModelDoc::LoadAnimationClips(
AssetFS::GetInstance()
.GetFullPath("engine://run.fbx")
.generic_string(),
charModel.skeleton
);
for (auto &clip: runClips) {
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
}
if (!runClips.empty()) {
animator->play("Root|Run");
}
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f));
CharObj->GetTransform().SetWorldPosition(glm::vec3(5, 0, 0));
scene.Add(CharObj);
}
}
void LightKeeper::customUpdate(float dt) {
@@ -194,7 +375,7 @@ void LightKeeper::customDraw() {
renderer.endDrawing();
const auto cmd = gfxDevice.beginFrame();
const auto& drawImage = renderer.getDrawImage(gfxDevice);
const auto &drawImage = renderer.getDrawImage(gfxDevice);
renderer.draw(
cmd, gfxDevice, camera, GameRenderer::SceneData{