ADD: animation and primitive test completed

This commit is contained in:
2025-12-21 17:09:36 +09:00
parent a3c0029723
commit 79f3a7f0f9
5 changed files with 232 additions and 3 deletions

View File

@@ -384,6 +384,99 @@ Docs: `docs/Scene.md`
- Spawn primitives or dynamic meshes at runtime (e.g. projectiles, props). - Spawn primitives or dynamic meshes at runtime (e.g. projectiles, props).
- Use `setMeshInstanceTransform` every frame to move them based on game logic. - 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) - GLTF instances (actors)
- `void addGLTFInstance(const std::string &name, std::shared_ptr<LoadedGLTF> scene, const glm::mat4 &transform = glm::mat4(1.f));` - `void addGLTFInstance(const std::string &name, std::shared_ptr<LoadedGLTF> scene, const glm::mat4 &transform = glm::mat4(1.f));`
- `bool getGLTFInstanceTransform(const std::string &name, glm::mat4 &outTransform);` - `bool getGLTFInstanceTransform(const std::string &name, glm::mat4 &outTransform);`

View File

@@ -604,11 +604,52 @@ void VulkanEngine::init_default_data()
BoundsType::Sphere); 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))) if (addGLTFInstance("mirage", "mirage2000/scene.gltf", glm::mat4(1.0f)))
{ {
preloadInstanceTextures("mirage"); 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([&]() { _mainDeletionQueue.push_function([&]() {
_resourceManager->destroy_image(_whiteImage); _resourceManager->destroy_image(_whiteImage);
_resourceManager->destroy_image(_greyImage); _resourceManager->destroy_image(_greyImage);

View File

@@ -526,6 +526,71 @@ bool Engine::add_primitive_instance(const std::string& name,
: false; : 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) bool Engine::remove_mesh_instance(const std::string& name)
{ {
return _engine->_sceneManager ? _engine->_sceneManager->removeMeshInstance(name) : false; return _engine->_sceneManager ? _engine->_sceneManager->removeMeshInstance(name) : false;

View File

@@ -51,6 +51,20 @@ enum class PrimitiveType
Capsule 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 // Point light data
struct PointLight struct PointLight
{ {
@@ -288,6 +302,16 @@ public:
PrimitiveType type, PrimitiveType type,
const TransformD& transform); 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) // Remove mesh instance (primitives or custom meshes)
bool remove_mesh_instance(const std::string& name); bool remove_mesh_instance(const std::string& name);

View File

@@ -244,10 +244,16 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine,
gltf.images.size(), gltf.images.size(),
gltf.samplers.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<DescriptorAllocatorGrowable::PoolSizeRatio> sizes = { std::vector<DescriptorAllocatorGrowable::PoolSizeRatio> sizes = {
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 5},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}
}; };
file.descriptorPool.init(engine->_deviceManager->device(), gltf.materials.size(), sizes); file.descriptorPool.init(engine->_deviceManager->device(), gltf.materials.size(), sizes);