From 79f3a7f0f9d860595f18eaa9ed037b1b0e511212 Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Sun, 21 Dec 2025 17:09:36 +0900 Subject: [PATCH] ADD: animation and primitive test completed --- docs/GameAPI.md | 93 +++++++++++++++++++++++++++++++++++++++++ src/core/engine.cpp | 41 ++++++++++++++++++ src/core/game_api.cpp | 65 ++++++++++++++++++++++++++++ src/core/game_api.h | 24 +++++++++++ src/scene/vk_loader.cpp | 12 ++++-- 5 files changed, 232 insertions(+), 3 deletions(-) diff --git a/docs/GameAPI.md b/docs/GameAPI.md index 92551dc..235f2cd 100644 --- a/docs/GameAPI.md +++ b/docs/GameAPI.md @@ -384,6 +384,99 @@ Docs: `docs/Scene.md` - Spawn primitives or dynamic meshes at runtime (e.g. projectiles, props). - Use `setMeshInstanceTransform` every frame to move them based on game logic. +### Textured Primitives + +Spawn primitive meshes (cube, sphere, plane, capsule) with custom PBR textures at runtime. + +#### PrimitiveMaterial Structure + +```cpp +struct PrimitiveMaterial +{ + std::string albedoPath; // Color/diffuse texture (relative to assets/) + std::string metalRoughPath; // Metallic (R) + Roughness (G) texture + std::string normalPath; // Tangent-space normal map + std::string occlusionPath; // Ambient occlusion (R channel) + std::string emissivePath; // Emissive map + + glm::vec4 colorFactor{1.0f}; // Base color multiplier (RGBA) + float metallic{0.0f}; // Metallic factor (0-1) + float roughness{0.5f}; // Roughness factor (0-1) +}; +``` + +#### PrimitiveType Enum + +```cpp +enum class PrimitiveType +{ + Cube, + Sphere, + Plane, + Capsule +}; +``` + +#### API Functions + +```cpp +bool add_textured_primitive(const std::string& name, + PrimitiveType type, + const PrimitiveMaterial& material, + const Transform& transform = {}); + +bool add_textured_primitive(const std::string& name, + PrimitiveType type, + const PrimitiveMaterial& material, + const TransformD& transform); // double-precision +``` + +#### Usage Example + +```cpp +GameAPI::Engine api(&engine); + +// Create material with textures +GameAPI::PrimitiveMaterial mat; +mat.albedoPath = "textures/brick_albedo.png"; +mat.normalPath = "textures/brick_normal.png"; +mat.metalRoughPath = "textures/brick_mro.png"; // Metallic-Roughness-Occlusion packed +mat.roughness = 0.7f; +mat.metallic = 0.0f; + +// Spawn a textured cube +GameAPI::Transform transform; +transform.position = glm::vec3(0.0f, 1.0f, -5.0f); +transform.scale = glm::vec3(2.0f); + +api.add_textured_primitive("my_brick_cube", GameAPI::PrimitiveType::Cube, mat, transform); + +// Spawn a textured sphere with different material +GameAPI::PrimitiveMaterial metalMat; +metalMat.albedoPath = "textures/metal_albedo.png"; +metalMat.normalPath = "textures/metal_normal.png"; +metalMat.metallic = 1.0f; +metalMat.roughness = 0.3f; +metalMat.colorFactor = glm::vec4(0.9f, 0.9f, 1.0f, 1.0f); // Slight blue tint + +GameAPI::Transform sphereT; +sphereT.position = glm::vec3(3.0f, 1.0f, -5.0f); + +api.add_textured_primitive("chrome_sphere", GameAPI::PrimitiveType::Sphere, metalMat, sphereT); +``` + +#### Notes + +- Texture paths are relative to the `assets/` directory. +- If a texture path is empty, the engine uses default placeholder textures: + - Albedo: error checkerboard (magenta/black) + - Normal: flat normal (0.5, 0.5, 1.0) + - MetalRough: white (default values from `metallic`/`roughness` factors) + - Occlusion: white (no occlusion) + - Emissive: black (no emission) +- Textures are loaded asynchronously via `TextureCache`; placeholders appear until upload completes. +- For non-textured primitives with solid colors, use `add_primitive_instance()` instead. + - GLTF instances (actors) - `void addGLTFInstance(const std::string &name, std::shared_ptr scene, const glm::mat4 &transform = glm::mat4(1.f));` - `bool getGLTFInstanceTransform(const std::string &name, glm::mat4 &outTransform);` diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 4053534..3901642 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -604,11 +604,52 @@ void VulkanEngine::init_default_data() BoundsType::Sphere); } + // Test textured primitives + { + AssetManager::MeshMaterialDesc matDesc; + matDesc.kind = AssetManager::MeshMaterialDesc::Kind::Textured; + matDesc.options.albedoPath = "textures/grass_albedo.png"; + matDesc.options.normalPath = "textures/grass_normal.png"; + matDesc.options.metalRoughPath = "textures/grass_mro.png"; + matDesc.options.occlusionPath = "textures/grass_ao.png"; + + addPrimitiveInstance("textured.cube", + AssetManager::MeshGeometryDesc::Type::Cube, + glm::translate(glm::mat4(1.f), glm::vec3(0.f, 1.f, -4.f)), + matDesc); + + addPrimitiveInstance("textured.sphere", + AssetManager::MeshGeometryDesc::Type::Sphere, + glm::translate(glm::mat4(1.f), glm::vec3(3.f, 1.f, -4.f)), + matDesc); + + addPrimitiveInstance("textured.plane", + AssetManager::MeshGeometryDesc::Type::Plane, + glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.f, -6.f)), glm::vec3(4.f)), + matDesc); + } + if (addGLTFInstance("mirage", "mirage2000/scene.gltf", glm::mat4(1.0f))) { preloadInstanceTextures("mirage"); } + // Windmill animation test + { + glm::mat4 windmillTransform = glm::translate(glm::mat4(1.0f), glm::vec3(10.0f, 0.0f, 0.0f)); + windmillTransform = glm::scale(windmillTransform, glm::vec3(0.5f)); + if (addGLTFInstance("windmill", "windmill/scene.gltf", windmillTransform)) + { + preloadInstanceTextures("windmill"); + // Enable first animation (index 0) with looping + if (_sceneManager) + { + _sceneManager->setGLTFInstanceAnimation("windmill", 0, true); + _sceneManager->setGLTFInstanceAnimationLoop("windmill", true); + } + } + } + _mainDeletionQueue.push_function([&]() { _resourceManager->destroy_image(_whiteImage); _resourceManager->destroy_image(_greyImage); diff --git a/src/core/game_api.cpp b/src/core/game_api.cpp index e2edb92..2625300 100644 --- a/src/core/game_api.cpp +++ b/src/core/game_api.cpp @@ -526,6 +526,71 @@ bool Engine::add_primitive_instance(const std::string& name, : false; } +bool Engine::add_textured_primitive(const std::string& name, + PrimitiveType type, + const PrimitiveMaterial& material, + const Transform& transform) +{ + AssetManager::MeshGeometryDesc::Type geomType; + switch (type) + { + case PrimitiveType::Cube: geomType = AssetManager::MeshGeometryDesc::Type::Cube; break; + case PrimitiveType::Sphere: geomType = AssetManager::MeshGeometryDesc::Type::Sphere; break; + case PrimitiveType::Plane: geomType = AssetManager::MeshGeometryDesc::Type::Plane; break; + case PrimitiveType::Capsule: geomType = AssetManager::MeshGeometryDesc::Type::Capsule; break; + default: return false; + } + + AssetManager::MeshMaterialDesc matDesc; + matDesc.kind = AssetManager::MeshMaterialDesc::Kind::Textured; + matDesc.options.albedoPath = material.albedoPath; + matDesc.options.metalRoughPath = material.metalRoughPath; + matDesc.options.normalPath = material.normalPath; + matDesc.options.occlusionPath = material.occlusionPath; + matDesc.options.emissivePath = material.emissivePath; + matDesc.options.constants.colorFactors = material.colorFactor; + matDesc.options.constants.metal_rough_factors = glm::vec4(material.metallic, material.roughness, 0.0f, 0.0f); + + return _engine->addPrimitiveInstance(name, geomType, transform.to_matrix(), matDesc); +} + +bool Engine::add_textured_primitive(const std::string& name, + PrimitiveType type, + const PrimitiveMaterial& material, + const TransformD& transform) +{ + AssetManager::MeshGeometryDesc::Type geomType; + switch (type) + { + case PrimitiveType::Cube: geomType = AssetManager::MeshGeometryDesc::Type::Cube; break; + case PrimitiveType::Sphere: geomType = AssetManager::MeshGeometryDesc::Type::Sphere; break; + case PrimitiveType::Plane: geomType = AssetManager::MeshGeometryDesc::Type::Plane; break; + case PrimitiveType::Capsule: geomType = AssetManager::MeshGeometryDesc::Type::Capsule; break; + default: return false; + } + + AssetManager::MeshMaterialDesc matDesc; + matDesc.kind = AssetManager::MeshMaterialDesc::Kind::Textured; + matDesc.options.albedoPath = material.albedoPath; + matDesc.options.metalRoughPath = material.metalRoughPath; + matDesc.options.normalPath = material.normalPath; + matDesc.options.occlusionPath = material.occlusionPath; + matDesc.options.emissivePath = material.emissivePath; + matDesc.options.constants.colorFactors = material.colorFactor; + matDesc.options.constants.metal_rough_factors = glm::vec4(material.metallic, material.roughness, 0.0f, 0.0f); + + if (!_engine->addPrimitiveInstance(name, geomType, glm::mat4(1.0f), matDesc)) + { + return false; + } + return _engine->_sceneManager + ? _engine->_sceneManager->setMeshInstanceTRSWorld(name, + WorldVec3(transform.position), + transform.rotation, + transform.scale) + : false; +} + bool Engine::remove_mesh_instance(const std::string& name) { return _engine->_sceneManager ? _engine->_sceneManager->removeMeshInstance(name) : false; diff --git a/src/core/game_api.h b/src/core/game_api.h index 4c9bf58..8d857c0 100644 --- a/src/core/game_api.h +++ b/src/core/game_api.h @@ -51,6 +51,20 @@ enum class PrimitiveType Capsule }; +// Material description for textured primitives +struct PrimitiveMaterial +{ + std::string albedoPath; // Color/diffuse texture (relative to assets/) + std::string metalRoughPath; // Metallic (R) + Roughness (G) texture + std::string normalPath; // Tangent-space normal map + std::string occlusionPath; // Ambient occlusion (R channel) + std::string emissivePath; // Emissive map + + glm::vec4 colorFactor{1.0f}; // Base color multiplier (RGBA) + float metallic{0.0f}; // Metallic factor (0-1) + float roughness{0.5f}; // Roughness factor (0-1) +}; + // Point light data struct PointLight { @@ -288,6 +302,16 @@ public: PrimitiveType type, const TransformD& transform); + // Add primitive mesh instance with textures + bool add_textured_primitive(const std::string& name, + PrimitiveType type, + const PrimitiveMaterial& material, + const Transform& transform = {}); + bool add_textured_primitive(const std::string& name, + PrimitiveType type, + const PrimitiveMaterial& material, + const TransformD& transform); + // Remove mesh instance (primitives or custom meshes) bool remove_mesh_instance(const std::string& name); diff --git a/src/scene/vk_loader.cpp b/src/scene/vk_loader.cpp index 108ea84..a83a7b9 100644 --- a/src/scene/vk_loader.cpp +++ b/src/scene/vk_loader.cpp @@ -244,10 +244,16 @@ std::optional > loadGltf(VulkanEngine *engine, gltf.images.size(), gltf.samplers.size()); + // One material descriptor set binds: + // - 1x uniform buffer (material constants) + // - 5x combined image samplers (baseColor, metalRough, normal, occlusion, emissive) + // + // The pool must have at least these counts per material. If the ratio is too low + // (e.g. 3 samplers) and the asset has only 1 material, allocating the first set + // can fail with VK_ERROR_OUT_OF_POOL_MEMORY. std::vector sizes = { - {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3}, - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1} + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 5}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1}, }; file.descriptorPool.init(engine->_deviceManager->device(), gltf.materials.size(), sizes);