229 lines
8.4 KiB
C++
229 lines
8.4 KiB
C++
|
|
#include <XCEngine/Scene/ModelSceneInstantiation.h>
|
||
|
|
|
||
|
|
#include <XCEngine/Components/GameObject.h>
|
||
|
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
||
|
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
||
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||
|
|
#include <XCEngine/Scene/Scene.h>
|
||
|
|
|
||
|
|
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<GameObject*>* outMeshObjects) {
|
||
|
|
if (!modelAssetRef.IsValid()) {
|
||
|
|
SetErrorMessage(outErrorMessage, "Model asset ref is required when instantiating mesh bindings.");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto* meshFilter = targetObject.GetComponent<MeshFilterComponent>();
|
||
|
|
if (meshFilter == nullptr) {
|
||
|
|
meshFilter = targetObject.AddComponent<MeshFilterComponent>();
|
||
|
|
}
|
||
|
|
|
||
|
|
auto* meshRenderer = targetObject.GetComponent<MeshRendererComponent>();
|
||
|
|
if (meshRenderer == nullptr) {
|
||
|
|
meshRenderer = targetObject.AddComponent<MeshRendererComponent>();
|
||
|
|
}
|
||
|
|
|
||
|
|
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<size_t>(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<Core::uint32>(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<Resources::Model> modelHandle = resourceManager.Load<Resources::Model>(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
|