Add Nahida model import and preview pipeline
This commit is contained in:
228
engine/src/Scene/ModelSceneInstantiation.cpp
Normal file
228
engine/src/Scene/ModelSceneInstantiation.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user