#include #include #include #include #include #include namespace XCEngine { namespace { using namespace Components; using namespace Resources; Containers::String MakeNodeName(const ModelNode& node, Core::uint32 nodeIndex) { if (!node.name.Empty()) { return node.name; } return Containers::String(("ModelNode_" + std::to_string(nodeIndex)).c_str()); } Containers::String MakeMeshObjectName(const Containers::String& nodeName, Core::uint32 meshBindingOffset) { return Containers::String( (std::string(nodeName.CStr()) + "_Mesh" + std::to_string(meshBindingOffset)).c_str()); } AssetRef MakeSubAssetRef(const AssetRef& modelAssetRef, LocalID localID, ResourceType resourceType) { AssetRef ref; ref.assetGuid = modelAssetRef.assetGuid; ref.localID = localID; ref.resourceType = resourceType; return ref; } void SetErrorMessage(Containers::String* outErrorMessage, const Containers::String& message) { if (outErrorMessage != nullptr) { *outErrorMessage = message; } } bool AttachMeshBinding( GameObject& targetObject, const Model& model, const AssetRef& modelAssetRef, const ModelMeshBinding& meshBinding, Containers::String* outErrorMessage, std::vector* outMeshObjects) { if (!modelAssetRef.IsValid()) { SetErrorMessage(outErrorMessage, "Model asset ref is required when instantiating mesh bindings."); return false; } auto* meshFilter = targetObject.GetComponent(); if (meshFilter == nullptr) { meshFilter = targetObject.AddComponent(); } auto* meshRenderer = targetObject.GetComponent(); if (meshRenderer == nullptr) { meshRenderer = targetObject.AddComponent(); } meshFilter->SetMeshAssetRef(MakeSubAssetRef(modelAssetRef, meshBinding.meshLocalID, ResourceType::Mesh)); const auto& materialBindings = model.GetMaterialBindings(); const Core::uint32 materialBindingEnd = meshBinding.materialBindingStart + meshBinding.materialBindingCount; if (materialBindingEnd > materialBindings.Size()) { SetErrorMessage(outErrorMessage, "Model mesh binding references material bindings outside the model range."); return false; } for (Core::uint32 materialBindingIndex = meshBinding.materialBindingStart; materialBindingIndex < materialBindingEnd; ++materialBindingIndex) { const ModelMaterialBinding& materialBinding = materialBindings[materialBindingIndex]; meshRenderer->SetMaterialAssetRef( static_cast(materialBinding.slotIndex), MakeSubAssetRef(modelAssetRef, materialBinding.materialLocalID, ResourceType::Material)); } if (outMeshObjects != nullptr) { outMeshObjects->push_back(&targetObject); } return true; } } // namespace bool InstantiateModelHierarchy( Components::Scene& scene, const Resources::Model& model, const Resources::AssetRef& modelAssetRef, Components::GameObject* parent, ModelSceneInstantiationResult* outResult, Containers::String* outErrorMessage) { if (!model.IsValid()) { SetErrorMessage(outErrorMessage, "Model is invalid."); return false; } if (!model.HasRootNode()) { SetErrorMessage(outErrorMessage, "Model does not have a root node."); return false; } const auto& nodes = model.GetNodes(); const auto& meshBindings = model.GetMeshBindings(); if (model.GetRootNodeIndex() >= nodes.Size()) { SetErrorMessage(outErrorMessage, "Model root node index is outside the node range."); return false; } ModelSceneInstantiationResult localResult; localResult.nodeObjects.resize(nodes.Size(), nullptr); for (Core::uint32 nodeIndex = 0; nodeIndex < nodes.Size(); ++nodeIndex) { const ModelNode& node = nodes[nodeIndex]; localResult.nodeObjects[nodeIndex] = scene.CreateGameObject(MakeNodeName(node, nodeIndex).CStr(), nullptr); if (localResult.nodeObjects[nodeIndex] == nullptr) { SetErrorMessage(outErrorMessage, "Failed to create a model node game object."); return false; } auto* transform = localResult.nodeObjects[nodeIndex]->GetTransform(); transform->SetLocalPosition(node.localPosition); transform->SetLocalRotation(node.localRotation); transform->SetLocalScale(node.localScale); } for (Core::uint32 nodeIndex = 0; nodeIndex < nodes.Size(); ++nodeIndex) { const ModelNode& node = nodes[nodeIndex]; GameObject* nodeObject = localResult.nodeObjects[nodeIndex]; if (nodeObject == nullptr) { continue; } if (node.parentIndex >= 0) { const Core::uint32 parentIndex = static_cast(node.parentIndex); if (parentIndex >= localResult.nodeObjects.size() || localResult.nodeObjects[parentIndex] == nullptr) { SetErrorMessage(outErrorMessage, "Model node references an invalid parent index."); return false; } nodeObject->SetParent(localResult.nodeObjects[parentIndex], false); } else if (parent != nullptr) { nodeObject->SetParent(parent, false); } } for (Core::uint32 nodeIndex = 0; nodeIndex < nodes.Size(); ++nodeIndex) { const ModelNode& node = nodes[nodeIndex]; GameObject* nodeObject = localResult.nodeObjects[nodeIndex]; if (nodeObject == nullptr || node.meshBindingCount == 0u) { continue; } const Core::uint32 meshBindingEnd = node.meshBindingStart + node.meshBindingCount; if (meshBindingEnd > meshBindings.Size()) { SetErrorMessage(outErrorMessage, "Model node references mesh bindings outside the model range."); return false; } if (node.meshBindingCount == 1u) { if (!AttachMeshBinding( *nodeObject, model, modelAssetRef, meshBindings[node.meshBindingStart], outErrorMessage, &localResult.meshObjects)) { return false; } continue; } const Containers::String nodeName = MakeNodeName(node, nodeIndex); for (Core::uint32 meshBindingIndex = node.meshBindingStart; meshBindingIndex < meshBindingEnd; ++meshBindingIndex) { GameObject* meshObject = scene.CreateGameObject( MakeMeshObjectName(nodeName, meshBindingIndex - node.meshBindingStart).CStr(), nodeObject); if (meshObject == nullptr) { SetErrorMessage(outErrorMessage, "Failed to create an auxiliary mesh binding game object."); return false; } if (!AttachMeshBinding( *meshObject, model, modelAssetRef, meshBindings[meshBindingIndex], outErrorMessage, &localResult.meshObjects)) { return false; } } } localResult.rootObject = localResult.nodeObjects[model.GetRootNodeIndex()]; if (outResult != nullptr) { *outResult = std::move(localResult); } return true; } bool InstantiateModelHierarchy( Components::Scene& scene, const Containers::String& modelPath, Components::GameObject* parent, ModelSceneInstantiationResult* outResult, Containers::String* outErrorMessage) { ResourceManager& resourceManager = ResourceManager::Get(); const ResourceHandle modelHandle = resourceManager.Load(modelPath); if (!modelHandle.IsValid()) { SetErrorMessage(outErrorMessage, Containers::String("Failed to load model asset: ") + modelPath); return false; } AssetRef modelAssetRef; if (!resourceManager.TryGetAssetRef(modelPath, ResourceType::Model, modelAssetRef)) { SetErrorMessage(outErrorMessage, Containers::String("Failed to resolve model asset ref: ") + modelPath); return false; } return InstantiateModelHierarchy(scene, *modelHandle, modelAssetRef, parent, outResult, outErrorMessage); } } // namespace XCEngine