feat: fix scene loading that it loads from disk if not already loaded
This commit is contained in:
+242
-152
@@ -1,56 +1,45 @@
|
||||
#include <destrum/Components/Animator.h>
|
||||
#include <destrum/Assets/AssetManager.h>
|
||||
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <destrum/Util/DeltaTime.h>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
nlohmann::json Vec3Json(const glm::vec3& value) {
|
||||
return {value.x, value.y, value.z};
|
||||
}
|
||||
|
||||
glm::vec3 ReadVec3(const nlohmann::json& value) {
|
||||
return {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
||||
}
|
||||
|
||||
nlohmann::json QuatJson(const glm::quat& value) {
|
||||
return {value.x, value.y, value.z, value.w};
|
||||
}
|
||||
|
||||
glm::quat ReadQuat(const nlohmann::json& value) {
|
||||
return {
|
||||
value.at(3).get<float>(),
|
||||
value.at(0).get<float>(),
|
||||
value.at(1).get<float>(),
|
||||
value.at(2).get<float>()
|
||||
};
|
||||
}
|
||||
|
||||
nlohmann::json Mat4Json(const glm::mat4& value) {
|
||||
nlohmann::json result = nlohmann::json::array();
|
||||
for (std::size_t column = 0; column < 4; ++column) {
|
||||
for (std::size_t row = 0; row < 4; ++row) {
|
||||
result.push_back(value[static_cast<glm::mat4::length_type>(column)]
|
||||
[static_cast<glm::mat4::length_type>(row)]);
|
||||
}
|
||||
std::optional<std::size_t> ParseAnimationIndex(const AssetReference& asset)
|
||||
{
|
||||
constexpr std::string_view prefix = "animation:";
|
||||
if (asset.subresource.rfind(prefix, 0) != 0 ||
|
||||
asset.subresource.size() == prefix.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stoull(asset.subresource.substr(prefix.size()));
|
||||
} catch (const std::exception&) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::mat4 ReadMat4(const nlohmann::json& value) {
|
||||
glm::mat4 result{1.0f};
|
||||
for (std::size_t column = 0; column < 4; ++column) {
|
||||
for (std::size_t row = 0; row < 4; ++row) {
|
||||
result[static_cast<glm::mat4::length_type>(column)]
|
||||
[static_cast<glm::mat4::length_type>(row)] =
|
||||
value.at(column * 4 + row).get<float>();
|
||||
std::string FormatAvailableClips(
|
||||
const std::vector<SkeletalAnimation>& clips)
|
||||
{
|
||||
std::ostringstream result;
|
||||
result << "[";
|
||||
for (std::size_t index = 0; index < clips.size(); ++index) {
|
||||
if (index != 0) {
|
||||
result << ", ";
|
||||
}
|
||||
result << '\'' << clips[index].name << '\'';
|
||||
}
|
||||
return result;
|
||||
result << "]";
|
||||
return result.str();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,60 +47,40 @@ Animator::Animator(GameObject& parent)
|
||||
: Component(parent, "Animator") {}
|
||||
|
||||
nlohmann::json Animator::Serialize() const {
|
||||
nlohmann::json skeleton;
|
||||
skeleton["hierarchy"] = nlohmann::json::array();
|
||||
for (const auto& node : m_skeleton.hierarchy) {
|
||||
skeleton["hierarchy"].push_back({
|
||||
{"id", node.id},
|
||||
{"children", node.children}
|
||||
});
|
||||
if (!m_skeleton.joints.empty() && m_skeletonAsset.empty()) {
|
||||
throw std::runtime_error(
|
||||
"Animator cannot be saved without a skeleton asset reference");
|
||||
}
|
||||
skeleton["inverseBindMatrices"] = nlohmann::json::array();
|
||||
for (const auto& matrix : m_skeleton.inverseBindMatrices) {
|
||||
skeleton["inverseBindMatrices"].push_back(Mat4Json(matrix));
|
||||
}
|
||||
skeleton["joints"] = nlohmann::json::array();
|
||||
for (const auto& joint : m_skeleton.joints) {
|
||||
skeleton["joints"].push_back({
|
||||
{"id", joint.id},
|
||||
{"translation", Vec3Json(joint.localTranslation)},
|
||||
{"rotation", QuatJson(joint.localRotation)},
|
||||
{"scale", Vec3Json(joint.localScale)}
|
||||
});
|
||||
}
|
||||
skeleton["jointNames"] = m_skeleton.jointNames;
|
||||
skeleton["parentIndex"] = m_skeleton.parentIndex;
|
||||
skeleton["rootPreTransform"] = Mat4Json(m_skeleton.rootPreTransform);
|
||||
|
||||
nlohmann::json clips = nlohmann::json::array();
|
||||
nlohmann::json animations = nlohmann::json::array();
|
||||
const auto appendAnimation = [&animations, this](const std::string& name) {
|
||||
const auto clipIt = m_clips.find(name);
|
||||
if (clipIt == m_clips.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& clip = clipIt->second;
|
||||
if (clip->assetReference.empty()) {
|
||||
throw std::runtime_error(
|
||||
"Animator clip has no asset reference: " + name);
|
||||
}
|
||||
animations.push_back({
|
||||
{"clip", name},
|
||||
{"asset", {
|
||||
{"path", clip->assetReference.path},
|
||||
{"subresource", clip->assetReference.subresource}
|
||||
}}
|
||||
});
|
||||
};
|
||||
|
||||
for (const auto& name : m_clipOrder) {
|
||||
appendAnimation(name);
|
||||
}
|
||||
for (const auto& [name, clip] : m_clips) {
|
||||
nlohmann::json clipJson{
|
||||
{"name", name},
|
||||
{"duration", clip->duration},
|
||||
{"looped", clip->looped},
|
||||
{"startFrame", clip->startFrame},
|
||||
{"tracks", nlohmann::json::array()},
|
||||
{"events", nlohmann::json::object()}
|
||||
};
|
||||
for (const auto& track : clip->tracks) {
|
||||
nlohmann::json trackJson{
|
||||
{"jointIndex", track.jointIndex},
|
||||
{"keyframes", nlohmann::json::array()}
|
||||
};
|
||||
for (const auto& keyframe : track.keyframes) {
|
||||
trackJson["keyframes"].push_back({
|
||||
{"time", keyframe.time},
|
||||
{"translation", Vec3Json(keyframe.translation)},
|
||||
{"rotation", QuatJson(keyframe.rotation)},
|
||||
{"scale", Vec3Json(keyframe.scale)}
|
||||
});
|
||||
}
|
||||
clipJson["tracks"].push_back(std::move(trackJson));
|
||||
(void)clip;
|
||||
if (std::find(m_clipOrder.begin(), m_clipOrder.end(), name) == m_clipOrder.end()) {
|
||||
appendAnimation(name);
|
||||
}
|
||||
for (const auto& [frame, events] : clip->events) {
|
||||
clipJson["events"][std::to_string(frame)] = events;
|
||||
}
|
||||
clips.push_back(std::move(clipJson));
|
||||
}
|
||||
|
||||
const auto playback = [](const PlaybackState& state, const std::string& name) {
|
||||
@@ -123,10 +92,15 @@ nlohmann::json Animator::Serialize() const {
|
||||
};
|
||||
|
||||
return {
|
||||
{"skeleton", std::move(skeleton)},
|
||||
{"clips", std::move(clips)},
|
||||
{"assetReferences", {
|
||||
{"skeleton", {
|
||||
{"path", m_skeletonAsset.path},
|
||||
{"subresource", m_skeletonAsset.subresource}
|
||||
}},
|
||||
{"animations", std::move(animations)}
|
||||
}},
|
||||
{"current", playback(m_current, m_currentClipName)},
|
||||
{"previous", playback(m_previous, m_previous.clip ? m_previous.clip->name : std::string{})},
|
||||
{"previous", playback(m_previous, m_previousClipName)},
|
||||
{"blendT", m_blendT},
|
||||
{"blendDuration", m_blendDuration}
|
||||
};
|
||||
@@ -135,94 +109,196 @@ nlohmann::json Animator::Serialize() const {
|
||||
void Animator::Deserialize(const nlohmann::json& data) {
|
||||
m_skeleton = {};
|
||||
m_clips.clear();
|
||||
m_clipOrder.clear();
|
||||
m_clipAssets.clear();
|
||||
m_skeletonAsset = {};
|
||||
m_current = {};
|
||||
m_previous = {};
|
||||
m_currentClipName.clear();
|
||||
m_previousClipName.clear();
|
||||
m_referencesResolved = true;
|
||||
|
||||
if (data.contains("skeleton")) {
|
||||
const auto& skeleton = data.at("skeleton");
|
||||
for (const auto& node : skeleton.value("hierarchy", nlohmann::json::array())) {
|
||||
m_skeleton.hierarchy.push_back({
|
||||
node.at("id").get<JointId>(),
|
||||
node.value("children", std::vector<JointId>{})
|
||||
if (!data.contains("assetReferences") || !data.at("assetReferences").is_object()) {
|
||||
throw std::runtime_error("Animator data must contain assetReferences");
|
||||
}
|
||||
const auto& references = data.at("assetReferences");
|
||||
const auto& skeleton = references.at("skeleton");
|
||||
m_skeletonAsset.path = skeleton.value("path", std::string{});
|
||||
m_skeletonAsset.subresource = skeleton.value("subresource", std::string{});
|
||||
for (const auto& animation : references.value("animations", nlohmann::json::array())) {
|
||||
const std::string name = animation.at("clip").get<std::string>();
|
||||
const auto& asset = animation.at("asset");
|
||||
m_clipAssets.insert_or_assign(
|
||||
name,
|
||||
AssetReference{
|
||||
asset.at("path").get<std::string>(),
|
||||
asset.value("subresource", std::string{})
|
||||
});
|
||||
}
|
||||
for (const auto& matrix : skeleton.value("inverseBindMatrices", nlohmann::json::array())) {
|
||||
m_skeleton.inverseBindMatrices.push_back(ReadMat4(matrix));
|
||||
}
|
||||
for (const auto& joint : skeleton.value("joints", nlohmann::json::array())) {
|
||||
m_skeleton.joints.push_back({
|
||||
joint.at("id").get<JointId>(),
|
||||
ReadVec3(joint.at("translation")),
|
||||
ReadQuat(joint.at("rotation")),
|
||||
ReadVec3(joint.at("scale"))
|
||||
});
|
||||
}
|
||||
m_skeleton.jointNames = skeleton.value("jointNames", std::vector<std::string>{});
|
||||
m_skeleton.parentIndex = skeleton.value("parentIndex", std::vector<int>{});
|
||||
if (skeleton.contains("rootPreTransform")) {
|
||||
m_skeleton.rootPreTransform = ReadMat4(skeleton.at("rootPreTransform"));
|
||||
}
|
||||
if (m_skeleton.parentIndex.size() != m_skeleton.joints.size()) {
|
||||
buildParentIndex(m_skeleton);
|
||||
if (std::find(m_clipOrder.begin(), m_clipOrder.end(), name) == m_clipOrder.end()) {
|
||||
m_clipOrder.push_back(name);
|
||||
}
|
||||
}
|
||||
m_referencesResolved = m_skeletonAsset.empty() && m_clipAssets.empty();
|
||||
RestorePlaybackState(data);
|
||||
}
|
||||
|
||||
for (const auto& clipJson : data.value("clips", nlohmann::json::array())) {
|
||||
auto clip = std::make_shared<SkeletalAnimation>();
|
||||
clip->name = clipJson.at("name").get<std::string>();
|
||||
clip->duration = clipJson.value("duration", 0.0f);
|
||||
clip->looped = clipJson.value("looped", true);
|
||||
clip->startFrame = clipJson.value("startFrame", 0);
|
||||
for (const auto& trackJson : clipJson.value("tracks", nlohmann::json::array())) {
|
||||
SkeletalAnimation::Track track;
|
||||
track.jointIndex = trackJson.at("jointIndex").get<std::uint32_t>();
|
||||
for (const auto& keyframeJson : trackJson.value("keyframes", nlohmann::json::array())) {
|
||||
track.keyframes.push_back({
|
||||
keyframeJson.at("time").get<float>(),
|
||||
ReadVec3(keyframeJson.at("translation")),
|
||||
ReadQuat(keyframeJson.at("rotation")),
|
||||
ReadVec3(keyframeJson.at("scale"))
|
||||
});
|
||||
}
|
||||
clip->tracks.push_back(std::move(track));
|
||||
}
|
||||
if (clipJson.contains("events")) {
|
||||
for (auto it = clipJson.at("events").begin(); it != clipJson.at("events").end(); ++it) {
|
||||
clip->events[std::stoi(it.key())] = it.value().get<std::vector<std::string>>();
|
||||
}
|
||||
}
|
||||
m_clips[clip->name] = std::move(clip);
|
||||
}
|
||||
|
||||
void Animator::RestorePlaybackState(const nlohmann::json& data) {
|
||||
const auto restorePlayback = [this](const nlohmann::json& playback, PlaybackState& state,
|
||||
std::string* clipName) {
|
||||
std::string& clipName) {
|
||||
const std::string name = playback.value("clip", std::string{});
|
||||
state.time = playback.value("time", 0.0f);
|
||||
state.speed = playback.value("speed", 1.0f);
|
||||
state.clip = nullptr;
|
||||
if (!name.empty()) {
|
||||
if (!name.empty() && m_referencesResolved) {
|
||||
const auto it = m_clips.find(name);
|
||||
if (it == m_clips.end()) {
|
||||
throw std::runtime_error("Animator clip not found: " + name);
|
||||
}
|
||||
state.clip = it->second.get();
|
||||
}
|
||||
if (clipName != nullptr) {
|
||||
*clipName = name;
|
||||
}
|
||||
clipName = name;
|
||||
};
|
||||
|
||||
if (data.contains("current")) {
|
||||
restorePlayback(data.at("current"), m_current, &m_currentClipName);
|
||||
restorePlayback(data.at("current"), m_current, m_currentClipName);
|
||||
}
|
||||
if (data.contains("previous")) {
|
||||
restorePlayback(data.at("previous"), m_previous, nullptr);
|
||||
restorePlayback(data.at("previous"), m_previous, m_previousClipName);
|
||||
}
|
||||
m_blendT = data.value("blendT", 0.0f);
|
||||
m_blendDuration = data.value("blendDuration", 0.0f);
|
||||
}
|
||||
|
||||
void Animator::ResolveReferences(const ObjectMap&) {
|
||||
LoadAssetReferences();
|
||||
}
|
||||
|
||||
void Animator::LoadAssetReferences() {
|
||||
if (m_referencesResolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_skeletonAsset.empty()) {
|
||||
throw std::runtime_error("Animator asset references have no skeleton source");
|
||||
}
|
||||
|
||||
ModelDoc::LoadOptions options{};
|
||||
options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
||||
options.loadMaterials = false;
|
||||
options.loadSkeleton = true;
|
||||
options.loadAnimations = false;
|
||||
|
||||
auto skeletonAsset = AssetManager::GetInstance().LoadModel(m_skeletonAsset.path, options);
|
||||
if (skeletonAsset.skeleton.joints.empty()) {
|
||||
throw std::runtime_error("Animator skeleton source has no joints: " + m_skeletonAsset.path);
|
||||
}
|
||||
m_skeleton = std::move(skeletonAsset.skeleton);
|
||||
m_skeleton.assetReference = m_skeletonAsset;
|
||||
|
||||
std::unordered_map<std::string, std::vector<std::string>> requestedBySource;
|
||||
for (const auto& name : m_clipOrder) {
|
||||
const auto assetIt = m_clipAssets.find(name);
|
||||
if (assetIt != m_clipAssets.end()) {
|
||||
requestedBySource[assetIt->second.path].push_back(name);
|
||||
}
|
||||
}
|
||||
for (const auto& [name, asset] : m_clipAssets) {
|
||||
if (std::find(m_clipOrder.begin(), m_clipOrder.end(), name) == m_clipOrder.end()) {
|
||||
requestedBySource[asset.path].push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
m_clips.clear();
|
||||
for (const auto& [source, requestedNames] : requestedBySource) {
|
||||
auto clips = AssetManager::GetInstance().LoadAnimationClips(source, m_skeleton);
|
||||
std::unordered_map<std::string, std::size_t> clipIndicesByName;
|
||||
for (std::size_t index = 0; index < clips.size(); ++index) {
|
||||
clipIndicesByName.emplace(clips[index].name, index);
|
||||
}
|
||||
|
||||
for (const std::string& requestedName : requestedNames) {
|
||||
const auto sourceIt = m_clipAssets.find(requestedName);
|
||||
if (sourceIt == m_clipAssets.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> clipIndex =
|
||||
ParseAnimationIndex(sourceIt->second);
|
||||
|
||||
if (clipIndex && *clipIndex >= clips.size()) {
|
||||
clipIndex = std::nullopt;
|
||||
}
|
||||
|
||||
if (!clipIndex) {
|
||||
const auto nameIt = clipIndicesByName.find(requestedName);
|
||||
if (nameIt != clipIndicesByName.end()) {
|
||||
clipIndex = nameIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
// Older scene files did not store animation subresources. If the
|
||||
// source contains exactly one clip, it is unambiguous even when
|
||||
// the imported name changed between loader versions.
|
||||
if (!clipIndex && clips.size() == 1 && requestedNames.size() == 1) {
|
||||
clipIndex = 0;
|
||||
}
|
||||
|
||||
if (!clipIndex && clips.size() == requestedNames.size()) {
|
||||
const auto requestedIt = std::find(
|
||||
requestedNames.begin(),
|
||||
requestedNames.end(),
|
||||
requestedName);
|
||||
if (requestedIt != requestedNames.end()) {
|
||||
clipIndex = static_cast<std::size_t>(
|
||||
std::distance(requestedNames.begin(), requestedIt));
|
||||
}
|
||||
}
|
||||
|
||||
if (!clipIndex) {
|
||||
throw std::runtime_error(
|
||||
"Animator clip was not found in its source: " + requestedName +
|
||||
". Available clips: " + FormatAvailableClips(clips));
|
||||
}
|
||||
|
||||
auto clip = std::move(clips[*clipIndex]);
|
||||
AssetReference resolvedReference = sourceIt->second;
|
||||
if (resolvedReference.subresource.empty()) {
|
||||
resolvedReference.subresource = clip.assetReference.subresource;
|
||||
}
|
||||
|
||||
// The serialized name is the stable scene-facing name. Keep it
|
||||
// even when the importer supplied a different display name.
|
||||
clip.name = requestedName;
|
||||
clip.assetReference = std::move(resolvedReference);
|
||||
m_clips[requestedName] =
|
||||
std::make_shared<SkeletalAnimation>(std::move(clip));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [name, asset] : m_clipAssets) {
|
||||
(void)asset;
|
||||
if (!m_clips.contains(name)) {
|
||||
throw std::runtime_error("Animator clip was not found in its source: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
m_referencesResolved = true;
|
||||
RestorePlaybackState({
|
||||
{"current", {
|
||||
{"clip", m_currentClipName},
|
||||
{"time", m_current.time},
|
||||
{"speed", m_current.speed}
|
||||
}},
|
||||
{"previous", {
|
||||
{"clip", m_previousClipName},
|
||||
{"time", m_previous.time},
|
||||
{"speed", m_previous.speed}
|
||||
}},
|
||||
{"blendT", m_blendT},
|
||||
{"blendDuration", m_blendDuration}
|
||||
});
|
||||
}
|
||||
|
||||
void Animator::Update(float dt) {
|
||||
if (!m_current.clip) return;
|
||||
|
||||
@@ -245,6 +321,7 @@ void Animator::Update(float dt) {
|
||||
if (m_blendT >= 1.f) {
|
||||
m_blendT = 1.f;
|
||||
m_previous = {};
|
||||
m_previousClipName.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,7 +348,17 @@ void Animator::ImGuiInspector() {
|
||||
}
|
||||
|
||||
void Animator::addClip(std::shared_ptr<SkeletalAnimation> clip) {
|
||||
m_clips[clip->name] = std::move(clip);
|
||||
if (!clip) {
|
||||
return;
|
||||
}
|
||||
const std::string name = clip->name;
|
||||
if (!m_clips.contains(name)) {
|
||||
m_clipOrder.push_back(name);
|
||||
}
|
||||
if (!clip->assetReference.empty()) {
|
||||
m_clipAssets[name] = clip->assetReference;
|
||||
}
|
||||
m_clips[name] = std::move(clip);
|
||||
}
|
||||
|
||||
void Animator::play(const std::string& name, float blendTime) {
|
||||
@@ -290,10 +377,12 @@ void Animator::play(const std::string& name, float blendTime) {
|
||||
|
||||
if (m_current.clip && blendTime > 0.f) {
|
||||
m_previous = m_current;
|
||||
m_previousClipName = m_currentClipName;
|
||||
m_blendT = 0.f;
|
||||
m_blendDuration = blendTime;
|
||||
} else {
|
||||
m_previous = {};
|
||||
m_previousClipName.clear();
|
||||
m_blendT = 1.f;
|
||||
}
|
||||
|
||||
@@ -305,6 +394,7 @@ void Animator::stop() {
|
||||
m_current = {};
|
||||
m_previous = {};
|
||||
m_currentClipName = {};
|
||||
m_previousClipName = {};
|
||||
}
|
||||
|
||||
std::size_t Animator::uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <destrum/Components/MeshRendererComponent.h>
|
||||
#include <destrum/Assets/AssetReference.h>
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Renderer.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
|
||||
#include "destrum/Components/Animator.h"
|
||||
@@ -79,19 +82,53 @@ void MeshRendererComponent::ResolveReferences(const ObjectMap&) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto loadAssetForKey = [&](const std::string& key) {
|
||||
const auto asset = AssetReference::fromCacheKey(key);
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
if (!GameState::GetInstance().HasGfxDevice()) {
|
||||
throw std::runtime_error(
|
||||
"Cannot load mesh asset without a graphics device: " + asset->path);
|
||||
}
|
||||
|
||||
resources->loadModelAsset(GameState::GetInstance().Gfx(), asset->path);
|
||||
};
|
||||
|
||||
if (!meshKey.empty()) {
|
||||
const auto resolved = resources->meshes().findMeshByKey(meshKey);
|
||||
auto resolved = resources->meshes().findMeshByKey(meshKey);
|
||||
if (!resolved) {
|
||||
loadAssetForKey(meshKey);
|
||||
resolved = resources->meshes().findMeshByKey(meshKey);
|
||||
}
|
||||
if (!resolved) {
|
||||
throw std::runtime_error("Mesh resource not found: " + meshKey);
|
||||
}
|
||||
meshID = *resolved;
|
||||
}
|
||||
if (!materialKey.empty()) {
|
||||
const auto resolved = resources->materials().findMaterialByKey(materialKey);
|
||||
auto resolved = resources->materials().findMaterialByKey(materialKey);
|
||||
if (!resolved) {
|
||||
loadAssetForKey(materialKey);
|
||||
resolved = resources->materials().findMaterialByKey(materialKey);
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
const auto meshAsset = AssetReference::fromCacheKey(meshKey);
|
||||
resolved = resources->materials().findMaterialByName(
|
||||
materialKey,
|
||||
meshAsset ? meshAsset->path : std::string_view{});
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
resolved = resources->materials().findMaterialByName(materialKey);
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
throw std::runtime_error("Material resource not found: " + materialKey);
|
||||
}
|
||||
materialID = *resolved;
|
||||
materialKey = resources->materials().getMaterialKey(*resolved);
|
||||
}
|
||||
|
||||
if (meshID != NULL_MESH_ID && meshKey.empty()) {
|
||||
|
||||
Reference in New Issue
Block a user