1892 lines
77 KiB
C++
1892 lines
77 KiB
C++
#include "MetaCoreRender/MetaCorePandaSceneBridge.h"
|
||
|
||
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||
#include "MetaCoreScene/MetaCoreComponents.h"
|
||
#include "MetaCoreScene/MetaCoreScene.h"
|
||
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
|
||
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||
|
||
#include "ambientLight.h"
|
||
#include "camera.h"
|
||
#include "directionalLight.h"
|
||
#include "geom.h"
|
||
#include "geomNode.h"
|
||
#include "geomLines.h"
|
||
#include "geomTriangles.h"
|
||
#include "geomVertexData.h"
|
||
#include "geomVertexFormat.h"
|
||
#include "geomVertexWriter.h"
|
||
#include "internalName.h"
|
||
#include "lineSegs.h"
|
||
#include "loader.h"
|
||
#include "nodePath.h"
|
||
#include "perspectiveLens.h"
|
||
#include "pandaNode.h"
|
||
#include "shader.h"
|
||
#include "texture.h"
|
||
#include "texturePool.h"
|
||
#include "transparencyAttrib.h"
|
||
#include "windowFramework.h"
|
||
#include "pointerTo.h"
|
||
#include "dcast.h"
|
||
|
||
#include <algorithm>
|
||
#include <chrono>
|
||
#include <filesystem>
|
||
#include <mutex>
|
||
#include <optional>
|
||
#include <string>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#include <vector>
|
||
#include <span>
|
||
#include <cstddef>
|
||
#include <fstream>
|
||
|
||
#define GLM_ENABLE_EXPERIMENTAL
|
||
#include <glm/glm.hpp>
|
||
#include <glm/ext/matrix_transform.hpp>
|
||
#include <glm/gtx/euler_angles.hpp>
|
||
#include <glm/gtx/matrix_decompose.hpp>
|
||
#include <glm/mat4x4.hpp>
|
||
#include <cstdlib>
|
||
#include <cstdio>
|
||
#include <ctime>
|
||
#include <memory>
|
||
|
||
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
|
||
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||
#include "MetaCoreFoundation/MetaCoreArchive.h"
|
||
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
||
|
||
namespace MetaCore {
|
||
|
||
namespace {
|
||
|
||
void MetaCoreTrace(const char* message);
|
||
[[nodiscard]] NodePath MetaCoreBuildMeshFromBinary(
|
||
const std::string& name,
|
||
std::span<const std::byte> payload,
|
||
std::span<const MetaCoreMeshSubMeshDocument> subMeshes
|
||
);
|
||
[[nodiscard]] PT(Texture) MetaCoreBuildTextureFromBinary(const std::string& name, const MetaCoreTextureAssetDocument& doc, std::span<const std::byte> payload);
|
||
[[nodiscard]] PT(Texture) MetaCoreResolveTextureWithFallback(const std::filesystem::path& projectRoot, const std::string& relativeTexturePath, const PT(Texture)& fallbackTexture);
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultBaseColorTexture();
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultMetalRoughnessTexture();
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultNormalTexture();
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultEmissionTexture();
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultAoTexture();
|
||
[[nodiscard]] PT(Shader) MetaCoreLoadSimplePbrShader();
|
||
void MetaCoreApplySimplePbrViewInputs(NodePath& meshNode, const NodePath& sceneRootNode, const NodePath& cameraNode);
|
||
|
||
void MetaCoreTrace(const char* message) {
|
||
if (message == nullptr) {
|
||
return;
|
||
}
|
||
|
||
const auto now = std::chrono::system_clock::now();
|
||
const auto time = std::chrono::system_clock::to_time_t(now);
|
||
struct tm timeInfo {};
|
||
localtime_s(&timeInfo, &time);
|
||
|
||
char timeBuffer[32]{};
|
||
std::strftime(timeBuffer, sizeof(timeBuffer), "[%H:%M:%S] ", &timeInfo);
|
||
|
||
FILE* file = nullptr;
|
||
if (fopen_s(&file, "editor.crash.log", "a") == 0 && file != nullptr) {
|
||
std::fputs(timeBuffer, file);
|
||
std::fputs(message, file);
|
||
std::fputs("\n", file);
|
||
std::fflush(file);
|
||
std::fclose(file);
|
||
}
|
||
|
||
std::printf("%s%s\n", timeBuffer, message);
|
||
}
|
||
|
||
glm::mat4 MetaCoreBuildBasisSwapMatrix() {
|
||
glm::mat4 basis(1.0F);
|
||
basis[0] = glm::vec4(1.0F, 0.0F, 0.0F, 0.0F);
|
||
basis[1] = glm::vec4(0.0F, 0.0F, 1.0F, 0.0F);
|
||
basis[2] = glm::vec4(0.0F, 1.0F, 0.0F, 0.0F);
|
||
basis[3] = glm::vec4(0.0F, 0.0F, 0.0F, 1.0F);
|
||
return basis;
|
||
}
|
||
|
||
LMatrix4f MetaCoreConvertTransformToPanda(const MetaCoreTransformComponent& transform) {
|
||
const glm::mat4 translation = glm::translate(glm::mat4(1.0F), transform.Position);
|
||
const glm::mat4 rotation = glm::yawPitchRoll(
|
||
glm::radians(transform.RotationEulerDegrees.y),
|
||
glm::radians(transform.RotationEulerDegrees.x),
|
||
glm::radians(transform.RotationEulerDegrees.z)
|
||
);
|
||
const glm::mat4 scale = glm::scale(glm::mat4(1.0F), transform.Scale);
|
||
|
||
const glm::mat4 metaCoreMatrix = translation * rotation * scale;
|
||
const glm::mat4 basis = MetaCoreBuildBasisSwapMatrix();
|
||
const glm::mat4 pandaMatrix = basis * metaCoreMatrix * basis;
|
||
|
||
LMatrix4f result;
|
||
for (int row = 0; row < 4; ++row) {
|
||
for (int column = 0; column < 4; ++column) {
|
||
result.set_cell(row, column, pandaMatrix[column][row]);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
LPoint3f MetaCoreToPandaPoint(const glm::vec3& value) {
|
||
return LPoint3f(value.x, -value.z, value.y);
|
||
}
|
||
|
||
LVector3f MetaCoreToPandaVector(const glm::vec3& value) {
|
||
return LVector3f(value.x, -value.z, value.y);
|
||
}
|
||
|
||
LVecBase3f MetaCoreToPandaScale(const glm::vec3& value) {
|
||
return LVecBase3f(value.x, value.z, value.y);
|
||
}
|
||
|
||
glm::mat4 MetaCoreBuildLocalTransformMatrix(const MetaCoreTransformComponent& transform) {
|
||
const glm::mat4 translation = glm::translate(glm::mat4(1.0F), transform.Position);
|
||
const glm::mat4 rotation = glm::yawPitchRoll(
|
||
glm::radians(transform.RotationEulerDegrees.y),
|
||
glm::radians(transform.RotationEulerDegrees.x),
|
||
glm::radians(transform.RotationEulerDegrees.z)
|
||
);
|
||
const glm::mat4 scale = glm::scale(glm::mat4(1.0F), transform.Scale);
|
||
return translation * rotation * scale;
|
||
}
|
||
|
||
MetaCoreTransformComponent MetaCoreBuildWorldTransformComponent(const MetaCoreScene& scene, const MetaCoreGameObject& gameObject) {
|
||
glm::mat4 worldMatrix = MetaCoreBuildLocalTransformMatrix(gameObject.GetComponent<MetaCoreTransformComponent>());
|
||
|
||
MetaCoreGameObject current = gameObject;
|
||
std::size_t guard = 0;
|
||
while (current && current.GetParentId() != 0 && guard++ < scene.GetGameObjects().size()) {
|
||
current = scene.FindGameObject(current.GetParentId());
|
||
if (current) {
|
||
worldMatrix = MetaCoreBuildLocalTransformMatrix(current.GetComponent<MetaCoreTransformComponent>()) * worldMatrix;
|
||
}
|
||
}
|
||
|
||
MetaCoreTransformComponent worldTransform{};
|
||
glm::vec3 skew{};
|
||
glm::vec4 perspective{};
|
||
glm::quat rotation{};
|
||
glm::decompose(worldMatrix, worldTransform.Scale, rotation, worldTransform.Position, skew, perspective);
|
||
worldTransform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation));
|
||
return worldTransform;
|
||
}
|
||
|
||
void MetaCoreApplyTransformToPandaNode(const MetaCoreTransformComponent& transform, NodePath& nodePath) {
|
||
nodePath.set_pos(MetaCoreToPandaPoint(transform.Position));
|
||
nodePath.set_hpr(
|
||
transform.RotationEulerDegrees.y,
|
||
transform.RotationEulerDegrees.x,
|
||
transform.RotationEulerDegrees.z
|
||
);
|
||
nodePath.set_scale(MetaCoreToPandaScale(transform.Scale));
|
||
}
|
||
|
||
NodePath MetaCoreCreateGridNode(const NodePath& parentNode) {
|
||
(void)parentNode;
|
||
MetaCoreTrace("metacore.render: debug scene grid paused due to backend runtime instability");
|
||
return NodePath();
|
||
}
|
||
|
||
[[nodiscard]] std::filesystem::path MetaCoreEnsureBuiltinCubeEggPath() {
|
||
const std::filesystem::path cubePath = std::filesystem::current_path() / "metacore_builtin_cube.egg";
|
||
if (std::filesystem::exists(cubePath)) {
|
||
return cubePath;
|
||
}
|
||
|
||
std::ofstream cubeFile(cubePath, std::ios::trunc);
|
||
if (!cubeFile.is_open()) {
|
||
return {};
|
||
}
|
||
|
||
cubeFile
|
||
<< "<CoordinateSystem> { Y-Up }\n"
|
||
<< "<Group> MetaCoreUnitCube {\n"
|
||
<< " <VertexPool> cube.verts {\n"
|
||
<< " <Vertex> 1 { -0.5 -0.5 -0.5 <Normal> { 0 0 -1 } <UV> { 0 0 } }\n"
|
||
<< " <Vertex> 2 { 0.5 -0.5 -0.5 <Normal> { 0 0 -1 } <UV> { 1 0 } }\n"
|
||
<< " <Vertex> 3 { 0.5 0.5 -0.5 <Normal> { 0 0 -1 } <UV> { 1 1 } }\n"
|
||
<< " <Vertex> 4 { -0.5 0.5 -0.5 <Normal> { 0 0 -1 } <UV> { 0 1 } }\n"
|
||
<< " <Vertex> 5 { -0.5 -0.5 0.5 <Normal> { 0 0 1 } <UV> { 0 0 } }\n"
|
||
<< " <Vertex> 6 { 0.5 -0.5 0.5 <Normal> { 0 0 1 } <UV> { 1 0 } }\n"
|
||
<< " <Vertex> 7 { 0.5 0.5 0.5 <Normal> { 0 0 1 } <UV> { 1 1 } }\n"
|
||
<< " <Vertex> 8 { -0.5 0.5 0.5 <Normal> { 0 0 1 } <UV> { 0 1 } }\n"
|
||
<< " }\n"
|
||
<< " <Polygon> { <RGBA> { 0.75 0.78 0.84 1 } <VertexRef> { 1 2 3 4 <Ref> { cube.verts } } }\n"
|
||
<< " <Polygon> { <RGBA> { 0.75 0.78 0.84 1 } <VertexRef> { 5 8 7 6 <Ref> { cube.verts } } }\n"
|
||
<< " <Polygon> { <RGBA> { 0.75 0.78 0.84 1 } <VertexRef> { 1 5 6 2 <Ref> { cube.verts } } }\n"
|
||
<< " <Polygon> { <RGBA> { 0.75 0.78 0.84 1 } <VertexRef> { 2 6 7 3 <Ref> { cube.verts } } }\n"
|
||
<< " <Polygon> { <RGBA> { 0.75 0.78 0.84 1 } <VertexRef> { 3 7 8 4 <Ref> { cube.verts } } }\n"
|
||
<< " <Polygon> { <RGBA> { 0.75 0.78 0.84 1 } <VertexRef> { 4 8 5 1 <Ref> { cube.verts } } }\n"
|
||
<< "}\n";
|
||
return cubePath;
|
||
}
|
||
|
||
[[nodiscard]] std::filesystem::path MetaCoreEnsureBuiltinCubeModelPath() {
|
||
const std::filesystem::path bamPath = std::filesystem::current_path() / "metacore_builtin_cube.bam";
|
||
if (std::filesystem::exists(bamPath)) {
|
||
return bamPath;
|
||
}
|
||
|
||
const std::filesystem::path eggPath = MetaCoreEnsureBuiltinCubeEggPath();
|
||
if (eggPath.empty()) {
|
||
return {};
|
||
}
|
||
|
||
const std::filesystem::path converterPath =
|
||
std::filesystem::current_path() / ".." / ".." / ".." / ".metacore" / "deps" / "panda3d" / "1.10.16-x64" / "bin" / "egg2bam.exe";
|
||
if (std::filesystem::exists(converterPath)) {
|
||
const std::string command =
|
||
"\"" + converterPath.lexically_normal().string() + "\" \"" + eggPath.string() + "\" \"" + bamPath.string() + "\"";
|
||
const int result = std::system(command.c_str());
|
||
if (result == 0 && std::filesystem::exists(bamPath)) {
|
||
return bamPath;
|
||
}
|
||
}
|
||
|
||
return eggPath;
|
||
}
|
||
|
||
|
||
NodePath MetaCoreCreateUnitCubeNode(WindowFramework* windowFrameworkHandle, const NodePath& parentNode) {
|
||
const std::filesystem::path cubePath = MetaCoreEnsureBuiltinCubeModelPath();
|
||
if (windowFrameworkHandle != nullptr && !cubePath.empty()) {
|
||
NodePath loadedNode = windowFrameworkHandle->load_model(parentNode, Filename::from_os_specific(cubePath.string()));
|
||
if (!loadedNode.is_empty()) {
|
||
return loadedNode;
|
||
}
|
||
MetaCoreTrace("metacore.render.sync: builtin cube asset load failed");
|
||
}
|
||
|
||
#if defined(_DEBUG)
|
||
MetaCoreTrace("metacore.render.sync: debug fallback skipped for manual GeomVertexData cube");
|
||
return NodePath();
|
||
#else
|
||
MetaCoreTrace("metacore.render.sync: create cube vertex data begin");
|
||
PT(GeomVertexData) vertexData = new GeomVertexData(
|
||
"MetaCoreUnitCube",
|
||
GeomVertexFormat::get_v3n3(),
|
||
Geom::UH_static
|
||
);
|
||
|
||
MetaCoreTrace("metacore.render.sync: create cube vertex writers begin");
|
||
GeomVertexWriter vertexWriter(vertexData, "vertex");
|
||
GeomVertexWriter normalWriter(vertexData, "normal");
|
||
|
||
const auto addFace = [&](const glm::vec3& normal, const glm::vec3& a, const glm::vec3& b, const glm::vec3& c, const glm::vec3& d) {
|
||
const glm::vec3 corners[4] = {a, b, c, d};
|
||
for (const glm::vec3& corner : corners) {
|
||
const LPoint3f pandaPoint = MetaCoreToPandaPoint(corner);
|
||
const LVector3f pandaNormal = MetaCoreToPandaVector(normal);
|
||
vertexWriter.add_data3(pandaPoint);
|
||
normalWriter.add_data3(pandaNormal);
|
||
}
|
||
};
|
||
|
||
MetaCoreTrace("metacore.render.sync: write cube vertices begin");
|
||
addFace(glm::vec3(0.0F, 0.0F, 1.0F),
|
||
glm::vec3(-0.5F, -0.5F, 0.5F),
|
||
glm::vec3(0.5F, -0.5F, 0.5F),
|
||
glm::vec3(0.5F, 0.5F, 0.5F),
|
||
glm::vec3(-0.5F, 0.5F, 0.5F));
|
||
addFace(glm::vec3(0.0F, 0.0F, -1.0F),
|
||
glm::vec3(0.5F, -0.5F, -0.5F),
|
||
glm::vec3(-0.5F, -0.5F, -0.5F),
|
||
glm::vec3(-0.5F, 0.5F, -0.5F),
|
||
glm::vec3(0.5F, 0.5F, -0.5F));
|
||
addFace(glm::vec3(-1.0F, 0.0F, 0.0F),
|
||
glm::vec3(-0.5F, -0.5F, -0.5F),
|
||
glm::vec3(-0.5F, -0.5F, 0.5F),
|
||
glm::vec3(-0.5F, 0.5F, 0.5F),
|
||
glm::vec3(-0.5F, 0.5F, -0.5F));
|
||
addFace(glm::vec3(1.0F, 0.0F, 0.0F),
|
||
glm::vec3(0.5F, -0.5F, 0.5F),
|
||
glm::vec3(0.5F, -0.5F, -0.5F),
|
||
glm::vec3(0.5F, 0.5F, -0.5F),
|
||
glm::vec3(0.5F, 0.5F, 0.5F));
|
||
addFace(glm::vec3(0.0F, 1.0F, 0.0F),
|
||
glm::vec3(-0.5F, 0.5F, 0.5F),
|
||
glm::vec3(0.5F, 0.5F, 0.5F),
|
||
glm::vec3(0.5F, 0.5F, -0.5F),
|
||
glm::vec3(-0.5F, 0.5F, -0.5F));
|
||
addFace(glm::vec3(0.0F, -1.0F, 0.0F),
|
||
glm::vec3(-0.5F, -0.5F, -0.5F),
|
||
glm::vec3(0.5F, -0.5F, -0.5F),
|
||
glm::vec3(0.5F, -0.5F, 0.5F),
|
||
glm::vec3(-0.5F, -0.5F, 0.5F));
|
||
|
||
MetaCoreTrace("metacore.render.sync: create cube triangles begin");
|
||
PT(GeomTriangles) triangles = new GeomTriangles(Geom::UH_static);
|
||
for (int faceIndex = 0; faceIndex < 6; ++faceIndex) {
|
||
const int baseVertex = faceIndex * 4;
|
||
triangles->add_vertices(baseVertex + 0, baseVertex + 1, baseVertex + 2);
|
||
triangles->add_vertices(baseVertex + 0, baseVertex + 2, baseVertex + 3);
|
||
}
|
||
|
||
MetaCoreTrace("metacore.render.sync: create cube geom begin");
|
||
PT(Geom) geom = new Geom(vertexData);
|
||
geom->add_primitive(triangles);
|
||
|
||
MetaCoreTrace("metacore.render.sync: create cube geom node begin");
|
||
PT(GeomNode) geomNode = new GeomNode("MetaCoreUnitCubeNode");
|
||
geomNode->add_geom(geom);
|
||
MetaCoreTrace("metacore.render.sync: attach cube geom node begin");
|
||
return parentNode.attach_new_node(geomNode);
|
||
#endif
|
||
}
|
||
|
||
[[nodiscard]] std::filesystem::path MetaCoreResolveProjectRootFromEnvironment() {
|
||
char* rawProjectPath = nullptr;
|
||
std::size_t valueLength = 0;
|
||
if (_dupenv_s(&rawProjectPath, &valueLength, "METACORE_PROJECT_PATH") != 0 || rawProjectPath == nullptr || valueLength == 0) {
|
||
if (rawProjectPath != nullptr) {
|
||
std::free(rawProjectPath);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
const std::filesystem::path projectRoot(rawProjectPath);
|
||
std::free(rawProjectPath);
|
||
return projectRoot;
|
||
}
|
||
|
||
[[nodiscard]] std::string MetaCoreBuildModelCacheKey(
|
||
const MetaCoreAssetGuid& sourceModelAssetGuid,
|
||
const std::string& relativeSourcePath
|
||
) {
|
||
if (sourceModelAssetGuid.IsValid()) {
|
||
return "model:" + sourceModelAssetGuid.ToString();
|
||
}
|
||
return "path:" + relativeSourcePath;
|
||
}
|
||
|
||
[[nodiscard]] bool MetaCoreShouldCompareSourceModelPath(
|
||
const MetaCoreAssetGuid& previousSourceModelGuid,
|
||
const MetaCoreAssetGuid& nextSourceModelGuid
|
||
) {
|
||
return !previousSourceModelGuid.IsValid() && !nextSourceModelGuid.IsValid();
|
||
}
|
||
|
||
struct MetaCoreRuntimeModelLoadRequest {
|
||
MetaCoreAssetGuid SourceModelAssetGuid{};
|
||
std::filesystem::path ProjectRoot{};
|
||
std::string RelativeSourcePath{};
|
||
bool PreferSourceLoader = false;
|
||
};
|
||
|
||
[[nodiscard]] std::filesystem::path MetaCoreResolveModelRuntimePackagePath(
|
||
const MetaCoreRuntimeModelLoadRequest& request,
|
||
const std::filesystem::path& absoluteSourcePath
|
||
) {
|
||
if (request.SourceModelAssetGuid.IsValid() && !request.ProjectRoot.empty()) {
|
||
const std::filesystem::path libraryPackagePath =
|
||
request.ProjectRoot / "Library" / "RuntimeAssets" / "Models" /
|
||
(request.SourceModelAssetGuid.ToString() + ".mcasset");
|
||
if (std::filesystem::exists(libraryPackagePath)) {
|
||
return libraryPackagePath;
|
||
}
|
||
}
|
||
|
||
if (absoluteSourcePath.empty()) {
|
||
return {};
|
||
}
|
||
|
||
const std::filesystem::path sourceAdjacentPackagePath =
|
||
std::filesystem::path(absoluteSourcePath.string() + ".mcasset");
|
||
if (std::filesystem::exists(sourceAdjacentPackagePath)) {
|
||
return sourceAdjacentPackagePath;
|
||
}
|
||
|
||
return sourceAdjacentPackagePath;
|
||
}
|
||
|
||
[[nodiscard]] std::filesystem::path MetaCoreResolveAbsoluteModelSourcePath(
|
||
const std::filesystem::path& projectRoot,
|
||
const std::string& relativeSourcePath
|
||
) {
|
||
return relativeSourcePath.empty()
|
||
? std::filesystem::path{}
|
||
: (projectRoot / std::filesystem::path(relativeSourcePath)).lexically_normal();
|
||
}
|
||
|
||
[[nodiscard]] std::optional<MetaCoreModelAssetDocument> MetaCoreReadRuntimeModelDocument(
|
||
const MetaCoreRuntimeModelLoadRequest& request
|
||
) {
|
||
const std::filesystem::path absoluteSourcePath =
|
||
MetaCoreResolveAbsoluteModelSourcePath(request.ProjectRoot, request.RelativeSourcePath);
|
||
const std::filesystem::path binaryPackagePath =
|
||
MetaCoreResolveModelRuntimePackagePath(request, absoluteSourcePath);
|
||
if (binaryPackagePath.empty() || !std::filesystem::exists(binaryPackagePath)) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
MetaCoreTypeRegistry registry;
|
||
MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||
|
||
auto mappedPackage = MetaCoreMappedPackage::Open(binaryPackagePath, registry);
|
||
if (!mappedPackage.has_value() || mappedPackage->GetPayloadSectionCount() == 0) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
MetaCoreModelAssetDocument modelDoc;
|
||
if (!MetaCoreDeserializeFromBytes(mappedPackage->GetPayloadSection(0), modelDoc, registry)) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
return modelDoc;
|
||
}
|
||
|
||
[[nodiscard]] NodePath MetaCoreTryLoadBinaryModelPackage(const std::filesystem::path& binaryPackagePath) {
|
||
MetaCoreTrace(("metacore.render: MetaCoreTryLoadBinaryModelPackage path=" + binaryPackagePath.string()).c_str());
|
||
if (binaryPackagePath.empty() || !std::filesystem::exists(binaryPackagePath)) {
|
||
MetaCoreTrace("metacore.render: binary package path empty or does not exist");
|
||
return NodePath();
|
||
}
|
||
|
||
MetaCoreTypeRegistry registry;
|
||
MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||
|
||
auto mappedPackage = MetaCoreMappedPackage::Open(binaryPackagePath, registry);
|
||
if (!mappedPackage.has_value() || mappedPackage->GetPayloadSectionCount() == 0) {
|
||
MetaCoreTrace(("metacore.render: model binary package missing payload: " + binaryPackagePath.string()).c_str());
|
||
return NodePath();
|
||
}
|
||
|
||
MetaCoreTrace(("metacore.render: package has " + std::to_string(mappedPackage->GetPayloadSectionCount()) + " payload sections").c_str());
|
||
|
||
MetaCoreModelAssetDocument modelDoc;
|
||
if (!MetaCoreDeserializeFromBytes(mappedPackage->GetPayloadSection(0), modelDoc, registry)) {
|
||
MetaCoreTrace(("metacore.render: model binary metadata failed: " + binaryPackagePath.string()).c_str());
|
||
return NodePath();
|
||
}
|
||
|
||
MetaCoreTrace(("metacore.render: modelDoc.Nodes.size()=" + std::to_string(modelDoc.Nodes.size()) +
|
||
" Meshes.size()=" + std::to_string(modelDoc.Meshes.size()) +
|
||
" GeneratedMeshAssets.size()=" + std::to_string(modelDoc.GeneratedMeshAssets.size())).c_str());
|
||
|
||
NodePath rootNode("ModelRoot");
|
||
std::vector<NodePath> pandaNodes;
|
||
pandaNodes.resize(modelDoc.Nodes.size());
|
||
|
||
for (std::size_t i = 0; i < modelDoc.Nodes.size(); ++i) {
|
||
const auto& nodeDoc = modelDoc.Nodes[i];
|
||
pandaNodes[i] = NodePath(new PandaNode(nodeDoc.Name));
|
||
pandaNodes[i].set_tag("MetaCoreNodeIndex", std::to_string(i));
|
||
|
||
const std::size_t meshPayloadIndex =
|
||
nodeDoc.MeshIndex >= 0 ? static_cast<std::size_t>(nodeDoc.MeshIndex) + 1U : 0U;
|
||
|
||
MetaCoreTrace(("metacore.render: Node[" + std::to_string(i) + "] name=" + nodeDoc.Name +
|
||
" MeshIndex=" + std::to_string(nodeDoc.MeshIndex) +
|
||
" meshPayloadIndex=" + std::to_string(meshPayloadIndex) +
|
||
" payloadCount=" + std::to_string(mappedPackage->GetPayloadSectionCount())).c_str());
|
||
|
||
if (nodeDoc.MeshIndex >= 0 && meshPayloadIndex < mappedPackage->GetPayloadSectionCount()) {
|
||
std::span<const MetaCoreMeshSubMeshDocument> subMeshes;
|
||
if (static_cast<std::size_t>(nodeDoc.MeshIndex) < modelDoc.GeneratedMeshAssets.size()) {
|
||
subMeshes = modelDoc.GeneratedMeshAssets[nodeDoc.MeshIndex].SubMeshes;
|
||
MetaCoreTrace(("metacore.render: subMeshes.size()=" + std::to_string(subMeshes.size())).c_str());
|
||
}
|
||
|
||
const auto payloadSpan = mappedPackage->GetPayloadSection(meshPayloadIndex);
|
||
MetaCoreTrace(("metacore.render: payload size=" + std::to_string(payloadSpan.size()) + " bytes").c_str());
|
||
|
||
NodePath meshGeom = MetaCoreBuildMeshFromBinary(
|
||
nodeDoc.Name,
|
||
payloadSpan,
|
||
subMeshes
|
||
);
|
||
if (!meshGeom.is_empty()) {
|
||
meshGeom.reparent_to(pandaNodes[i]);
|
||
MetaCoreTrace(("metacore.render: mesh geom attached OK for node " + std::to_string(i)).c_str());
|
||
} else {
|
||
MetaCoreTrace(("metacore.render: mesh geom EMPTY for node " + std::to_string(i)).c_str());
|
||
}
|
||
} else if (nodeDoc.MeshIndex >= 0) {
|
||
MetaCoreTrace(("metacore.render: SKIP node " + std::to_string(i) + " meshPayloadIndex=" + std::to_string(meshPayloadIndex) + " >= payloadCount=" + std::to_string(mappedPackage->GetPayloadSectionCount())).c_str());
|
||
}
|
||
}
|
||
|
||
for (std::size_t i = 0; i < modelDoc.Nodes.size(); ++i) {
|
||
const auto& nodeDoc = modelDoc.Nodes[i];
|
||
if (nodeDoc.ParentIndex >= 0 && static_cast<std::size_t>(nodeDoc.ParentIndex) < pandaNodes.size()) {
|
||
pandaNodes[i].reparent_to(pandaNodes[static_cast<std::size_t>(nodeDoc.ParentIndex)]);
|
||
} else {
|
||
pandaNodes[i].reparent_to(rootNode);
|
||
}
|
||
}
|
||
|
||
MetaCoreTrace(("metacore.render: model binary load success: " + binaryPackagePath.string()).c_str());
|
||
return rootNode;
|
||
}
|
||
|
||
[[nodiscard]] NodePath MetaCoreTryLoadSourceModelNode(const std::filesystem::path& absoluteSourcePath) {
|
||
if (!std::filesystem::exists(absoluteSourcePath)) {
|
||
MetaCoreTrace(("metacore.render: model source file missing: " + absoluteSourcePath.string()).c_str());
|
||
return NodePath();
|
||
}
|
||
|
||
const Filename pandaPath = Filename::from_os_specific(absoluteSourcePath.string());
|
||
|
||
// Fallback to naive Panda3D loader for raw assets.
|
||
PointerTo<PandaNode> loadedNode = Loader::get_global_ptr()->load_sync(pandaPath);
|
||
|
||
if (loadedNode == nullptr) {
|
||
MetaCoreTrace(("metacore.render: backend model load failed: " + absoluteSourcePath.string()).c_str());
|
||
return NodePath();
|
||
}
|
||
|
||
MetaCoreTrace(("metacore.render: model source load success: " + absoluteSourcePath.string()).c_str());
|
||
return NodePath(loadedNode);
|
||
}
|
||
|
||
// Must be called on the render/main thread. This function creates Panda3D nodes.
|
||
[[nodiscard]] NodePath MetaCoreTryLoadModelRuntimeAsset(const MetaCoreRuntimeModelLoadRequest& request) {
|
||
if (request.ProjectRoot.empty()) {
|
||
MetaCoreTrace("metacore.render: model project root is empty");
|
||
return NodePath();
|
||
}
|
||
if (!request.SourceModelAssetGuid.IsValid() && request.RelativeSourcePath.empty()) {
|
||
MetaCoreTrace("metacore.render: model has neither asset guid nor source path");
|
||
return NodePath();
|
||
}
|
||
|
||
const std::filesystem::path absoluteSourcePath =
|
||
MetaCoreResolveAbsoluteModelSourcePath(request.ProjectRoot, request.RelativeSourcePath);
|
||
const std::filesystem::path binaryPackagePath =
|
||
MetaCoreResolveModelRuntimePackagePath(request, absoluteSourcePath);
|
||
|
||
if (!request.PreferSourceLoader) {
|
||
NodePath binaryModel = MetaCoreTryLoadBinaryModelPackage(binaryPackagePath);
|
||
if (!binaryModel.is_empty()) {
|
||
return binaryModel;
|
||
}
|
||
if (!absoluteSourcePath.empty() && std::filesystem::exists(binaryPackagePath)) {
|
||
MetaCoreTrace(("metacore.render: model binary fallback to source: " + absoluteSourcePath.string()).c_str());
|
||
}
|
||
}
|
||
|
||
if (absoluteSourcePath.empty()) {
|
||
MetaCoreTrace(("metacore.render: model runtime package missing for guid: " + request.SourceModelAssetGuid.ToString()).c_str());
|
||
return NodePath();
|
||
}
|
||
|
||
return MetaCoreTryLoadSourceModelNode(absoluteSourcePath);
|
||
}
|
||
|
||
|
||
// Build a Panda3D Mesh from MetaCore's binary mesh payload.
|
||
// format: [u32 vcount, u32 icount, Vertex[vcount], u32[icount]]
|
||
[[nodiscard]] NodePath MetaCoreBuildMeshFromBinary(
|
||
const std::string& name,
|
||
std::span<const std::byte> payload,
|
||
std::span<const MetaCoreMeshSubMeshDocument> subMeshes
|
||
) {
|
||
if (payload.size() < sizeof(std::uint32_t) * 2) {
|
||
MetaCoreTrace("metacore.render: mesh payload too small for header");
|
||
return NodePath();
|
||
}
|
||
|
||
std::uint32_t vCount = 0;
|
||
std::uint32_t iCount = 0;
|
||
std::memcpy(&vCount, payload.data(), sizeof(std::uint32_t));
|
||
std::memcpy(&iCount, payload.data() + sizeof(std::uint32_t), sizeof(std::uint32_t));
|
||
|
||
MetaCoreTrace(("metacore.render: MetaCoreBuildMeshFromBinary name=" + name + " vCount=" + std::to_string(vCount) + " iCount=" + std::to_string(iCount)).c_str());
|
||
|
||
const std::size_t expectedSize = sizeof(std::uint32_t) * 2 +
|
||
vCount * sizeof(MetaCoreMeshVertex) +
|
||
iCount * sizeof(std::uint32_t);
|
||
|
||
if (payload.size() < expectedSize) {
|
||
MetaCoreTrace(("metacore.render: mesh payload size mismatch. expected=" +
|
||
std::to_string(expectedSize) + " actual=" + std::to_string(payload.size())).c_str());
|
||
return NodePath();
|
||
}
|
||
if (iCount % 3 != 0) {
|
||
MetaCoreTrace(("metacore.render: mesh payload index count is not triangular: " + std::to_string(iCount)).c_str());
|
||
return NodePath();
|
||
}
|
||
|
||
const MetaCoreMeshVertex* vertices = reinterpret_cast<const MetaCoreMeshVertex*>(payload.data() + sizeof(std::uint32_t) * 2);
|
||
const std::uint32_t* indices = reinterpret_cast<const std::uint32_t*>(payload.data() + sizeof(std::uint32_t) * 2 + vCount * sizeof(MetaCoreMeshVertex));
|
||
|
||
PT(GeomVertexData) vdata = new GeomVertexData(name, GeomVertexFormat::get_v3n3t2(), Geom::UH_static);
|
||
vdata->unclean_set_num_rows(vCount);
|
||
|
||
GeomVertexWriter vertexWriter(vdata, "vertex");
|
||
GeomVertexWriter normalWriter(vdata, "normal");
|
||
GeomVertexWriter texcoordWriter(vdata, "texcoord");
|
||
|
||
for (std::uint32_t i = 0; i < vCount; ++i) {
|
||
const LPoint3f pandaPoint = MetaCoreToPandaPoint(glm::vec3(vertices[i].px, vertices[i].py, vertices[i].pz));
|
||
const LVector3f pandaNormal = MetaCoreToPandaVector(glm::vec3(vertices[i].nx, vertices[i].ny, vertices[i].nz));
|
||
vertexWriter.add_data3(pandaPoint);
|
||
normalWriter.add_data3(pandaNormal);
|
||
texcoordWriter.add_data2(vertices[i].u, vertices[i].v);
|
||
}
|
||
|
||
NodePath meshRoot(new PandaNode(name));
|
||
auto appendSubMesh = [&](
|
||
const std::string& subMeshName,
|
||
std::int32_t materialSlotIndex,
|
||
std::uint32_t firstIndex,
|
||
std::uint32_t subMeshIndexCount
|
||
) {
|
||
if (firstIndex >= iCount || subMeshIndexCount == 0) {
|
||
return;
|
||
}
|
||
|
||
const std::uint32_t clampedEnd = std::min<std::uint32_t>(iCount, firstIndex + subMeshIndexCount);
|
||
const std::uint32_t triangleEnd = firstIndex + ((clampedEnd - firstIndex) / 3U) * 3U;
|
||
if (triangleEnd <= firstIndex) {
|
||
return;
|
||
}
|
||
|
||
PT(GeomTriangles) triangles = new GeomTriangles(Geom::UH_static);
|
||
for (std::uint32_t i = firstIndex; i + 2 < triangleEnd; i += 3) {
|
||
triangles->add_vertices(indices[i], indices[i + 1], indices[i + 2]);
|
||
}
|
||
|
||
PT(Geom) geom = new Geom(vdata);
|
||
geom->add_primitive(triangles);
|
||
|
||
PT(GeomNode) node = new GeomNode(subMeshName);
|
||
node->add_geom(geom);
|
||
NodePath subMeshNode = meshRoot.attach_new_node(node);
|
||
subMeshNode.set_tag("MetaCoreMaterialSlotIndex", std::to_string(materialSlotIndex));
|
||
};
|
||
|
||
bool appendedAnySubMesh = false;
|
||
for (const MetaCoreMeshSubMeshDocument& subMesh : subMeshes) {
|
||
const std::uint32_t firstIndex = subMesh.FirstIndex;
|
||
const std::uint32_t subMeshIndexCount = subMesh.IndexCount == 0 ? iCount : subMesh.IndexCount;
|
||
const int childCountBefore = meshRoot.get_num_children();
|
||
appendSubMesh(
|
||
subMesh.Name.empty() ? (name + "_submesh") : subMesh.Name,
|
||
subMesh.MaterialSlotIndex,
|
||
firstIndex,
|
||
subMeshIndexCount
|
||
);
|
||
appendedAnySubMesh = appendedAnySubMesh || meshRoot.get_num_children() > childCountBefore;
|
||
}
|
||
|
||
if (!appendedAnySubMesh) {
|
||
appendSubMesh(name + "_submesh_0", 0, 0, iCount);
|
||
}
|
||
|
||
return meshRoot;
|
||
}
|
||
|
||
|
||
// Build a Panda3D Texture from MetaCore's binary texture payload.
|
||
[[nodiscard]] PT(Texture) MetaCoreBuildTextureFromBinary(
|
||
const std::string& name,
|
||
const MetaCoreTextureAssetDocument& doc,
|
||
std::span<const std::byte> payload
|
||
) {
|
||
if (doc.Width <= 0 || doc.Height <= 0 || doc.Channels <= 0) {
|
||
MetaCoreTrace("metacore.render: texture payload has invalid dimensions");
|
||
return nullptr;
|
||
}
|
||
|
||
PT(Texture) texture = new Texture(name);
|
||
|
||
Texture::Format format = Texture::F_rgba8;
|
||
if (doc.Channels == 3) format = Texture::F_rgb8;
|
||
else if (doc.Channels == 1) format = Texture::F_luminance;
|
||
|
||
texture->setup_2d_texture(doc.Width, doc.Height, Texture::T_unsigned_byte, format);
|
||
|
||
if (doc.Format == "dxt5") {
|
||
PTA_uchar ramImage = PTA_uchar::empty_array(payload.size());
|
||
std::memcpy(ramImage.p(), payload.data(), payload.size());
|
||
texture->set_ram_image(ramImage, Texture::CM_dxt5);
|
||
} else {
|
||
// Copy payload data directly to Panda3D's RAM image buffer
|
||
PTA_uchar ramImage = texture->modify_ram_image();
|
||
if (ramImage.size() == payload.size()) {
|
||
std::memcpy(&ramImage[0], reinterpret_cast<const void*>(payload.data()), payload.size());
|
||
} else {
|
||
MetaCoreTrace(("metacore.render: texture payload size mismatch. expected=" +
|
||
std::to_string(ramImage.size()) + " actual=" + std::to_string(payload.size())).c_str());
|
||
return nullptr;
|
||
}
|
||
}
|
||
|
||
// Enable linear filtering by default for smoother look
|
||
texture->set_minfilter(SamplerState::FT_linear_mipmap_linear);
|
||
texture->set_magfilter(SamplerState::FT_linear);
|
||
|
||
return texture;
|
||
}
|
||
|
||
|
||
[[nodiscard]] PT(Shader) MetaCoreLoadSimplePbrShader() {
|
||
static PT(Shader) cachedShader;
|
||
static bool attemptedLoad = false;
|
||
if (attemptedLoad) {
|
||
return cachedShader;
|
||
}
|
||
|
||
attemptedLoad = true;
|
||
const std::filesystem::path shaderRoot = std::filesystem::current_path() / "simplepbr" / "shaders";
|
||
const std::filesystem::path vertexPath = shaderRoot / "simplepbr.vert";
|
||
const std::filesystem::path fragmentPath = shaderRoot / "simplepbr.frag";
|
||
if (!std::filesystem::exists(vertexPath) || !std::filesystem::exists(fragmentPath)) {
|
||
MetaCoreTrace("simplepbr: shader files missing in runtime directory");
|
||
return nullptr;
|
||
}
|
||
|
||
cachedShader = Shader::load(
|
||
Shader::SL_GLSL,
|
||
Filename::from_os_specific(vertexPath.string()),
|
||
Filename::from_os_specific(fragmentPath.string())
|
||
);
|
||
if (cachedShader == nullptr) {
|
||
MetaCoreTrace("simplepbr: failed to load GLSL shader");
|
||
} else {
|
||
MetaCoreTrace("simplepbr: shader loaded");
|
||
}
|
||
return cachedShader;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreCreateSolidTexture(
|
||
const std::string& textureName,
|
||
unsigned char r,
|
||
unsigned char g,
|
||
unsigned char b,
|
||
unsigned char a
|
||
) {
|
||
PT(Texture) texture = new Texture(textureName);
|
||
texture->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_rgba8);
|
||
PTA_uchar image = texture->modify_ram_image();
|
||
if (image.size() >= 4) {
|
||
image[0] = r;
|
||
image[1] = g;
|
||
image[2] = b;
|
||
image[3] = a;
|
||
}
|
||
texture->set_minfilter(SamplerState::FT_nearest);
|
||
texture->set_magfilter(SamplerState::FT_nearest);
|
||
texture->set_wrap_u(SamplerState::WM_repeat);
|
||
texture->set_wrap_v(SamplerState::WM_repeat);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreCreateSolidCubeMap(
|
||
const std::string& textureName,
|
||
unsigned char r,
|
||
unsigned char g,
|
||
unsigned char b,
|
||
unsigned char a
|
||
) {
|
||
PT(Texture) texture = new Texture(textureName);
|
||
texture->setup_cube_map(1, Texture::T_unsigned_byte, Texture::F_rgba8);
|
||
PTA_uchar image = texture->modify_ram_image();
|
||
for (size_t index = 0; index + 3 < image.size(); index += 4) {
|
||
image[index + 0] = r;
|
||
image[index + 1] = g;
|
||
image[index + 2] = b;
|
||
image[index + 3] = a;
|
||
}
|
||
texture->set_minfilter(SamplerState::FT_nearest);
|
||
texture->set_magfilter(SamplerState::FT_nearest);
|
||
texture->set_wrap_u(SamplerState::WM_clamp);
|
||
texture->set_wrap_v(SamplerState::WM_clamp);
|
||
texture->set_wrap_w(SamplerState::WM_clamp);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultBaseColorTexture() {
|
||
static PT(Texture) texture = MetaCoreCreateSolidTexture("MetaCoreDefaultBaseColor", 255, 255, 255, 255);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultMetalRoughnessTexture() {
|
||
static PT(Texture) texture = MetaCoreCreateSolidTexture("MetaCoreDefaultMetalRoughness", 255, 255, 255, 255);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultEmissionTexture() {
|
||
static PT(Texture) texture = MetaCoreCreateSolidTexture("MetaCoreDefaultEmission", 0, 0, 0, 255);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultAoTexture() {
|
||
static PT(Texture) texture = MetaCoreCreateSolidTexture("MetaCoreDefaultAo", 255, 255, 255, 255);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultNormalTexture() {
|
||
static PT(Texture) texture = MetaCoreCreateSolidTexture("MetaCoreDefaultNormal", 128, 128, 255, 255);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreLoadBrdfLutTexture() {
|
||
static PT(Texture) cachedTexture;
|
||
static bool attemptedLoad = false;
|
||
if (attemptedLoad) {
|
||
return cachedTexture;
|
||
}
|
||
|
||
attemptedLoad = true;
|
||
const std::filesystem::path texturePath = std::filesystem::current_path() / "simplepbr" / "textures" / "brdf_lut.txo";
|
||
if (!std::filesystem::exists(texturePath)) {
|
||
MetaCoreTrace("simplepbr: brdf_lut.txo missing in runtime directory");
|
||
cachedTexture = MetaCoreCreateSolidTexture("MetaCoreFallbackBrdfLut", 255, 255, 255, 255);
|
||
return cachedTexture;
|
||
}
|
||
|
||
cachedTexture = TexturePool::load_texture(Filename::from_os_specific(texturePath.string()));
|
||
if (cachedTexture == nullptr) {
|
||
MetaCoreTrace("simplepbr: failed to load brdf_lut.txo");
|
||
cachedTexture = MetaCoreCreateSolidTexture("MetaCoreFallbackBrdfLut", 255, 255, 255, 255);
|
||
}
|
||
return cachedTexture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreGetDefaultFilteredEnvMap() {
|
||
static PT(Texture) texture = MetaCoreCreateSolidCubeMap("MetaCoreDefaultFilteredEnvMap", 0, 0, 0, 255);
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreResolveTextureWithFallback(
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreAssetGuid& textureGuid,
|
||
const std::string& relativeTexturePath,
|
||
const PT(Texture)& fallbackTexture
|
||
) {
|
||
if (projectRoot.empty() || (!textureGuid.IsValid() && relativeTexturePath.empty())) {
|
||
return fallbackTexture;
|
||
}
|
||
|
||
const std::filesystem::path absoluteTexturePath =
|
||
relativeTexturePath.empty() ? std::filesystem::path{} : projectRoot / std::filesystem::path(relativeTexturePath);
|
||
std::filesystem::path binaryPackagePath;
|
||
if (textureGuid.IsValid()) {
|
||
binaryPackagePath =
|
||
projectRoot / "Library" / "RuntimeAssets" / "Textures" / (textureGuid.ToString() + ".mcasset");
|
||
}
|
||
if ((binaryPackagePath.empty() || !std::filesystem::exists(binaryPackagePath)) && !absoluteTexturePath.empty()) {
|
||
binaryPackagePath = std::filesystem::path(absoluteTexturePath.string() + ".mcasset");
|
||
}
|
||
|
||
if (std::filesystem::exists(binaryPackagePath)) {
|
||
MetaCoreTypeRegistry registry;
|
||
// We only need basic types for the asset document
|
||
MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||
|
||
auto mappedPackage = MetaCoreMappedPackage::Open(binaryPackagePath, registry);
|
||
if (mappedPackage.has_value() && mappedPackage->GetPayloadSectionCount() > 1) {
|
||
// First payload is the metadata (MetaCoreTextureAssetDocument)
|
||
MetaCoreTextureAssetDocument texDoc;
|
||
if (MetaCoreDeserializeFromBytes(mappedPackage->GetPayloadSection(0), texDoc, registry)) {
|
||
PT(Texture) binaryTex = MetaCoreBuildTextureFromBinary(
|
||
relativeTexturePath,
|
||
texDoc,
|
||
mappedPackage->GetPayloadSection(1)
|
||
);
|
||
if (binaryTex != nullptr) {
|
||
MetaCoreTrace(("metacore.render: texture binary load success: " + binaryPackagePath.string()).c_str());
|
||
return binaryTex;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (absoluteTexturePath.empty()) {
|
||
return fallbackTexture;
|
||
}
|
||
|
||
if (!std::filesystem::exists(absoluteTexturePath)) {
|
||
return fallbackTexture;
|
||
}
|
||
|
||
PT(Texture) texture = TexturePool::load_texture(Filename::from_os_specific(absoluteTexturePath.string()));
|
||
|
||
if (texture == nullptr) {
|
||
MetaCoreTrace(("metacore.render: backend texture load failed: " + absoluteTexturePath.string()).c_str());
|
||
return fallbackTexture;
|
||
}
|
||
|
||
return texture;
|
||
}
|
||
|
||
[[nodiscard]] PT(Texture) MetaCoreResolveBaseColorTexture(
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreAssetGuid& textureGuid,
|
||
const std::string& relativeTexturePath
|
||
) {
|
||
return MetaCoreResolveTextureWithFallback(projectRoot, textureGuid, relativeTexturePath, MetaCoreGetDefaultBaseColorTexture());
|
||
}
|
||
|
||
|
||
void MetaCoreApplyBaseColorTexture(
|
||
NodePath& meshNode,
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreAssetGuid& textureGuid,
|
||
const std::string& relativeTexturePath
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
PT(Texture) texture = MetaCoreResolveBaseColorTexture(projectRoot, textureGuid, relativeTexturePath);
|
||
if (texture == nullptr) {
|
||
meshNode.set_texture_off(1);
|
||
return;
|
||
}
|
||
|
||
meshNode.set_texture(texture, 1);
|
||
meshNode.set_shader_input(InternalName::make("p3d_TextureBaseColor"), texture);
|
||
}
|
||
|
||
void MetaCoreApplyMetallicRoughnessTexture(
|
||
NodePath& meshNode,
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreAssetGuid& textureGuid,
|
||
const std::string& relativeTexturePath
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
|
||
projectRoot,
|
||
textureGuid,
|
||
relativeTexturePath,
|
||
MetaCoreGetDefaultMetalRoughnessTexture()
|
||
);
|
||
if (texture == nullptr) {
|
||
return;
|
||
}
|
||
|
||
meshNode.set_shader_input(InternalName::make("p3d_TextureMetalRoughness"), texture);
|
||
}
|
||
|
||
void MetaCoreApplyNormalTexture(
|
||
NodePath& meshNode,
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreAssetGuid& textureGuid,
|
||
const std::string& relativeTexturePath
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
|
||
projectRoot,
|
||
textureGuid,
|
||
relativeTexturePath,
|
||
MetaCoreGetDefaultNormalTexture()
|
||
);
|
||
if (texture == nullptr) {
|
||
return;
|
||
}
|
||
|
||
meshNode.set_shader_input(InternalName::make("p3d_TextureNormal"), texture);
|
||
}
|
||
|
||
void MetaCoreApplyEmissionTexture(
|
||
NodePath& meshNode,
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreAssetGuid& textureGuid,
|
||
const std::string& relativeTexturePath
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
|
||
projectRoot,
|
||
textureGuid,
|
||
relativeTexturePath,
|
||
MetaCoreGetDefaultEmissionTexture()
|
||
);
|
||
if (texture == nullptr) {
|
||
return;
|
||
}
|
||
|
||
meshNode.set_shader_input(InternalName::make("p3d_TextureEmission"), texture);
|
||
}
|
||
|
||
void MetaCoreApplyAoTexture(
|
||
NodePath& meshNode,
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreAssetGuid& textureGuid,
|
||
const std::string& relativeTexturePath
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
|
||
projectRoot,
|
||
textureGuid,
|
||
relativeTexturePath,
|
||
MetaCoreGetDefaultAoTexture()
|
||
);
|
||
if (texture == nullptr) {
|
||
return;
|
||
}
|
||
|
||
meshNode.set_shader_input(InternalName::make("p3d_TextureOcclusion"), texture);
|
||
}
|
||
|
||
void MetaCoreApplySimplePbrPreview(NodePath& meshNode) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
PT(Shader) simplePbrShader = MetaCoreLoadSimplePbrShader();
|
||
if (simplePbrShader == nullptr) {
|
||
meshNode.clear_shader();
|
||
return;
|
||
}
|
||
|
||
meshNode.set_shader(simplePbrShader, 1);
|
||
}
|
||
|
||
void MetaCoreApplySimplePbrMaterialInputs(
|
||
NodePath& meshNode,
|
||
const MetaCoreMeshRendererComponent& meshRenderer
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_Material.baseColor"),
|
||
LVecBase4(
|
||
meshRenderer.BaseColor.r,
|
||
meshRenderer.BaseColor.g,
|
||
meshRenderer.BaseColor.b,
|
||
1.0F
|
||
)
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_Material.roughness"),
|
||
LVecBase4(meshRenderer.Roughness, 0.0F, 0.0F, 0.0F)
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_Material.metallic"),
|
||
LVecBase4(meshRenderer.Metallic, 0.0F, 0.0F, 0.0F)
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_Material.emission"),
|
||
LVecBase4(
|
||
meshRenderer.EmissiveColor.r,
|
||
meshRenderer.EmissiveColor.g,
|
||
meshRenderer.EmissiveColor.b,
|
||
1.0F
|
||
)
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_TextureMetalRoughness"),
|
||
MetaCoreGetDefaultMetalRoughnessTexture()
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_TextureNormal"),
|
||
MetaCoreGetDefaultNormalTexture()
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_TextureEmission"),
|
||
MetaCoreGetDefaultEmissionTexture()
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("p3d_TextureOcclusion"),
|
||
MetaCoreGetDefaultAoTexture()
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("brdf_lut"),
|
||
MetaCoreLoadBrdfLutTexture()
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("filtered_env_map"),
|
||
MetaCoreGetDefaultFilteredEnvMap()
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("max_reflection_lod"),
|
||
LVecBase4(0.0F, 0.0F, 0.0F, 0.0F)
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("metacore_AlphaCutoff"),
|
||
LVecBase4(meshRenderer.AlphaCutoff, 0.0F, 0.0F, 0.0F)
|
||
);
|
||
meshNode.set_shader_input(
|
||
InternalName::make("metacore_AlphaMode"),
|
||
LVecBase4(static_cast<float>(meshRenderer.AlphaMode), 0.0F, 0.0F, 0.0F)
|
||
);
|
||
}
|
||
|
||
[[nodiscard]] std::string MetaCoreFindTextureSourcePath(
|
||
const MetaCoreModelAssetDocument& modelDoc,
|
||
const MetaCoreAssetGuid& textureGuid
|
||
) {
|
||
if (!textureGuid.IsValid()) {
|
||
return {};
|
||
}
|
||
|
||
const auto textureIterator = std::find_if(
|
||
modelDoc.GeneratedTextureAssets.begin(),
|
||
modelDoc.GeneratedTextureAssets.end(),
|
||
[&](const MetaCoreTextureAssetDocument& textureAsset) {
|
||
return textureAsset.AssetGuid == textureGuid;
|
||
}
|
||
);
|
||
return textureIterator != modelDoc.GeneratedTextureAssets.end()
|
||
? textureIterator->SourcePath.generic_string()
|
||
: std::string{};
|
||
}
|
||
|
||
[[nodiscard]] MetaCoreMeshRendererComponent MetaCoreBuildMaterialPreviewComponent(
|
||
const MetaCoreModelAssetDocument& modelDoc,
|
||
const MetaCoreMaterialAssetDocument& materialAsset
|
||
) {
|
||
MetaCoreMeshRendererComponent preview;
|
||
preview.BaseColor = materialAsset.BaseColor;
|
||
preview.DoubleSided = materialAsset.DoubleSided;
|
||
preview.Metallic = materialAsset.Metallic;
|
||
preview.Roughness = materialAsset.Roughness;
|
||
preview.AlphaCutoff = materialAsset.AlphaCutoff;
|
||
preview.EmissiveColor = materialAsset.EmissiveColor;
|
||
preview.AlphaMode =
|
||
materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Mask
|
||
? MetaCoreMeshAlphaMode::Mask
|
||
: (materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Blend
|
||
? MetaCoreMeshAlphaMode::Blend
|
||
: MetaCoreMeshAlphaMode::Opaque);
|
||
preview.BaseColorTextureGuid = materialAsset.BaseColorTexture;
|
||
preview.MetallicRoughnessTextureGuid = materialAsset.MetallicRoughnessTexture;
|
||
preview.NormalTextureGuid = materialAsset.NormalTexture;
|
||
preview.EmissiveTextureGuid = materialAsset.EmissiveTexture;
|
||
preview.AoTextureGuid = materialAsset.AoTexture;
|
||
preview.BaseColorTexturePath = MetaCoreFindTextureSourcePath(modelDoc, materialAsset.BaseColorTexture);
|
||
preview.MetallicRoughnessTexturePath = MetaCoreFindTextureSourcePath(modelDoc, materialAsset.MetallicRoughnessTexture);
|
||
preview.NormalTexturePath = MetaCoreFindTextureSourcePath(modelDoc, materialAsset.NormalTexture);
|
||
preview.EmissiveTexturePath = MetaCoreFindTextureSourcePath(modelDoc, materialAsset.EmissiveTexture);
|
||
preview.AoTexturePath = MetaCoreFindTextureSourcePath(modelDoc, materialAsset.AoTexture);
|
||
return preview;
|
||
}
|
||
|
||
void MetaCoreApplyMaterialPreviewToNode(
|
||
NodePath& meshNode,
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreMeshRendererComponent& materialPreview,
|
||
const NodePath& sceneRootNode,
|
||
const NodePath* cameraNode
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
meshNode.set_material_off(1);
|
||
meshNode.set_light_off(1);
|
||
meshNode.set_two_sided(materialPreview.DoubleSided);
|
||
meshNode.set_color(
|
||
materialPreview.BaseColor.r,
|
||
materialPreview.BaseColor.g,
|
||
materialPreview.BaseColor.b,
|
||
1.0F
|
||
);
|
||
MetaCoreApplyBaseColorTexture(
|
||
meshNode,
|
||
projectRoot,
|
||
materialPreview.BaseColorTextureGuid,
|
||
materialPreview.BaseColorTexturePath
|
||
);
|
||
MetaCoreApplyMetallicRoughnessTexture(
|
||
meshNode,
|
||
projectRoot,
|
||
materialPreview.MetallicRoughnessTextureGuid,
|
||
materialPreview.MetallicRoughnessTexturePath
|
||
);
|
||
MetaCoreApplyNormalTexture(
|
||
meshNode,
|
||
projectRoot,
|
||
materialPreview.NormalTextureGuid,
|
||
materialPreview.NormalTexturePath
|
||
);
|
||
MetaCoreApplyEmissionTexture(
|
||
meshNode,
|
||
projectRoot,
|
||
materialPreview.EmissiveTextureGuid,
|
||
materialPreview.EmissiveTexturePath
|
||
);
|
||
MetaCoreApplyAoTexture(
|
||
meshNode,
|
||
projectRoot,
|
||
materialPreview.AoTextureGuid,
|
||
materialPreview.AoTexturePath
|
||
);
|
||
MetaCoreApplySimplePbrPreview(meshNode);
|
||
MetaCoreApplySimplePbrMaterialInputs(meshNode, materialPreview);
|
||
if (cameraNode != nullptr) {
|
||
MetaCoreApplySimplePbrViewInputs(meshNode, sceneRootNode, *cameraNode);
|
||
}
|
||
|
||
if (materialPreview.AlphaMode == MetaCoreMeshAlphaMode::Blend) {
|
||
meshNode.set_transparency(TransparencyAttrib::M_alpha);
|
||
} else if (materialPreview.AlphaMode == MetaCoreMeshAlphaMode::Mask) {
|
||
meshNode.set_transparency(TransparencyAttrib::M_binary);
|
||
} else {
|
||
meshNode.clear_transparency();
|
||
}
|
||
}
|
||
|
||
void MetaCoreApplyMaterialSlotsToSubMeshes(
|
||
NodePath& meshNode,
|
||
const std::filesystem::path& projectRoot,
|
||
const MetaCoreMeshRendererComponent& meshRenderer,
|
||
const MetaCoreModelAssetDocument& modelDoc,
|
||
const NodePath& sceneRootNode,
|
||
const NodePath* cameraNode
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
NodePathCollection subMeshNodes = meshNode.find_all_matches("**/=MetaCoreMaterialSlotIndex");
|
||
for (int nodeIndex = 0; nodeIndex < subMeshNodes.get_num_paths(); ++nodeIndex) {
|
||
NodePath subMeshNode = subMeshNodes.get_path(nodeIndex);
|
||
const std::string slotText = subMeshNode.get_tag("MetaCoreMaterialSlotIndex");
|
||
char* parseEnd = nullptr;
|
||
const long slotIndex = std::strtol(slotText.c_str(), &parseEnd, 10);
|
||
if (parseEnd == slotText.c_str() || slotIndex < 0 ||
|
||
static_cast<std::size_t>(slotIndex) >= meshRenderer.MaterialAssetGuids.size()) {
|
||
MetaCoreApplyMaterialPreviewToNode(subMeshNode, projectRoot, meshRenderer, sceneRootNode, cameraNode);
|
||
continue;
|
||
}
|
||
|
||
const MetaCoreAssetGuid materialGuid =
|
||
meshRenderer.MaterialAssetGuids[static_cast<std::size_t>(slotIndex)];
|
||
if (!materialGuid.IsValid()) {
|
||
MetaCoreApplyMaterialPreviewToNode(subMeshNode, projectRoot, meshRenderer, sceneRootNode, cameraNode);
|
||
continue;
|
||
}
|
||
|
||
const auto materialIterator = std::find_if(
|
||
modelDoc.GeneratedMaterialAssets.begin(),
|
||
modelDoc.GeneratedMaterialAssets.end(),
|
||
[&](const MetaCoreMaterialAssetDocument& materialAsset) {
|
||
return materialAsset.AssetGuid == materialGuid;
|
||
}
|
||
);
|
||
if (materialIterator == modelDoc.GeneratedMaterialAssets.end()) {
|
||
MetaCoreApplyMaterialPreviewToNode(subMeshNode, projectRoot, meshRenderer, sceneRootNode, cameraNode);
|
||
continue;
|
||
}
|
||
|
||
MetaCoreMeshRendererComponent materialPreview =
|
||
MetaCoreBuildMaterialPreviewComponent(modelDoc, *materialIterator);
|
||
MetaCoreApplyMaterialPreviewToNode(
|
||
subMeshNode,
|
||
projectRoot,
|
||
materialPreview,
|
||
sceneRootNode,
|
||
cameraNode
|
||
);
|
||
}
|
||
}
|
||
|
||
void MetaCoreApplySimplePbrViewInputs(
|
||
NodePath& meshNode,
|
||
const NodePath& sceneRootNode,
|
||
const NodePath& cameraNode
|
||
) {
|
||
if (meshNode.is_empty()) {
|
||
return;
|
||
}
|
||
|
||
LPoint3f cameraWorldPosition(0.0F, 0.0F, 0.0F);
|
||
if (!cameraNode.is_empty()) {
|
||
cameraWorldPosition = cameraNode.get_pos(sceneRootNode);
|
||
}
|
||
|
||
meshNode.set_shader_input(
|
||
InternalName::make("camera_world_position"),
|
||
LVecBase3f(cameraWorldPosition.get_x(), cameraWorldPosition.get_y(), cameraWorldPosition.get_z())
|
||
);
|
||
}
|
||
|
||
glm::mat4 MetaCoreConvertPandaMatrixToGlm(const LMatrix4f& matrix) {
|
||
glm::mat4 result(1.0F);
|
||
for (int col = 0; col < 4; ++col) {
|
||
for (int row = 0; row < 4; ++row) {
|
||
result[col][row] = matrix.get_cell(col, row);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
class MetaCorePandaSceneBridge::MetaCorePandaSceneBridgeImpl {
|
||
public:
|
||
struct MetaCorePandaObjectState {
|
||
NodePath RootNode{};
|
||
NodePath MeshNode{};
|
||
NodePath LightNode{};
|
||
MetaCoreMeshSourceKind MeshSource = MetaCoreMeshSourceKind::Builtin;
|
||
MetaCoreBuiltinMeshType BuiltinMesh = MetaCoreBuiltinMeshType::Cube;
|
||
MetaCoreAssetGuid MeshAssetGuid{};
|
||
MetaCoreAssetGuid SourceModelAssetGuid{};
|
||
std::string SourceModelPath{};
|
||
std::int32_t ModelNodeIndex = -1;
|
||
std::vector<MetaCoreAssetGuid> MaterialAssetGuids{};
|
||
bool IsUsingPlaceholder = false;
|
||
};
|
||
|
||
MetaCoreRenderDevice* RenderDevice = nullptr;
|
||
NodePath GridNode{};
|
||
NodePath AmbientLightNode{};
|
||
std::filesystem::path ProjectRootPath{};
|
||
MetaCoreId SelectedObjectId = 0;
|
||
std::unordered_map<MetaCoreId, MetaCorePandaObjectState> ObjectStates{};
|
||
|
||
// Asset Management
|
||
std::mutex CacheMutex{};
|
||
std::unordered_map<std::string, NodePath> ModelCache{};
|
||
bool RuntimeSyncFailure = false;
|
||
std::string LastRuntimeSyncFailure{};
|
||
};
|
||
|
||
MetaCorePandaSceneBridge::MetaCorePandaSceneBridge()
|
||
: Impl_(std::make_unique<MetaCorePandaSceneBridgeImpl>()) {
|
||
}
|
||
|
||
MetaCorePandaSceneBridge::~MetaCorePandaSceneBridge() {
|
||
Shutdown();
|
||
}
|
||
|
||
bool MetaCorePandaSceneBridge::Initialize(MetaCoreRenderDevice& renderDevice) {
|
||
Shutdown();
|
||
|
||
auto* windowFrameworkHandle = static_cast<WindowFramework*>(renderDevice.GetNativeWindowFrameworkHandle());
|
||
auto* sceneRootHandle = static_cast<NodePath*>(renderDevice.GetNativeSceneRootHandle());
|
||
if (windowFrameworkHandle == nullptr || sceneRootHandle == nullptr || sceneRootHandle->is_empty()) {
|
||
return false;
|
||
}
|
||
|
||
Impl_->RenderDevice = &renderDevice;
|
||
Impl_->ProjectRootPath = MetaCoreResolveProjectRootFromEnvironment();
|
||
|
||
Impl_->GridNode = MetaCoreCreateGridNode(*sceneRootHandle);
|
||
if (!Impl_->GridNode.is_empty()) {
|
||
Impl_->GridNode.set_light_off(1);
|
||
}
|
||
|
||
PT(AmbientLight) ambientLight = new AmbientLight("MetaCoreAmbientLight");
|
||
ambientLight->set_color(LColor(0.85F, 0.85F, 0.85F, 1.0F));
|
||
Impl_->AmbientLightNode = sceneRootHandle->attach_new_node(ambientLight);
|
||
sceneRootHandle->set_light(Impl_->AmbientLightNode);
|
||
|
||
return true;
|
||
}
|
||
|
||
void MetaCorePandaSceneBridge::Shutdown() {
|
||
if (Impl_ == nullptr) {
|
||
return;
|
||
}
|
||
|
||
for (auto& [objectId, objectState] : Impl_->ObjectStates) {
|
||
(void)objectId;
|
||
if (!objectState.MeshNode.is_empty()) {
|
||
objectState.MeshNode.remove_node();
|
||
objectState.MeshNode = NodePath();
|
||
}
|
||
if (!objectState.LightNode.is_empty()) {
|
||
objectState.LightNode.remove_node();
|
||
objectState.LightNode = NodePath();
|
||
}
|
||
if (!objectState.RootNode.is_empty()) {
|
||
objectState.RootNode.remove_node();
|
||
objectState.RootNode = NodePath();
|
||
}
|
||
}
|
||
Impl_->ObjectStates.clear();
|
||
|
||
// Clear asset cache
|
||
{
|
||
std::lock_guard<std::mutex> lock(Impl_->CacheMutex);
|
||
for (auto& [path, node] : Impl_->ModelCache) {
|
||
node.remove_node();
|
||
}
|
||
Impl_->ModelCache.clear();
|
||
}
|
||
|
||
if (!Impl_->AmbientLightNode.is_empty()) {
|
||
Impl_->AmbientLightNode.remove_node();
|
||
Impl_->AmbientLightNode = NodePath();
|
||
}
|
||
|
||
|
||
if (!Impl_->GridNode.is_empty()) {
|
||
Impl_->GridNode.remove_node();
|
||
Impl_->GridNode = NodePath();
|
||
}
|
||
|
||
Impl_->RenderDevice = nullptr;
|
||
Impl_->ProjectRootPath.clear();
|
||
Impl_->SelectedObjectId = 0;
|
||
Impl_->RuntimeSyncFailure = false;
|
||
Impl_->LastRuntimeSyncFailure.clear();
|
||
}
|
||
|
||
void MetaCorePandaSceneBridge::SetProjectRootPath(const std::filesystem::path& projectRootPath) {
|
||
if (Impl_ == nullptr) {
|
||
return;
|
||
}
|
||
if (Impl_->ProjectRootPath != projectRootPath) {
|
||
std::lock_guard<std::mutex> lock(Impl_->CacheMutex);
|
||
for (auto& [cacheKey, node] : Impl_->ModelCache) {
|
||
(void)cacheKey;
|
||
node.remove_node();
|
||
}
|
||
Impl_->ModelCache.clear();
|
||
}
|
||
Impl_->ProjectRootPath = projectRootPath;
|
||
MetaCoreTrace(("metacore.render: project root path set to " + projectRootPath.string()).c_str());
|
||
}
|
||
|
||
void MetaCorePandaSceneBridge::SyncScene(const MetaCoreScene& scene, bool compatibilityMeshOnly) {
|
||
if (Impl_->RuntimeSyncFailure) {
|
||
return;
|
||
}
|
||
|
||
if (Impl_->RenderDevice == nullptr) {
|
||
return;
|
||
}
|
||
|
||
auto* windowFrameworkHandle = static_cast<WindowFramework*>(Impl_->RenderDevice->GetNativeWindowFrameworkHandle());
|
||
auto* sceneRootHandle = static_cast<NodePath*>(Impl_->RenderDevice->GetNativeSceneRootHandle());
|
||
if (windowFrameworkHandle == nullptr || sceneRootHandle == nullptr || sceneRootHandle->is_empty()) {
|
||
return;
|
||
}
|
||
|
||
std::unordered_set<MetaCoreId> aliveObjectIds;
|
||
|
||
for (const MetaCoreGameObject& gameObject : scene.GetGameObjects()) {
|
||
if (compatibilityMeshOnly && !gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||
continue;
|
||
}
|
||
std::string syncStage = "begin";
|
||
try {
|
||
aliveObjectIds.insert(gameObject.GetId());
|
||
syncStage = "object_state_lookup";
|
||
auto [iterator, inserted] = Impl_->ObjectStates.try_emplace(gameObject.GetId());
|
||
MetaCorePandaSceneBridgeImpl::MetaCorePandaObjectState& objectState = iterator->second;
|
||
if (!compatibilityMeshOnly) {
|
||
if (inserted || objectState.RootNode.is_empty()) {
|
||
syncStage = "create_root_node";
|
||
PT(PandaNode) rootNode = new PandaNode("MetaCoreObject");
|
||
syncStage = "attach_root_node";
|
||
objectState.RootNode = sceneRootHandle->attach_new_node(rootNode);
|
||
}
|
||
|
||
syncStage = "apply_transform";
|
||
MetaCoreApplyTransformToPandaNode(gameObject.GetComponent<MetaCoreTransformComponent>(), objectState.RootNode);
|
||
|
||
if (gameObject.GetParentId() != 0) {
|
||
syncStage = "reparent_parent";
|
||
const auto parentIterator = Impl_->ObjectStates.find(gameObject.GetParentId());
|
||
if (parentIterator != Impl_->ObjectStates.end() && !parentIterator->second.RootNode.is_empty()) {
|
||
objectState.RootNode.reparent_to(parentIterator->second.RootNode);
|
||
} else {
|
||
syncStage = "reparent_scene_root_from_parent";
|
||
objectState.RootNode.reparent_to(*sceneRootHandle);
|
||
}
|
||
} else {
|
||
syncStage = "reparent_scene_root";
|
||
objectState.RootNode.reparent_to(*sceneRootHandle);
|
||
}
|
||
} else if (!objectState.RootNode.is_empty()) {
|
||
objectState.RootNode.remove_node();
|
||
objectState.RootNode = NodePath();
|
||
}
|
||
|
||
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||
syncStage = "mesh_renderer";
|
||
const bool materialSlotsChanged =
|
||
objectState.MaterialAssetGuids != gameObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids;
|
||
const bool requiresMeshRebuild =
|
||
objectState.MeshNode.is_empty() ||
|
||
objectState.MeshSource != gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshSource ||
|
||
(gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshSource == MetaCoreMeshSourceKind::Builtin &&
|
||
objectState.BuiltinMesh != gameObject.GetComponent<MetaCoreMeshRendererComponent>().BuiltinMesh) ||
|
||
(gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshSource == MetaCoreMeshSourceKind::Asset &&
|
||
(objectState.MeshAssetGuid != gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshAssetGuid ||
|
||
objectState.SourceModelAssetGuid != gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelAssetGuid ||
|
||
(MetaCoreShouldCompareSourceModelPath(
|
||
objectState.SourceModelAssetGuid,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelAssetGuid
|
||
) &&
|
||
objectState.SourceModelPath != gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath) ||
|
||
objectState.ModelNodeIndex != gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex));
|
||
|
||
if (requiresMeshRebuild && !objectState.MeshNode.is_empty()) {
|
||
objectState.MeshNode.remove_node();
|
||
objectState.MeshNode = NodePath();
|
||
}
|
||
|
||
if (requiresMeshRebuild) {
|
||
NodePath meshParent = compatibilityMeshOnly ? *sceneRootHandle : objectState.RootNode;
|
||
if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshSource == MetaCoreMeshSourceKind::Builtin) {
|
||
objectState.MeshNode = MetaCoreCreateUnitCubeNode(windowFrameworkHandle, meshParent);
|
||
objectState.IsUsingPlaceholder = false;
|
||
} else {
|
||
const std::string& path = gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath;
|
||
const std::string modelCacheKey = MetaCoreBuildModelCacheKey(
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelAssetGuid,
|
||
path
|
||
);
|
||
|
||
// 1. Check Model Cache
|
||
bool foundInCache = false;
|
||
{
|
||
std::lock_guard<std::mutex> lock(Impl_->CacheMutex);
|
||
auto it = Impl_->ModelCache.find(modelCacheKey);
|
||
if (it != Impl_->ModelCache.end()) {
|
||
NodePath masterNode = it->second;
|
||
if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex < 0) {
|
||
objectState.MeshNode = masterNode.copy_to(meshParent);
|
||
} else {
|
||
NodePath subNode = masterNode.find("**/=MetaCoreNodeIndex=" + std::to_string(gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex));
|
||
if (!subNode.is_empty()) {
|
||
objectState.MeshNode = subNode.copy_to(meshParent);
|
||
objectState.MeshNode.set_mat(LMatrix4::ident_mat());
|
||
|
||
// Only remove children that are tagged as MetaCore model nodes,
|
||
// as these represent separate game objects in our hierarchy.
|
||
// This preserves internal geometry nodes (GeomNodes) of the targeted node.
|
||
NodePathCollection children = objectState.MeshNode.get_children();
|
||
for (int i = 0; i < children.get_num_paths(); ++i) {
|
||
NodePath child = children.get_path(i);
|
||
if (child.has_tag("MetaCoreNodeIndex")) {
|
||
child.remove_node();
|
||
}
|
||
}
|
||
} else {
|
||
objectState.MeshNode = masterNode.copy_to(meshParent);
|
||
}
|
||
}
|
||
objectState.IsUsingPlaceholder = false;
|
||
foundInCache = true;
|
||
}
|
||
}
|
||
|
||
if (!foundInCache) {
|
||
const std::filesystem::path& projectRoot =
|
||
!Impl_->ProjectRootPath.empty()
|
||
? Impl_->ProjectRootPath
|
||
: (Impl_->ProjectRootPath = MetaCoreResolveProjectRootFromEnvironment());
|
||
|
||
NodePath masterNode = MetaCoreTryLoadModelRuntimeAsset(MetaCoreRuntimeModelLoadRequest{
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelAssetGuid,
|
||
projectRoot,
|
||
path,
|
||
false
|
||
});
|
||
if (!masterNode.is_empty()) {
|
||
{
|
||
std::lock_guard<std::mutex> lock(Impl_->CacheMutex);
|
||
Impl_->ModelCache[modelCacheKey] = masterNode;
|
||
}
|
||
|
||
if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex < 0) {
|
||
objectState.MeshNode = masterNode.copy_to(meshParent);
|
||
} else {
|
||
NodePath subNode = masterNode.find("**/=MetaCoreNodeIndex=" + std::to_string(gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex));
|
||
if (!subNode.is_empty()) {
|
||
objectState.MeshNode = subNode.copy_to(meshParent);
|
||
objectState.MeshNode.set_mat(LMatrix4::ident_mat());
|
||
|
||
NodePathCollection children = objectState.MeshNode.get_children();
|
||
for (int i = 0; i < children.get_num_paths(); ++i) {
|
||
NodePath child = children.get_path(i);
|
||
if (child.has_tag("MetaCoreNodeIndex")) {
|
||
child.remove_node();
|
||
}
|
||
}
|
||
} else {
|
||
objectState.MeshNode = masterNode.copy_to(meshParent);
|
||
}
|
||
}
|
||
objectState.IsUsingPlaceholder = false;
|
||
} else {
|
||
objectState.MeshNode = MetaCoreCreateUnitCubeNode(windowFrameworkHandle, meshParent);
|
||
objectState.MeshNode.set_scale(0.2F);
|
||
objectState.IsUsingPlaceholder = true;
|
||
}
|
||
}
|
||
}
|
||
objectState.MeshSource = gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshSource;
|
||
objectState.BuiltinMesh = gameObject.GetComponent<MetaCoreMeshRendererComponent>().BuiltinMesh;
|
||
objectState.MeshAssetGuid = gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshAssetGuid;
|
||
objectState.SourceModelAssetGuid = gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelAssetGuid;
|
||
objectState.SourceModelPath = gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath;
|
||
objectState.ModelNodeIndex = gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex;
|
||
}
|
||
|
||
if (!objectState.MeshNode.is_empty()) {
|
||
if (compatibilityMeshOnly) {
|
||
syncStage = "apply_mesh_transform";
|
||
const MetaCoreTransformComponent worldTransform = MetaCoreBuildWorldTransformComponent(scene, gameObject);
|
||
MetaCoreApplyTransformToPandaNode(worldTransform, objectState.MeshNode);
|
||
}
|
||
if (materialSlotsChanged) {
|
||
syncStage = "refresh_material_slots";
|
||
}
|
||
objectState.MeshNode.set_material_off(1);
|
||
objectState.MeshNode.set_light_off(1);
|
||
objectState.MeshNode.set_two_sided(gameObject.GetComponent<MetaCoreMeshRendererComponent>().DoubleSided);
|
||
|
||
if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshSource == MetaCoreMeshSourceKind::Asset) {
|
||
if (objectState.IsUsingPlaceholder) {
|
||
// 如果是占位符立方体,用红色显示
|
||
objectState.MeshNode.set_color(1.0F, 0.0F, 0.0F, 1.0F);
|
||
} else {
|
||
// 如果成功加载了资产网格,用亮绿色显示,确保能看清几何体
|
||
objectState.MeshNode.set_color(0.0F, 1.0F, 0.0F, 1.0F);
|
||
}
|
||
} else {
|
||
// 内置网格(如 Cube)使用原本的 BaseColor
|
||
objectState.MeshNode.set_color(
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor.r,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor.g,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor.b,
|
||
1.0F
|
||
);
|
||
}
|
||
|
||
// 按照敏捷开发基础版思路,暂时跳过复杂的贴图和 PBR 材质应用,只看纯色几何体
|
||
/*
|
||
const bool useAssetPreviewPath =
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().MeshSource == MetaCoreMeshSourceKind::Asset &&
|
||
!objectState.IsUsingPlaceholder;
|
||
if (useAssetPreviewPath) {
|
||
MetaCoreApplyBaseColorTexture(
|
||
objectState.MeshNode,
|
||
Impl_->ProjectRootPath,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColorTextureGuid,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColorTexturePath
|
||
);
|
||
MetaCoreApplyMetallicRoughnessTexture(
|
||
objectState.MeshNode,
|
||
Impl_->ProjectRootPath,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().MetallicRoughnessTextureGuid,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().MetallicRoughnessTexturePath
|
||
);
|
||
MetaCoreApplyNormalTexture(
|
||
objectState.MeshNode,
|
||
Impl_->ProjectRootPath,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().NormalTextureGuid,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().NormalTexturePath
|
||
);
|
||
MetaCoreApplyEmissionTexture(
|
||
objectState.MeshNode,
|
||
Impl_->ProjectRootPath,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveTextureGuid,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().EmissiveTexturePath
|
||
);
|
||
MetaCoreApplyAoTexture(
|
||
objectState.MeshNode,
|
||
Impl_->ProjectRootPath,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().AoTextureGuid,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().AoTexturePath
|
||
);
|
||
MetaCoreApplySimplePbrPreview(objectState.MeshNode);
|
||
MetaCoreApplySimplePbrMaterialInputs(objectState.MeshNode, gameObject.GetComponent<MetaCoreMeshRendererComponent>());
|
||
auto* editorCameraHandle = static_cast<NodePath*>(Impl_->RenderDevice->GetNativeEditorCameraHandle());
|
||
if (editorCameraHandle != nullptr) {
|
||
MetaCoreApplySimplePbrViewInputs(objectState.MeshNode, *sceneRootHandle, *editorCameraHandle);
|
||
}
|
||
}
|
||
// 同样注释掉后续的材质槽应用和 else 分支,避免括号不匹配
|
||
/*
|
||
const std::filesystem::path& projectRoot =
|
||
!Impl_->ProjectRootPath.empty()
|
||
? Impl_->ProjectRootPath
|
||
: (Impl_->ProjectRootPath = MetaCoreResolveProjectRootFromEnvironment());
|
||
const auto modelDoc = MetaCoreReadRuntimeModelDocument(MetaCoreRuntimeModelLoadRequest{
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelAssetGuid,
|
||
projectRoot,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath,
|
||
false
|
||
});
|
||
if (modelDoc.has_value()) {
|
||
MetaCoreApplyMaterialSlotsToSubMeshes(
|
||
objectState.MeshNode,
|
||
projectRoot,
|
||
gameObject.GetComponent<MetaCoreMeshRendererComponent>(),
|
||
*modelDoc,
|
||
*sceneRootHandle,
|
||
editorCameraHandle
|
||
);
|
||
}
|
||
objectState.MaterialAssetGuids = gameObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids;
|
||
} else {
|
||
objectState.MeshNode.set_texture_off(1);
|
||
objectState.MeshNode.set_shader_off(1);
|
||
objectState.MaterialAssetGuids.clear();
|
||
}
|
||
*/
|
||
|
||
if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().AlphaMode == MetaCoreMeshAlphaMode::Blend) {
|
||
objectState.MeshNode.set_transparency(TransparencyAttrib::M_alpha);
|
||
} else if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().AlphaMode == MetaCoreMeshAlphaMode::Mask) {
|
||
objectState.MeshNode.set_transparency(TransparencyAttrib::M_binary);
|
||
} else {
|
||
objectState.MeshNode.clear_transparency();
|
||
}
|
||
|
||
if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().Visible) {
|
||
objectState.MeshNode.show();
|
||
} else {
|
||
objectState.MeshNode.hide();
|
||
}
|
||
|
||
if (gameObject.GetId() == Impl_->SelectedObjectId) {
|
||
objectState.MeshNode.set_color_scale(1.18F, 1.02F, 0.72F, 1.0F);
|
||
} else {
|
||
objectState.MeshNode.clear_color_scale();
|
||
}
|
||
}
|
||
} else if (!objectState.MeshNode.is_empty()) {
|
||
objectState.MeshNode.remove_node();
|
||
objectState.MeshNode = NodePath();
|
||
objectState.MaterialAssetGuids.clear();
|
||
}
|
||
|
||
if (gameObject.HasComponent<MetaCoreLightComponent>()) {
|
||
syncStage = "light_component";
|
||
if (objectState.LightNode.is_empty()) {
|
||
PT(DirectionalLight) newLight = new DirectionalLight("MetaCoreDirectionalLight");
|
||
objectState.LightNode = objectState.RootNode.attach_new_node(newLight);
|
||
sceneRootHandle->set_light(objectState.LightNode);
|
||
}
|
||
|
||
auto* directionalLight = DCAST(DirectionalLight, objectState.LightNode.node());
|
||
if (directionalLight != nullptr) {
|
||
directionalLight->set_color(LColor(
|
||
gameObject.GetComponent<MetaCoreLightComponent>().Color.x * gameObject.GetComponent<MetaCoreLightComponent>().Intensity,
|
||
gameObject.GetComponent<MetaCoreLightComponent>().Color.y * gameObject.GetComponent<MetaCoreLightComponent>().Intensity,
|
||
gameObject.GetComponent<MetaCoreLightComponent>().Color.z * gameObject.GetComponent<MetaCoreLightComponent>().Intensity,
|
||
1.0F
|
||
));
|
||
}
|
||
} else if (!objectState.LightNode.is_empty()) {
|
||
sceneRootHandle->clear_light(objectState.LightNode);
|
||
objectState.LightNode.remove_node();
|
||
objectState.LightNode = NodePath();
|
||
}
|
||
} catch (const std::exception& exceptionObject) {
|
||
Impl_->RuntimeSyncFailure = true;
|
||
Impl_->LastRuntimeSyncFailure =
|
||
"metacore.render object " + std::to_string(gameObject.GetId()) + " (" + gameObject.GetName() + ") stage=" + syncStage + ": " + exceptionObject.what();
|
||
MetaCoreTrace(Impl_->LastRuntimeSyncFailure.c_str());
|
||
return;
|
||
} catch (...) {
|
||
Impl_->RuntimeSyncFailure = true;
|
||
Impl_->LastRuntimeSyncFailure =
|
||
"metacore.render object " + std::to_string(gameObject.GetId()) + " (" + gameObject.GetName() + ") stage=" + syncStage + ": non-std exception";
|
||
MetaCoreTrace(Impl_->LastRuntimeSyncFailure.c_str());
|
||
return;
|
||
}
|
||
}
|
||
|
||
for (auto iterator = Impl_->ObjectStates.begin(); iterator != Impl_->ObjectStates.end();) {
|
||
if (!aliveObjectIds.contains(iterator->first)) {
|
||
if (!iterator->second.MeshNode.is_empty()) {
|
||
iterator->second.MeshNode.remove_node();
|
||
}
|
||
if (!iterator->second.LightNode.is_empty()) {
|
||
iterator->second.LightNode.remove_node();
|
||
}
|
||
if (!iterator->second.RootNode.is_empty()) {
|
||
iterator->second.RootNode.remove_node();
|
||
}
|
||
iterator = Impl_->ObjectStates.erase(iterator);
|
||
} else {
|
||
++iterator;
|
||
}
|
||
}
|
||
}
|
||
|
||
void MetaCorePandaSceneBridge::ApplySceneView(const MetaCoreSceneView& sceneView) {
|
||
if (Impl_->RenderDevice == nullptr) {
|
||
return;
|
||
}
|
||
|
||
auto* editorCameraHandle = static_cast<NodePath*>(Impl_->RenderDevice->GetNativeEditorCameraHandle());
|
||
if (editorCameraHandle == nullptr || editorCameraHandle->is_empty()) {
|
||
return;
|
||
}
|
||
|
||
Impl_->SelectedObjectId = sceneView.SelectedObjectId;
|
||
|
||
editorCameraHandle->set_pos(MetaCoreToPandaPoint(sceneView.CameraPosition));
|
||
editorCameraHandle->look_at(MetaCoreToPandaPoint(sceneView.CameraTarget), MetaCoreToPandaVector(sceneView.CameraUp));
|
||
|
||
auto* cameraNode = DCAST(Camera, editorCameraHandle->node());
|
||
if (cameraNode != nullptr) {
|
||
auto* perspectiveLens = DCAST(PerspectiveLens, cameraNode->get_lens());
|
||
if (perspectiveLens != nullptr) {
|
||
const float aspect = std::max(0.001F, perspectiveLens->get_aspect_ratio());
|
||
const float vFovRad = glm::radians(sceneView.VerticalFieldOfViewDegrees);
|
||
const float hFovDeg = glm::degrees(2.0F * std::atan(aspect * std::tan(vFovRad * 0.5F)));
|
||
perspectiveLens->set_fov(LVecBase2f(std::max(0.001F, hFovDeg), std::max(0.001F, sceneView.VerticalFieldOfViewDegrees)));
|
||
}
|
||
}
|
||
}
|
||
|
||
bool MetaCorePandaSceneBridge::TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const {
|
||
if (Impl_ == nullptr || Impl_->RenderDevice == nullptr) {
|
||
return false;
|
||
}
|
||
|
||
auto* sceneRootHandle = static_cast<NodePath*>(Impl_->RenderDevice->GetNativeSceneRootHandle());
|
||
if (sceneRootHandle == nullptr || sceneRootHandle->is_empty()) {
|
||
return false;
|
||
}
|
||
|
||
const auto objectIterator = Impl_->ObjectStates.find(objectId);
|
||
if (objectIterator == Impl_->ObjectStates.end()) {
|
||
return false;
|
||
}
|
||
|
||
const NodePath& targetNode =
|
||
!objectIterator->second.RootNode.is_empty()
|
||
? objectIterator->second.RootNode
|
||
: objectIterator->second.MeshNode;
|
||
if (targetNode.is_empty()) {
|
||
return false;
|
||
}
|
||
|
||
worldMatrix = MetaCoreConvertPandaMatrixToGlm(targetNode.get_mat(*sceneRootHandle));
|
||
return true;
|
||
}
|
||
|
||
bool MetaCorePandaSceneBridge::HasRuntimeSyncFailure() const {
|
||
return Impl_ != nullptr && Impl_->RuntimeSyncFailure;
|
||
}
|
||
|
||
const std::string& MetaCorePandaSceneBridge::GetLastRuntimeSyncFailure() const {
|
||
static const std::string empty;
|
||
if (Impl_ == nullptr) {
|
||
return empty;
|
||
}
|
||
return Impl_->LastRuntimeSyncFailure;
|
||
}
|
||
|
||
} // namespace MetaCore
|