Add Nahida model import and preview pipeline

This commit is contained in:
2026-04-11 20:16:49 +08:00
parent 8f71f99de4
commit 030230eb1f
87 changed files with 7245 additions and 117 deletions

View 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