MetaCore/Source/MetaCoreRender/Private/MetaCorePandaSceneBridge.cpp

988 lines
35 KiB
C++

#include "MetaCoreRender/MetaCorePandaSceneBridge.h"
#include "MetaCoreRender/MetaCoreRenderDevice.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreScene/MetaCoreComponents.h"
#include "MetaCoreScene/MetaCoreScene.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"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <glm/mat4x4.hpp>
#include <chrono>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <memory>
#include <unordered_map>
#include <unordered_set>
namespace MetaCore {
namespace {
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);
}
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) {
MetaCoreTrace("create_grid: begin");
try {
MetaCoreTrace("create_grid: create vertex data");
PT(GeomVertexData) vertexData = new GeomVertexData(
"MetaCoreGrid",
GeomVertexFormat::get_v3c4(),
Geom::UH_static
);
GeomVertexWriter vertexWriter(vertexData, "vertex");
GeomVertexWriter colorWriter(vertexData, "color");
MetaCoreTrace("create_grid: loop start");
constexpr int gridHalfExtent = 10;
int vertexCount = 0;
for (int lineIndex = -gridHalfExtent; lineIndex <= gridHalfExtent; ++lineIndex) {
const bool isAxisLine = (lineIndex == 0);
const LColor color(isAxisLine ? 0.42F : 0.30F, isAxisLine ? 0.45F : 0.33F, isAxisLine ? 0.50F : 0.36F, 1.0F);
// X-direction lines
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(static_cast<float>(lineIndex), 0.0F, static_cast<float>(-gridHalfExtent))));
colorWriter.add_data4(color);
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(static_cast<float>(lineIndex), 0.0F, static_cast<float>(gridHalfExtent))));
colorWriter.add_data4(color);
// Z-direction lines
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(static_cast<float>(-gridHalfExtent), 0.0F, static_cast<float>(lineIndex))));
colorWriter.add_data4(color);
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(static_cast<float>(gridHalfExtent), 0.0F, static_cast<float>(lineIndex))));
colorWriter.add_data4(color);
vertexCount += 4;
}
MetaCoreTrace("create_grid: loop end");
MetaCoreTrace("create_grid: create primitive");
PT(GeomLines) lines = new GeomLines(Geom::UH_static);
for (int i = 0; i < vertexCount; i += 2) {
lines->add_vertices(i, i + 1);
}
MetaCoreTrace("create_grid: create geom");
PT(Geom) geom = new Geom(vertexData);
geom->add_primitive(lines.p());
PT(GeomNode) geomNode = new GeomNode("MetaCoreGridNode");
geomNode->add_geom(geom);
return parentNode.attach_new_node(geomNode);
} catch (...) {
return NodePath();
}
}
NodePath MetaCoreCreateUnitCubeNode(const NodePath& parentNode) {
PT(GeomVertexData) vertexData = new GeomVertexData(
"MetaCoreUnitCube",
GeomVertexFormat::get_v3n3(),
Geom::UH_static
);
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);
}
};
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));
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);
}
PT(Geom) geom = new Geom(vertexData);
geom->add_primitive(triangles);
PT(GeomNode) geomNode = new GeomNode("MetaCoreUnitCubeNode");
geomNode->add_geom(geom);
return parentNode.attach_new_node(geomNode);
}
[[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]] NodePath MetaCoreTryLoadModelNode(
WindowFramework& windowFramework,
const NodePath& parentNode,
const std::filesystem::path& projectRoot,
const std::string& relativeSourcePath
) {
if (projectRoot.empty() || relativeSourcePath.empty()) {
return NodePath();
}
const std::filesystem::path absoluteSourcePath = projectRoot / std::filesystem::path(relativeSourcePath);
if (!std::filesystem::exists(absoluteSourcePath)) {
MetaCoreTrace(("load_model: source file missing: " + absoluteSourcePath.string()).c_str());
return NodePath();
}
const Filename pandaPath = Filename::from_os_specific(absoluteSourcePath.string());
NodePath loadedNode = windowFramework.load_model(parentNode, pandaPath);
if (loadedNode.is_empty()) {
MetaCoreTrace(("load_model: Panda3D failed to load: " + absoluteSourcePath.string()).c_str());
return NodePath();
}
MetaCoreTrace(("load_model: success: " + absoluteSourcePath.string()).c_str());
return loadedNode;
}
[[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) MetaCoreResolveBaseColorTexture(
const std::filesystem::path& projectRoot,
const std::string& relativeTexturePath
) {
if (projectRoot.empty() || relativeTexturePath.empty()) {
return MetaCoreGetDefaultBaseColorTexture();
}
const std::filesystem::path absoluteTexturePath = projectRoot / std::filesystem::path(relativeTexturePath);
if (!std::filesystem::exists(absoluteTexturePath)) {
return MetaCoreGetDefaultBaseColorTexture();
}
PT(Texture) texture = TexturePool::load_texture(Filename::from_os_specific(absoluteTexturePath.string()));
if (texture == nullptr) {
MetaCoreTrace(("load_texture: Panda3D failed to load: " + absoluteTexturePath.string()).c_str());
return MetaCoreGetDefaultBaseColorTexture();
}
return texture;
}
[[nodiscard]] PT(Texture) MetaCoreResolveTextureWithFallback(
const std::filesystem::path& projectRoot,
const std::string& relativeTexturePath,
const PT(Texture)& fallbackTexture
) {
if (projectRoot.empty() || relativeTexturePath.empty()) {
return fallbackTexture;
}
const std::filesystem::path absoluteTexturePath = projectRoot / std::filesystem::path(relativeTexturePath);
if (!std::filesystem::exists(absoluteTexturePath)) {
return fallbackTexture;
}
PT(Texture) texture = TexturePool::load_texture(Filename::from_os_specific(absoluteTexturePath.string()));
if (texture == nullptr) {
MetaCoreTrace(("load_texture: Panda3D failed to load: " + absoluteTexturePath.string()).c_str());
return fallbackTexture;
}
return texture;
}
void MetaCoreApplyBaseColorTexture(
NodePath& meshNode,
const std::filesystem::path& projectRoot,
const std::string& relativeTexturePath
) {
if (meshNode.is_empty()) {
return;
}
PT(Texture) texture = MetaCoreResolveBaseColorTexture(projectRoot, 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 std::string& relativeTexturePath
) {
if (meshNode.is_empty()) {
return;
}
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
projectRoot,
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 std::string& relativeTexturePath
) {
if (meshNode.is_empty()) {
return;
}
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
projectRoot,
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 std::string& relativeTexturePath
) {
if (meshNode.is_empty()) {
return;
}
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
projectRoot,
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 std::string& relativeTexturePath
) {
if (meshNode.is_empty()) {
return;
}
PT(Texture) texture = MetaCoreResolveTextureWithFallback(
projectRoot,
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)
);
}
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{};
std::string SourceModelPath{};
};
MetaCoreRenderDevice* RenderDevice = nullptr;
NodePath GridNode{};
NodePath AmbientLightNode{};
std::filesystem::path ProjectRootPath{};
MetaCoreId SelectedObjectId = 0;
std::unordered_map<MetaCoreId, MetaCorePandaObjectState> ObjectStates{};
};
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);
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.RootNode.is_empty()) {
objectState.RootNode.remove_node();
}
}
Impl_->ObjectStates.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;
}
void MetaCorePandaSceneBridge::SyncScene(const MetaCoreScene& scene) {
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()) {
aliveObjectIds.insert(gameObject.Id);
auto [iterator, inserted] = Impl_->ObjectStates.try_emplace(gameObject.Id);
MetaCorePandaSceneBridgeImpl::MetaCorePandaObjectState& objectState = iterator->second;
if (inserted || objectState.RootNode.is_empty()) {
PT(PandaNode) rootNode = new PandaNode(gameObject.Name);
objectState.RootNode = sceneRootHandle->attach_new_node(rootNode);
}
objectState.RootNode.set_name(gameObject.Name);
MetaCoreApplyTransformToPandaNode(gameObject.Transform, objectState.RootNode);
if (gameObject.ParentId != 0) {
const auto parentIterator = Impl_->ObjectStates.find(gameObject.ParentId);
if (parentIterator != Impl_->ObjectStates.end() && !parentIterator->second.RootNode.is_empty()) {
objectState.RootNode.reparent_to(parentIterator->second.RootNode);
} else {
objectState.RootNode.reparent_to(*sceneRootHandle);
}
} else {
objectState.RootNode.reparent_to(*sceneRootHandle);
}
if (gameObject.MeshRenderer.has_value()) {
const bool requiresMeshRebuild =
objectState.MeshNode.is_empty() ||
objectState.MeshSource != gameObject.MeshRenderer->MeshSource ||
(gameObject.MeshRenderer->MeshSource == MetaCoreMeshSourceKind::Builtin &&
objectState.BuiltinMesh != gameObject.MeshRenderer->BuiltinMesh) ||
(gameObject.MeshRenderer->MeshSource == MetaCoreMeshSourceKind::Asset &&
(objectState.MeshAssetGuid != gameObject.MeshRenderer->MeshAssetGuid ||
objectState.SourceModelPath != gameObject.MeshRenderer->SourceModelPath));
if (requiresMeshRebuild && !objectState.MeshNode.is_empty()) {
objectState.MeshNode.remove_node();
objectState.MeshNode = NodePath();
}
if (requiresMeshRebuild) {
if (gameObject.MeshRenderer->MeshSource == MetaCoreMeshSourceKind::Builtin) {
objectState.MeshNode = MetaCoreCreateUnitCubeNode(objectState.RootNode);
} else {
objectState.MeshNode = MetaCoreTryLoadModelNode(
*windowFrameworkHandle,
objectState.RootNode,
Impl_->ProjectRootPath,
gameObject.MeshRenderer->SourceModelPath
);
if (objectState.MeshNode.is_empty()) {
objectState.MeshNode = MetaCoreCreateUnitCubeNode(objectState.RootNode);
}
}
objectState.MeshSource = gameObject.MeshRenderer->MeshSource;
objectState.BuiltinMesh = gameObject.MeshRenderer->BuiltinMesh;
objectState.MeshAssetGuid = gameObject.MeshRenderer->MeshAssetGuid;
objectState.SourceModelPath = gameObject.MeshRenderer->SourceModelPath;
}
if (!objectState.MeshNode.is_empty()) {
objectState.MeshNode.set_material_off(1);
objectState.MeshNode.set_light_off(1);
objectState.MeshNode.set_two_sided(gameObject.MeshRenderer->DoubleSided);
objectState.MeshNode.set_color(
gameObject.MeshRenderer->BaseColor.r,
gameObject.MeshRenderer->BaseColor.g,
gameObject.MeshRenderer->BaseColor.b,
1.0F
);
MetaCoreApplyBaseColorTexture(
objectState.MeshNode,
Impl_->ProjectRootPath,
gameObject.MeshRenderer->BaseColorTexturePath
);
MetaCoreApplyMetallicRoughnessTexture(
objectState.MeshNode,
Impl_->ProjectRootPath,
gameObject.MeshRenderer->MetallicRoughnessTexturePath
);
MetaCoreApplyNormalTexture(
objectState.MeshNode,
Impl_->ProjectRootPath,
gameObject.MeshRenderer->NormalTexturePath
);
MetaCoreApplyEmissionTexture(
objectState.MeshNode,
Impl_->ProjectRootPath,
gameObject.MeshRenderer->EmissiveTexturePath
);
MetaCoreApplyAoTexture(
objectState.MeshNode,
Impl_->ProjectRootPath,
gameObject.MeshRenderer->AoTexturePath
);
if (gameObject.MeshRenderer->MeshSource == MetaCoreMeshSourceKind::Asset) {
MetaCoreApplySimplePbrPreview(objectState.MeshNode);
MetaCoreApplySimplePbrMaterialInputs(objectState.MeshNode, *gameObject.MeshRenderer);
auto* editorCameraHandle = static_cast<NodePath*>(Impl_->RenderDevice->GetNativeEditorCameraHandle());
if (editorCameraHandle != nullptr) {
MetaCoreApplySimplePbrViewInputs(objectState.MeshNode, *sceneRootHandle, *editorCameraHandle);
}
} else {
objectState.MeshNode.set_shader_off(1);
}
if (gameObject.MeshRenderer->AlphaMode == MetaCoreMeshAlphaMode::Blend) {
objectState.MeshNode.set_transparency(TransparencyAttrib::M_alpha);
} else if (gameObject.MeshRenderer->AlphaMode == MetaCoreMeshAlphaMode::Mask) {
objectState.MeshNode.set_transparency(TransparencyAttrib::M_binary);
} else {
objectState.MeshNode.clear_transparency();
}
if (gameObject.MeshRenderer->Visible) {
objectState.MeshNode.show();
} else {
objectState.MeshNode.hide();
}
if (gameObject.Id == 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();
}
if (gameObject.Light.has_value()) {
if (objectState.LightNode.is_empty()) {
PT(DirectionalLight) directionalLight = new DirectionalLight(gameObject.Name);
objectState.LightNode = objectState.RootNode.attach_new_node(directionalLight);
sceneRootHandle->set_light(objectState.LightNode);
}
auto* directionalLight = DCAST(DirectionalLight, objectState.LightNode.node());
if (directionalLight != nullptr) {
directionalLight->set_color(LColor(
gameObject.Light->Color.r * gameObject.Light->Intensity,
gameObject.Light->Color.g * gameObject.Light->Intensity,
gameObject.Light->Color.b * gameObject.Light->Intensity,
1.0F
));
}
} else if (!objectState.LightNode.is_empty()) {
sceneRootHandle->clear_light(objectState.LightNode);
objectState.LightNode.remove_node();
objectState.LightNode = NodePath();
}
}
for (auto iterator = Impl_->ObjectStates.begin(); iterator != Impl_->ObjectStates.end();) {
if (!aliveObjectIds.contains(iterator->first)) {
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() || objectIterator->second.RootNode.is_empty()) {
return false;
}
worldMatrix = MetaCoreConvertPandaMatrixToGlm(objectIterator->second.RootNode.get_mat(*sceneRootHandle));
return true;
}
} // namespace MetaCore