MetaCore/Source/MetaCoreRender/Private/MetaCorePandaSceneBridge.cpp

1892 lines
77 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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