From 620a69b0c04fb0c02dfaa9d1dd1f97db55b33596 Mon Sep 17 00:00:00 2001 From: jyxiong Date: Sun, 4 Jan 2026 18:03:08 +0800 Subject: [PATCH 1/2] rafactor: mesh & primitive --- source/paimon/app/panel/scene_panel.cpp | 307 +++++++++++++++++++++++- source/paimon/core/ecs/components.h | 12 +- source/paimon/core/io/gltf.cpp | 35 ++- source/paimon/opengl/type.h | 2 +- 4 files changed, 328 insertions(+), 28 deletions(-) diff --git a/source/paimon/app/panel/scene_panel.cpp b/source/paimon/app/panel/scene_panel.cpp index 26cc2de..b95a400 100644 --- a/source/paimon/app/panel/scene_panel.cpp +++ b/source/paimon/app/panel/scene_panel.cpp @@ -155,32 +155,323 @@ void ScenePanel::drawComponents(ecs::Entity entity) { // Camera component if (entity.hasComponent()) { if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("Camera Component"); - // TODO: Add camera properties editing + auto &cameraComp = entity.getComponent(); + + if (cameraComp.camera) { + auto cameraType = cameraComp.camera->getType(); + + // Camera type dropdown + const char* types[] = { "Perspective", "Orthographic" }; + int currentType = (cameraType == sg::Camera::Type::Perspective) ? 0 : 1; + + if (ImGui::Combo("Type", ¤tType, types, 2)) { + // Switch camera type + if (currentType == 0 && cameraType != sg::Camera::Type::Perspective) { + cameraComp.camera = std::make_shared(); + } else if (currentType == 1 && cameraType != sg::Camera::Type::Orthographic) { + cameraComp.camera = std::make_shared(); + } + } + + ImGui::Separator(); + + // Perspective camera properties + if (cameraComp.camera->getType() == sg::Camera::Type::Perspective) { + auto* perspCamera = dynamic_cast(cameraComp.camera.get()); + if (perspCamera) { + // Convert FOV to degrees for display + float fovDegrees = glm::degrees(perspCamera->yfov); + if (ImGui::SliderFloat("Field of View", &fovDegrees, 1.0f, 120.0f, "%.1f°")) { + perspCamera->yfov = glm::radians(fovDegrees); + } + + ImGui::DragFloat("Aspect Ratio", &perspCamera->aspectRatio, 0.01f, 0.1f, 10.0f); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("0 = use viewport aspect ratio"); + } + + ImGui::DragFloat("Near Plane", &perspCamera->znear, 0.01f, 0.001f, 10.0f, "%.3f"); + ImGui::DragFloat("Far Plane", &perspCamera->zfar, 1.0f, 1.0f, 1000.0f); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("0 = infinite far plane"); + } + } + } + // Orthographic camera properties + else if (cameraComp.camera->getType() == sg::Camera::Type::Orthographic) { + auto* orthoCamera = dynamic_cast(cameraComp.camera.get()); + if (orthoCamera) { + ImGui::DragFloat("Horizontal Mag", &orthoCamera->xmag, 0.1f, 0.1f, 100.0f); + ImGui::DragFloat("Vertical Mag", &orthoCamera->ymag, 0.1f, 0.1f, 100.0f); + ImGui::DragFloat("Near Plane", &orthoCamera->znear, 0.01f, 0.001f, 10.0f, "%.3f"); + ImGui::DragFloat("Far Plane", &orthoCamera->zfar, 1.0f, 1.0f, 1000.0f); + } + } + + ImGui::Separator(); + + // Display camera vectors (read-only) + ImGui::Text("Position: (%.2f, %.2f, %.2f)", + cameraComp.position.x, cameraComp.position.y, cameraComp.position.z); + ImGui::Text("Direction: (%.2f, %.2f, %.2f)", + cameraComp.direction.x, cameraComp.direction.y, cameraComp.direction.z); + } else { + ImGui::Text("No camera object assigned"); + } } } // Mesh component if (entity.hasComponent()) { if (ImGui::CollapsingHeader("Mesh", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("Mesh Component"); - // TODO: Add mesh properties + auto &meshComp = entity.getComponent(); + + if (meshComp.mesh) { + ImGui::Text("Primitives: %zu", meshComp.mesh->primitives.size()); + + ImGui::Separator(); + + // Display each primitive + for (size_t i = 0; i < meshComp.mesh->primitives.size(); ++i) { + const auto &primitive = meshComp.mesh->primitives[i]; + + if (ImGui::TreeNode((void*)(intptr_t)i, "Primitive %zu", i)) { + // Topology mode + const char* topologyName = "Unknown"; + switch (primitive.mode) { + case PrimitiveTopology::Points: topologyName = "Points"; break; + case PrimitiveTopology::Lines: topologyName = "Lines"; break; + case PrimitiveTopology::LineLoop: topologyName = "Line Loop"; break; + case PrimitiveTopology::LineStrip: topologyName = "Line Strip"; break; + case PrimitiveTopology::Triangles: topologyName = "Triangles"; break; + case PrimitiveTopology::TriangleStrip: topologyName = "Triangle Strip"; break; + case PrimitiveTopology::TriangleFan: topologyName = "Triangle Fan"; break; + default: break; + } + ImGui::Text("Topology: %s", topologyName); + + // Vertex and index counts + ImGui::Text("Vertices: %zu", primitive.vertexCount); + if (primitive.hasIndices()) { + ImGui::Text("Indices: %zu", primitive.indexCount); + } else { + ImGui::Text("Indices: None"); + } + + ImGui::Separator(); + + // Vertex attributes + ImGui::Text("Vertex Attributes:"); + ImGui::Indent(); + ImGui::Text("Positions: %s", primitive.positions ? "Yes" : "No"); + ImGui::Text("Normals: %s", primitive.normals ? "Yes" : "No"); + ImGui::Text("Texcoords: %s", primitive.texcoords ? "Yes" : "No"); + ImGui::Text("Colors: %s", primitive.colors ? "Yes" : "No"); + ImGui::Unindent(); + + ImGui::Separator(); + + // Material + if (primitive.material) { + ImGui::Text("Material: Assigned"); + } else { + ImGui::Text("Material: None"); + } + + ImGui::TreePop(); + } + } + } else { + ImGui::Text("No mesh object assigned"); + } } } // Material component if (entity.hasComponent()) { if (ImGui::CollapsingHeader("Material", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("Material Component"); - // TODO: Add material properties editing + auto &materialComp = entity.getComponent(); + + if (materialComp.material) { + auto &material = *materialComp.material; + + // PBR Metallic-Roughness + if (ImGui::TreeNodeEx("PBR Metallic-Roughness", ImGuiTreeNodeFlags_DefaultOpen)) { + auto &pbr = material.pbrMetallicRoughness; + + ImGui::ColorEdit4("Base Color", glm::value_ptr(pbr.baseColorFactor)); + ImGui::Text("Base Color Texture: %s", pbr.baseColorTexture ? "Assigned" : "None"); + + ImGui::SliderFloat("Metallic", &pbr.metallicFactor, 0.0f, 1.0f); + ImGui::SliderFloat("Roughness", &pbr.roughnessFactor, 0.0f, 1.0f); + ImGui::Text("Metallic-Roughness Texture: %s", + pbr.metallicRoughnessTexture ? "Assigned" : "None"); + + ImGui::TreePop(); + } + + // Normal Mapping + if (ImGui::TreeNodeEx("Normal Mapping", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text("Normal Texture: %s", material.normalTexture ? "Assigned" : "None"); + if (material.normalTexture) { + ImGui::SliderFloat("Normal Scale", &material.normalScale, 0.0f, 2.0f); + } + ImGui::TreePop(); + } + + // Occlusion Mapping + if (ImGui::TreeNodeEx("Occlusion Mapping", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text("Occlusion Texture: %s", material.occlusionTexture ? "Assigned" : "None"); + if (material.occlusionTexture) { + ImGui::SliderFloat("Occlusion Strength", &material.occlusionStrength, 0.0f, 1.0f); + } + ImGui::TreePop(); + } + + // Emissive + if (ImGui::TreeNodeEx("Emissive", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::ColorEdit3("Emissive Factor", glm::value_ptr(material.emissiveFactor)); + ImGui::Text("Emissive Texture: %s", material.emissiveTexture ? "Assigned" : "None"); + ImGui::TreePop(); + } + + ImGui::Separator(); + + // Alpha Mode + const char* alphaModes[] = { "Opaque", "Mask", "Blend" }; + int currentAlphaMode = static_cast(material.alphaMode); + if (ImGui::Combo("Alpha Mode", ¤tAlphaMode, alphaModes, 3)) { + material.alphaMode = static_cast(currentAlphaMode); + } + + if (material.alphaMode == sg::AlphaMode::Mask) { + ImGui::SliderFloat("Alpha Cutoff", &material.alphaCutoff, 0.0f, 1.0f); + } + + ImGui::Checkbox("Double Sided", &material.doubleSided); + + // Anisotropy (KHR_materials_anisotropy) + if (ImGui::TreeNode("Anisotropy")) { + ImGui::SliderFloat("Strength##Aniso", &material.anisotropy.strength, 0.0f, 1.0f); + ImGui::SliderFloat("Rotation##Aniso", &material.anisotropy.rotation, 0.0f, 360.0f, "%.1f°"); + ImGui::Text("Texture: %s", material.anisotropy.texture ? "Assigned" : "None"); + ImGui::TreePop(); + } + + // Clearcoat (KHR_materials_clearcoat) + if (ImGui::TreeNode("Clearcoat")) { + ImGui::SliderFloat("Factor##Clearcoat", &material.clearcoat.factor, 0.0f, 1.0f); + ImGui::Text("Texture: %s", material.clearcoat.texture ? "Assigned" : "None"); + ImGui::SliderFloat("Roughness##Clearcoat", &material.clearcoat.roughnessFactor, 0.0f, 1.0f); + ImGui::Text("Roughness Texture: %s", + material.clearcoat.roughnessTexture ? "Assigned" : "None"); + ImGui::Text("Normal Texture: %s", + material.clearcoat.normalTexture ? "Assigned" : "None"); + if (material.clearcoat.normalTexture) { + ImGui::SliderFloat("Normal Scale##Clearcoat", &material.clearcoat.normalScale, 0.0f, 2.0f); + } + ImGui::TreePop(); + } + + } else { + ImGui::Text("No material object assigned"); + } } } // Light component if (entity.hasComponent()) { if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("Light Component"); - // TODO: Add light properties editing + auto &lightComp = entity.getComponent(); + + if (lightComp.light) { + auto lightType = lightComp.light->getType(); + + // Light type dropdown + const char* types[] = { "Directional", "Point", "Spot" }; + int currentType = static_cast(lightType); + + if (ImGui::Combo("Type", ¤tType, types, 3)) { + // Switch light type + switch (currentType) { + case 0: // Directional + if (lightType != sg::PunctualLight::Type::Directional) { + lightComp.light = std::make_shared(); + } + break; + case 1: // Point + if (lightType != sg::PunctualLight::Type::Point) { + lightComp.light = std::make_shared(); + } + break; + case 2: // Spot + if (lightType != sg::PunctualLight::Type::Spot) { + lightComp.light = std::make_shared(); + } + break; + } + } + + ImGui::Separator(); + + // Common light properties + ImGui::ColorEdit3("Color", glm::value_ptr(lightComp.light->color)); + ImGui::DragFloat("Intensity", &lightComp.light->intensity, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Range", &lightComp.light->range, 0.1f, 0.0f, 1000.0f); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("0 = infinite range"); + } + + ImGui::Separator(); + + // Type-specific properties + if (lightType == sg::PunctualLight::Type::Directional) { + ImGui::Text("Direction: (%.2f, %.2f, %.2f)", + lightComp.direction.x, lightComp.direction.y, lightComp.direction.z); + ImGui::TextWrapped("Tip: Direction is controlled by Transform rotation"); + } + else if (lightType == sg::PunctualLight::Type::Point) { + ImGui::Text("Position: (%.2f, %.2f, %.2f)", + lightComp.position.x, lightComp.position.y, lightComp.position.z); + ImGui::TextWrapped("Tip: Position is controlled by Transform translation"); + } + else if (lightType == sg::PunctualLight::Type::Spot) { + auto* spotLight = dynamic_cast(lightComp.light.get()); + if (spotLight) { + ImGui::Text("Position: (%.2f, %.2f, %.2f)", + lightComp.position.x, lightComp.position.y, lightComp.position.z); + ImGui::Text("Direction: (%.2f, %.2f, %.2f)", + lightComp.direction.x, lightComp.direction.y, lightComp.direction.z); + + ImGui::Separator(); + + // Convert cone angles to degrees for display + float innerDegrees = glm::degrees(spotLight->innerConeAngle); + float outerDegrees = glm::degrees(spotLight->outerConeAngle); + + if (ImGui::SliderFloat("Inner Cone Angle", &innerDegrees, 0.0f, 90.0f, "%.1f°")) { + spotLight->innerConeAngle = glm::radians(innerDegrees); + // Ensure inner <= outer + if (spotLight->innerConeAngle > spotLight->outerConeAngle) { + spotLight->outerConeAngle = spotLight->innerConeAngle; + } + } + + if (ImGui::SliderFloat("Outer Cone Angle", &outerDegrees, 0.0f, 90.0f, "%.1f°")) { + spotLight->outerConeAngle = glm::radians(outerDegrees); + // Ensure outer >= inner + if (spotLight->outerConeAngle < spotLight->innerConeAngle) { + spotLight->innerConeAngle = spotLight->outerConeAngle; + } + } + + ImGui::TextWrapped("Tip: Position and direction are controlled by Transform"); + } + } + + } else { + ImGui::Text("No light object assigned"); + } } } } diff --git a/source/paimon/core/ecs/components.h b/source/paimon/core/ecs/components.h index 33f7445..f73fcea 100644 --- a/source/paimon/core/ecs/components.h +++ b/source/paimon/core/ecs/components.h @@ -9,7 +9,6 @@ #include "paimon/core/ecs/entity.h" #include "paimon/core/sg/camera.h" #include "paimon/core/sg/light.h" -#include "paimon/core/sg/material.h" #include "paimon/core/sg/mesh.h" namespace paimon { @@ -50,14 +49,9 @@ struct GlobalTransform { glm::mat4 matrix = glm::mat4(1.0f); }; -/// Mesh component - references mesh data -struct Mesh { - std::shared_ptr mesh; -}; - -/// Material component - references material data -struct Material { - std::shared_ptr material; +/// Primitive component - references primitive data +struct Primitive { + std::shared_ptr primitive; }; /// Perspective camera parameters diff --git a/source/paimon/core/io/gltf.cpp b/source/paimon/core/io/gltf.cpp index 0112d5e..4bd9e81 100644 --- a/source/paimon/core/io/gltf.cpp +++ b/source/paimon/core/io/gltf.cpp @@ -26,7 +26,7 @@ PrimitiveTopology parsePrimitiveMode(int primitiveMode) { case TINYGLTF_MODE_LINE: return PrimitiveTopology::Lines; case TINYGLTF_MODE_LINE_LOOP: - return PrimitiveTopology::LinesLoop; + return PrimitiveTopology::LineLoop; case TINYGLTF_MODE_LINE_STRIP: return PrimitiveTopology::LineStrip; case TINYGLTF_MODE_TRIANGLES: @@ -442,18 +442,18 @@ void GltfLoader::parseCameras() { void GltfLoader::parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs::Scene &scene) { // Create entity for this node - auto entity = scene.createEntity(node.name); + auto nodeEntity = scene.createEntity(node.name); // Parent components - auto &parentComp = entity.getComponent(); + auto &parentComp = nodeEntity.getComponent(); parentComp.parent = parent; // Parent should have a Children component; append this child auto &parentChildren = parent.getComponent(); - parentChildren.children.push_back(entity); + parentChildren.children.push_back(nodeEntity); // Transform component (TRS only) - auto &transform = entity.getComponent(); + auto &transform = nodeEntity.getComponent(); if (node.matrix.empty()) { // Use TRS directly transform.translation = node.translation.empty() @@ -473,21 +473,36 @@ void GltfLoader::parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs:: transform.translation, skew, perspective); } - // Mesh Component + // Mesh Component - create child entities for each primitive if (node.mesh >= 0) { + const auto& sg_mesh = m_meshes[node.mesh]; - - entity.addComponent(m_meshes[node.mesh]); + for (size_t i = 0; i < sg_mesh->primitives.size(); ++i) { + // Create child entity for each primitive + auto primitiveEntity = scene.createEntity(node.name + "_primitive_" + std::to_string(i)); + + // Set parent relationship + auto& primitiveParentComp = primitiveEntity.getComponent(); + primitiveParentComp.parent = nodeEntity; + + // Add to parent's children + auto& nodeChildren = nodeEntity.getComponent(); + nodeChildren.children.push_back(primitiveEntity); + + // Add Primitive component + auto primitive = std::make_shared(sg_mesh->primitives[i]); + primitiveEntity.addComponent(primitive); + } } // Light Component (from KHR_lights_punctual extension) if (node.light >= 0) { - entity.addComponent(m_lights[node.light]); + nodeEntity.addComponent(m_lights[node.light]); } if (node.camera >= 0) { // Camera Component can be handled here if needed - entity.addComponent(m_cameras[node.camera]); + nodeEntity.addComponent(m_cameras[node.camera]); } // Skin Component diff --git a/source/paimon/opengl/type.h b/source/paimon/opengl/type.h index 9c22b28..955b8b9 100644 --- a/source/paimon/opengl/type.h +++ b/source/paimon/opengl/type.h @@ -216,7 +216,7 @@ enum class PrimitiveTopology : GLenum { Points = GL_POINTS, Lines = GL_LINES, LineAdjacency = GL_LINES_ADJACENCY, - LinesLoop = GL_LINE_LOOP, + LineLoop = GL_LINE_LOOP, LineStrip = GL_LINE_STRIP, LineStripAdjacency = GL_LINE_STRIP_ADJACENCY, Triangles = GL_TRIANGLES, From 62a7a61b1f1b403e67632d4a382447cdfc6d3206 Mon Sep 17 00:00:00 2001 From: jyxiong Date: Mon, 5 Jan 2026 18:03:43 +0800 Subject: [PATCH 2/2] fix --- example/damaged_helmet/color_pass.cpp | 128 ++++++++++++------------ source/paimon/app/panel/scene_panel.cpp | 106 +++++++++----------- source/paimon/core/ecs/components.h | 6 ++ source/paimon/core/io/gltf.cpp | 15 ++- source/paimon/core/sg/mesh.h | 3 - 5 files changed, 127 insertions(+), 131 deletions(-) diff --git a/example/damaged_helmet/color_pass.cpp b/example/damaged_helmet/color_pass.cpp index 1b6f4bf..30d38e2 100644 --- a/example/damaged_helmet/color_pass.cpp +++ b/example/damaged_helmet/color_pass.cpp @@ -170,85 +170,81 @@ void ColorPass::draw(RenderContext &ctx, const glm::ivec2 &resolution, // Set viewport ctx.setViewport(0, 0, resolution.x, resolution.y); - // Iterate over entities with Mesh component - auto meshView = scene.view(); - for (auto entity : meshView) { - auto &mesh = meshView.get(entity).mesh; - auto &transform = meshView.get(entity); - - if (!mesh) + // Iterate over entities with Primitive and Material components + auto primitiveView = scene.view(); + for (auto [entity, primitiveComp, materialComp, transform] : primitiveView.each()) { + if (!primitiveComp.primitive) continue; - // Update uniform buffers + const auto &primitive = *primitiveComp.primitive; + + // Update transform uniform buffer TransformUBO transformData; transformData.model = transform.matrix; m_transform_ubo.set_sub_data(0, sizeof(TransformUBO), &transformData); - // Render each primitive in the mesh - for (const auto &primitive : mesh->primitives) { - // Bind vertex buffers - if (primitive.positions) { - ctx.bindVertexBuffer(0, *primitive.positions, 0, sizeof(glm::vec3)); - } - if (primitive.normals) { - ctx.bindVertexBuffer(1, *primitive.normals, 0, sizeof(glm::vec3)); + // Bind vertex buffers + if (primitive.positions) { + ctx.bindVertexBuffer(0, *primitive.positions, 0, sizeof(glm::vec3)); + } + if (primitive.normals) { + ctx.bindVertexBuffer(1, *primitive.normals, 0, sizeof(glm::vec3)); + } + if (primitive.texcoords) { + ctx.bindVertexBuffer(2, *primitive.texcoords, 0, sizeof(glm::vec2)); + } + if (primitive.colors) { + ctx.bindVertexBuffer(3, *primitive.colors, 0, sizeof(glm::vec3)); + } + + // Bind index buffer if present + if (primitive.indices) { + ctx.bindIndexBuffer(*primitive.indices, primitive.indexType); + } + + // Update material UBO and bind textures from Material component + if (materialComp.material) { + const auto &mat = materialComp.material; + const auto &pbr = mat->pbrMetallicRoughness; + + // Prepare material data + MaterialUBO materialData; + materialData.baseColorFactor = pbr.baseColorFactor; + materialData.emissiveFactor = mat->emissiveFactor; + materialData.metallicFactor = pbr.metallicFactor; + materialData.roughnessFactor = pbr.roughnessFactor; + m_material_ubo.set_sub_data(0, sizeof(MaterialUBO), &materialData); + + // Bind textures with sampler + if (pbr.baseColorTexture && pbr.baseColorTexture->image) { + ctx.bindTexture(0, *pbr.baseColorTexture->image, *m_sampler); } - if (primitive.texcoords) { - ctx.bindVertexBuffer(2, *primitive.texcoords, 0, sizeof(glm::vec2)); + if (pbr.metallicRoughnessTexture && + pbr.metallicRoughnessTexture->image) { + ctx.bindTexture(1, *pbr.metallicRoughnessTexture->image, + *m_sampler); } - if (primitive.colors) { - ctx.bindVertexBuffer(3, *primitive.colors, 0, sizeof(glm::vec3)); + if (mat->normalTexture && mat->normalTexture->image) { + ctx.bindTexture(2, *mat->normalTexture->image, *m_sampler); } - - // Bind index buffer if present - if (primitive.indices) { - ctx.bindIndexBuffer(*primitive.indices, primitive.indexType); + if (mat->emissiveTexture && mat->emissiveTexture->image) { + ctx.bindTexture(3, *mat->emissiveTexture->image, *m_sampler); } - - // Update material UBO and bind textures - if (primitive.material) { - const auto &mat = primitive.material; - const auto &pbr = mat->pbrMetallicRoughness; - - // Prepare material data - MaterialUBO materialData; - materialData.baseColorFactor = pbr.baseColorFactor; - materialData.emissiveFactor = mat->emissiveFactor; - materialData.metallicFactor = pbr.metallicFactor; - materialData.roughnessFactor = pbr.roughnessFactor; - m_material_ubo.set_sub_data(0, sizeof(MaterialUBO), &materialData); - - // Bind textures with sampler - directly from material - if (pbr.baseColorTexture && pbr.baseColorTexture->image) { - ctx.bindTexture(0, *pbr.baseColorTexture->image, *m_sampler); - } - if (pbr.metallicRoughnessTexture && - pbr.metallicRoughnessTexture->image) { - ctx.bindTexture(1, *pbr.metallicRoughnessTexture->image, - *m_sampler); - } - if (mat->normalTexture && mat->normalTexture->image) { - ctx.bindTexture(2, *mat->normalTexture->image, *m_sampler); - } - if (mat->emissiveTexture && mat->emissiveTexture->image) { - ctx.bindTexture(3, *mat->emissiveTexture->image, *m_sampler); - } - if (mat->occlusionTexture && mat->occlusionTexture->image) { - ctx.bindTexture(4, *mat->occlusionTexture->image, *m_sampler); - } + if (mat->occlusionTexture && mat->occlusionTexture->image) { + ctx.bindTexture(4, *mat->occlusionTexture->image, *m_sampler); } + } - ctx.bindUniformBuffer(0, m_transform_ubo); - ctx.bindUniformBuffer(1, m_camera_ubo); - ctx.bindUniformBuffer(2, m_lighting_ubo); - ctx.bindUniformBuffer(3, m_material_ubo); + ctx.bindUniformBuffer(0, m_transform_ubo); + ctx.bindUniformBuffer(1, m_camera_ubo); + ctx.bindUniformBuffer(2, m_lighting_ubo); + ctx.bindUniformBuffer(3, m_material_ubo); - // Draw the primitive - if (primitive.hasIndices()) { - ctx.drawElements(primitive.indexCount, nullptr); - } else if (primitive.positions) { - ctx.drawArrays(0, primitive.vertexCount); - } + // Draw the primitive + if (primitive.hasIndices()) { + ctx.drawElements(primitive.indexCount, nullptr); + } else if (primitive.positions) { + ctx.drawArrays(0, primitive.vertexCount); } } diff --git a/source/paimon/app/panel/scene_panel.cpp b/source/paimon/app/panel/scene_panel.cpp index b95a400..539232b 100644 --- a/source/paimon/app/panel/scene_panel.cpp +++ b/source/paimon/app/panel/scene_panel.cpp @@ -221,68 +221,60 @@ void ScenePanel::drawComponents(ecs::Entity entity) { } } - // Mesh component - if (entity.hasComponent()) { - if (ImGui::CollapsingHeader("Mesh", ImGuiTreeNodeFlags_DefaultOpen)) { - auto &meshComp = entity.getComponent(); + // Primitive component + if (entity.hasComponent()) { + if (ImGui::CollapsingHeader("Primitive", ImGuiTreeNodeFlags_DefaultOpen)) { + auto &primitiveComp = entity.getComponent(); - if (meshComp.mesh) { - ImGui::Text("Primitives: %zu", meshComp.mesh->primitives.size()); + if (primitiveComp.primitive) { + auto &primitive = *primitiveComp.primitive; + + // Topology + const char* topologyNames[] = { + "Points", "Lines", "Line Loop", "Line Strip", + "Triangles", "Triangle Strip", "Triangle Fan" + }; + int topologyIndex = static_cast(primitive.mode); + ImGui::Text("Topology: %s", topologyNames[topologyIndex]); + + ImGui::Separator(); + + // Vertex data + ImGui::Text("Vertex Count: %zu", primitive.vertexCount); + + ImGui::Text("Attributes:"); + ImGui::Indent(); + ImGui::Text("Positions: %s", primitive.positions ? "✓" : "✗"); + ImGui::Text("Normals: %s", primitive.normals ? "✓" : "✗"); + ImGui::Text("Texcoords: %s", primitive.texcoords ? "✓" : "✗"); + ImGui::Text("Colors: %s", primitive.colors ? "✓" : "✗"); + ImGui::Unindent(); ImGui::Separator(); - // Display each primitive - for (size_t i = 0; i < meshComp.mesh->primitives.size(); ++i) { - const auto &primitive = meshComp.mesh->primitives[i]; + // Index data + if (primitive.hasIndices()) { + ImGui::Text("Index Count: %zu", primitive.indexCount); - if (ImGui::TreeNode((void*)(intptr_t)i, "Primitive %zu", i)) { - // Topology mode - const char* topologyName = "Unknown"; - switch (primitive.mode) { - case PrimitiveTopology::Points: topologyName = "Points"; break; - case PrimitiveTopology::Lines: topologyName = "Lines"; break; - case PrimitiveTopology::LineLoop: topologyName = "Line Loop"; break; - case PrimitiveTopology::LineStrip: topologyName = "Line Strip"; break; - case PrimitiveTopology::Triangles: topologyName = "Triangles"; break; - case PrimitiveTopology::TriangleStrip: topologyName = "Triangle Strip"; break; - case PrimitiveTopology::TriangleFan: topologyName = "Triangle Fan"; break; - default: break; - } - ImGui::Text("Topology: %s", topologyName); - - // Vertex and index counts - ImGui::Text("Vertices: %zu", primitive.vertexCount); - if (primitive.hasIndices()) { - ImGui::Text("Indices: %zu", primitive.indexCount); - } else { - ImGui::Text("Indices: None"); - } - - ImGui::Separator(); - - // Vertex attributes - ImGui::Text("Vertex Attributes:"); - ImGui::Indent(); - ImGui::Text("Positions: %s", primitive.positions ? "Yes" : "No"); - ImGui::Text("Normals: %s", primitive.normals ? "Yes" : "No"); - ImGui::Text("Texcoords: %s", primitive.texcoords ? "Yes" : "No"); - ImGui::Text("Colors: %s", primitive.colors ? "Yes" : "No"); - ImGui::Unindent(); - - ImGui::Separator(); - - // Material - if (primitive.material) { - ImGui::Text("Material: Assigned"); - } else { - ImGui::Text("Material: None"); - } - - ImGui::TreePop(); + // Map DataType enum to display string + const char* indexTypeName = "Unknown"; + switch (primitive.indexType) { + case DataType::Byte: indexTypeName = "Byte"; break; + case DataType::UByte: indexTypeName = "UByte"; break; + case DataType::Short: indexTypeName = "Short"; break; + case DataType::UShort: indexTypeName = "UShort"; break; + case DataType::Int: indexTypeName = "Int"; break; + case DataType::UInt: indexTypeName = "UInt"; break; + case DataType::Float: indexTypeName = "Float"; break; + case DataType::Double: indexTypeName = "Double"; break; } + ImGui::Text("Index Type: %s", indexTypeName); + } else { + ImGui::Text("No Indices (Non-indexed rendering)"); } + } else { - ImGui::Text("No mesh object assigned"); + ImGui::Text("No primitive object assigned"); } } } @@ -496,9 +488,9 @@ void ScenePanel::drawAddComponentButton(ecs::Entity entity) { } } - if (!entity.hasComponent()) { - if (ImGui::MenuItem("Mesh")) { - entity.addComponent(); + if (!entity.hasComponent()) { + if (ImGui::MenuItem("Primitive")) { + entity.addComponent(); } } diff --git a/source/paimon/core/ecs/components.h b/source/paimon/core/ecs/components.h index f73fcea..a6251f1 100644 --- a/source/paimon/core/ecs/components.h +++ b/source/paimon/core/ecs/components.h @@ -9,6 +9,7 @@ #include "paimon/core/ecs/entity.h" #include "paimon/core/sg/camera.h" #include "paimon/core/sg/light.h" +#include "paimon/core/sg/material.h" #include "paimon/core/sg/mesh.h" namespace paimon { @@ -54,6 +55,11 @@ struct Primitive { std::shared_ptr primitive; }; +/// Material component - references material data +struct Material { + std::shared_ptr material; +}; + /// Perspective camera parameters struct Camera { std::shared_ptr camera; diff --git a/source/paimon/core/io/gltf.cpp b/source/paimon/core/io/gltf.cpp index 4bd9e81..bff6506 100644 --- a/source/paimon/core/io/gltf.cpp +++ b/source/paimon/core/io/gltf.cpp @@ -372,8 +372,6 @@ void GltfLoader::parseMeshes() { sg_primitive.indexType = parseCompnentType(accessor.componentType); sg_primitive.indices = m_accessors[primitive.indices]; } - - sg_primitive.material = m_materials[primitive.material]; } m_meshes.push_back(std::move(sg_mesh)); } @@ -476,6 +474,7 @@ void GltfLoader::parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs:: // Mesh Component - create child entities for each primitive if (node.mesh >= 0) { const auto& sg_mesh = m_meshes[node.mesh]; + const auto& mesh = m_model.meshes[node.mesh]; for (size_t i = 0; i < sg_mesh->primitives.size(); ++i) { // Create child entity for each primitive @@ -486,12 +485,18 @@ void GltfLoader::parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs:: primitiveParentComp.parent = nodeEntity; // Add to parent's children - auto& nodeChildren = nodeEntity.getComponent(); - nodeChildren.children.push_back(primitiveEntity); + auto& entityChildren = nodeEntity.getComponent(); + entityChildren.children.push_back(primitiveEntity); // Add Primitive component auto primitive = std::make_shared(sg_mesh->primitives[i]); primitiveEntity.addComponent(primitive); + + // Add Material component if available + // Note: In glTF, a mesh primitive can reference a material + if (mesh.primitives[i].material >= 0) { + primitiveEntity.addComponent(m_materials[mesh.primitives[i].material]); + } } } @@ -515,7 +520,7 @@ void GltfLoader::parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs:: // which simplifies GlobalTransform computation later for (int childIndex : node.children) { const auto &childNode = m_model.nodes[childIndex]; - parseNode(childNode, entity, scene); + parseNode(childNode, nodeEntity, scene); } } diff --git a/source/paimon/core/sg/mesh.h b/source/paimon/core/sg/mesh.h index c57d154..233ced7 100644 --- a/source/paimon/core/sg/mesh.h +++ b/source/paimon/core/sg/mesh.h @@ -4,7 +4,6 @@ #include -#include "paimon/core/sg/material.h" #include "paimon/opengl/buffer.h" #include "paimon/opengl/state/vertex_input.h" #include "paimon/opengl/type.h" @@ -25,8 +24,6 @@ struct Primitive { DataType indexType = DataType::UInt; std::shared_ptr indices; - std::shared_ptr material = nullptr; - bool hasIndices() const { return indices != nullptr; } static std::vector bindings();