#include "scene.h"
#include "asset/asset.h"
#include "core/math_rtm.h"
#include "rtm/quatf.h"
static bool scene_parent_is_valid(const SceneGraph &scene, int parent_idx) {
return parent_idx < 0 || scene.is_node_alive((u32)parent_idx);
}
static void scene_remove_from_parent_chain(SceneGraph &scene, u32 node) {
int old_parent = scene.parent[node];
if (old_parent >= 0) {
if (scene.first_child[old_parent] == node) {
scene.first_child[old_parent] = scene.next_sibling[node];
return;
}
for (u32 sib = scene.first_child[old_parent]; sib != SCENE_INVALID; sib = scene.next_sibling[sib]) {
if (scene.next_sibling[sib] == node) {
scene.next_sibling[sib] = scene.next_sibling[node];
return;
}
}
} else {
for (usize i = 0; i < scene.roots.size(); ++i) {
if (scene.roots[i] == node) {
scene.roots.erase(scene.roots.begin() + i);
return;
}
}
}
}
u32 SceneGraph::add_node(int parent_idx, const TRS &local) {
if (!scene_parent_is_valid(*this, parent_idx)) {
return SCENE_INVALID;
}
u32 idx = SCENE_INVALID;
if (!free_nodes.empty()) {
idx = free_nodes.back();
free_nodes.pop_back();
translations[idx] = local.translation;
rotations[idx] = local.rotation;
scales[idx] = local.scale;
locals[idx] = trs_to_mat4(local);
worlds[idx] = identity();
parent[idx] = parent_idx;
first_child[idx] = SCENE_INVALID;
next_sibling[idx] = SCENE_INVALID;
alive[idx] = 1;
asset[idx] = AssetHandle{};
asset_index[idx] = 0;
instance_base[idx] = 0;
instance_count[idx] = 0;
} else {
idx = node_count++;
translations.push_back(local.translation);
rotations.push_back(local.rotation);
scales.push_back(local.scale);
locals.push_back(trs_to_mat4(local));
worlds.push_back(identity());
parent.push_back(parent_idx);
first_child.push_back(SCENE_INVALID);
next_sibling.push_back(SCENE_INVALID);
alive.push_back(1);
asset.push_back(AssetHandle{});
asset_index.push_back(0);
instance_base.push_back(0);
instance_count.push_back(0);
}
if (parent_idx >= 0) {
next_sibling[idx] = first_child[parent_idx];
first_child[parent_idx] = idx;
} else {
roots.push_back(idx);
}
return idx;
}
u32 SceneGraph::add_node(int parent_idx, const mat4 &local) {
return add_node(parent_idx, mat4_to_trs(local));
}
bool SceneGraph::delete_node(u32 node) {
if (!is_node_alive(node)) {
return false;
}
scene_remove_from_parent_chain(*this, node);
std::vector<u32> stack;
stack.push_back(node);
while (!stack.empty()) {
u32 current = stack.back();
stack.pop_back();
for (u32 child = first_child[current]; child != SCENE_INVALID; child = next_sibling[child]) {
stack.push_back(child);
}
parent[current] = -1;
first_child[current] = SCENE_INVALID;
next_sibling[current] = SCENE_INVALID;
locals[current] = identity();
worlds[current] = identity();
alive[current] = 0;
asset[current] = AssetHandle{};
asset_index[current] = 0;
instance_base[current] = 0;
instance_count[current] = 0;
free_nodes.push_back(current);
}
return true;
}
bool SceneGraph::is_node_alive(u32 node) const {
return node < node_count && node < alive.size() && alive[node] != 0;
}
bool SceneGraph::is_descendant(u32 node, u32 ancestor) const {
if (!is_node_alive(node) || !is_node_alive(ancestor)) {
return false;
}
for (int p = parent[node]; p >= 0; p = parent[p]) {
if ((u32)p == ancestor) {
return true;
}
}
return false;
}
bool SceneGraph::reparent(u32 node, int new_parent) {
if (!is_node_alive(node) || !scene_parent_is_valid(*this, new_parent)) {
return false;
}
if (new_parent >= 0 && ((u32)new_parent == node || is_descendant((u32)new_parent, node))) {
return false;
}
int old_parent = parent[node];
if (old_parent == new_parent) {
return true;
}
scene_remove_from_parent_chain(*this, node);
parent[node] = new_parent;
if (new_parent >= 0) {
next_sibling[node] = first_child[new_parent];
first_child[new_parent] = node;
} else {
next_sibling[node] = SCENE_INVALID;
roots.push_back(node);
}
return true;
}
bool SceneGraph::reparent_keep_world(u32 node, int new_parent) {
if (!is_node_alive(node) || !scene_parent_is_valid(*this, new_parent)) {
return false;
}
if (new_parent >= 0 && ((u32)new_parent == node || is_descendant((u32)new_parent, node))) {
return false;
}
update_world_transforms();
mat4 old_world = worlds[node];
if (!reparent(node, new_parent)) {
return false;
}
mat4 new_local = old_world;
if (new_parent >= 0) {
new_local = old_world * inverse(worlds[new_parent]);
}
set_local_matrix(node, new_local);
return true;
}
void SceneGraph::set_local_trs(u32 index, const TRS &trs) {
if (!is_node_alive(index)) {
return;
}
translations[index] = trs.translation;
rotations[index] = trs.rotation;
scales[index] = trs.scale;
locals[index] = trs_to_mat4(trs);
}
void SceneGraph::set_local_matrix(u32 index, const mat4 &m) {
if (!is_node_alive(index)) {
return;
}
TRS trs = mat4_to_trs(m);
translations[index] = trs.translation;
rotations[index] = trs.rotation;
scales[index] = trs.scale;
locals[index] = m;
}
void SceneGraph::set_translation(u32 index, const vec3 &translation) {
if (!is_node_alive(index)) {
return;
}
// should remove this
// no-op guard:
// skip the local-transform write when the value hasn't
// actually changed (e.g. idle camera),
if (translations[index].x == translation.x && translations[index].y == translation.y &&
translations[index].z == translation.z) {
return;
}
TRS trs = get_local_trs(index);
trs.translation = translation;
set_local_trs(index, trs);
}
void SceneGraph::set_rotation(u32 index, const rtm::quatf &rotation) {
if (!is_node_alive(index)) {
return;
}
TRS trs = get_local_trs(index);
trs.rotation = rotation;
set_local_trs(index, trs);
}
void SceneGraph::set_scale(u32 index, const vec3 &scale) {
if (!is_node_alive(index)) {
return;
}
TRS trs = get_local_trs(index);
trs.scale = scale;
set_local_trs(index, trs);
}
void SceneGraph::update_world_transforms() {
std::vector<u32> stack;
stack.reserve(node_count);
for (u32 root : roots) {
if (!is_node_alive(root)) {
continue;
}
worlds[root] = locals[root];
for (u32 child = first_child[root]; child != SCENE_INVALID; child = next_sibling[child]) {
stack.push_back(child);
}
}
while (!stack.empty()) {
u32 node = stack.back();
stack.pop_back();
if (!is_node_alive(node)) {
continue;
}
worlds[node] = locals[node] * worlds[parent[node]];
for (u32 child = first_child[node]; child != SCENE_INVALID; child = next_sibling[child]) {
stack.push_back(child);
}
}
}
TRS SceneGraph::get_local_trs(u32 index) const {
if (!is_node_alive(index)) {
return {};
}
return {translations[index], rotations[index], scales[index]};
}
mat4 SceneGraph::get_local_matrix(u32 index) const {
if (!is_node_alive(index)) {
return identity();
}
return locals[index];
}
mat4 SceneGraph::get_world(u32 index) const {
if (!is_node_alive(index)) {
return identity();
}
return worlds[index];
}
u32 Scene::instantiate(AssetManager &mgr, AssetHandle handle, int parent_idx) {
if (!handle.valid()) {
return SCENE_INVALID;
}
if (!scene_parent_is_valid(scene_graph, parent_idx)) {
return SCENE_INVALID;
}
const AssetScene *asset = mgr.get(handle);
if (!asset) {
return SCENE_INVALID;
}
SceneGraph &sg = scene_graph;
u32 n = (u32)asset->nodes.size();
u32 base = sg.node_count;
u32 end = base + n;
sg.translations.resize(end);
sg.rotations.resize(end);
sg.scales.resize(end);
sg.locals.resize(end);
sg.worlds.resize(end);
sg.parent.resize(end, -1);
sg.first_child.resize(end, SCENE_INVALID);
sg.next_sibling.resize(end, SCENE_INVALID);
sg.alive.resize(end, 1);
sg.asset.resize(end);
sg.asset_index.resize(end);
sg.instance_base.resize(end);
sg.instance_count.resize(end);
sg.node_count = end;
for (u32 i = 0; i < n; ++i) {
const AssetNode &src = asset->nodes[i];
u32 dst = base + i;
int sp;
if (src.parent >= 0) {
sp = (int)(base + src.parent);
} else {
sp = parent_idx;
}
TRS trs = mat4_to_trs(src.local);
sg.translations[dst] = trs.translation;
sg.rotations[dst] = trs.rotation;
sg.scales[dst] = trs.scale;
sg.locals[dst] = src.local;
sg.parent[dst] = sp;
sg.asset[dst] = handle;
sg.asset_index[dst] = i;
if (sp >= 0) {
sg.next_sibling[dst] = sg.first_child[sp];
sg.first_child[sp] = dst;
} else {
sg.roots.push_back(dst);
}
if (src.mesh != ASSET_INVALID) {
u32 count = src.instance_count;
u32 inst_base = (u32)instance_locals.size();
if (count > 0) {
instance_locals.resize(inst_base + count);
} else {
instance_locals.resize(inst_base + 1);
}
if (count > 0) {
for (u32 k = 0; k < count; ++k) {
instance_locals[inst_base + k] = asset->instance_locals[src.first_instance + k];
}
} else {
instance_locals[inst_base] = TRS{};
}
sg.instance_base[dst] = inst_base;
sg.instance_count[dst] = count;
mesh_nodes.push_back(dst);
}
if (src.camera >= 0 && src.camera < (i32)asset->cameras.size()) {
const AssetCamera &ac = asset->cameras[src.camera];
Camera cam;
cam.node = dst;
if (ac.type == AssetCamera::Perspective) {
cam.fov_y_deg = degrees(ac.yfov);
cam.near_plane = ac.znear;
cam.far_plane = ac.zfar;
if (ac.aspect_ratio > 0.0f) {
cam.aspect = ac.aspect_ratio;
}
}
cameras.push_back(cam);
}
if (src.light >= 0 && src.light < (i32)asset->lights.size()) {
const AssetLight &al = asset->lights[src.light];
if (al.type == AssetLight::Directional) {
Light dl;
dl.node = dst;
dl.color = al.color;
dl.intensity = al.intensity;
directional_lights.push_back(dl);
} else {
PointLight pl;
pl.node = dst;
pl.color = al.color;
pl.intensity = al.intensity;
if (al.range > 0.0f) {
pl.range = al.range;
} else {
pl.range = 10.0f;
}
point_lights.push_back(pl);
}
}
}
sg.update_world_transforms();
return base;
}
const AssetScene *Scene::node_source(const AssetManager &mgr, u32 node) const {
const SceneGraph &sg = scene_graph;
if (!sg.is_node_alive(node)) {
return nullptr;
}
if (!sg.asset[node].valid()) {
return nullptr;
}
const AssetScene *asset = mgr.get(sg.asset[node]);
if (!asset || sg.asset_index[node] >= asset->nodes.size()) {
return nullptr;
}
return asset;
}
const char *Scene::node_name(const AssetManager &mgr, u32 node) const {
const AssetScene *asset = node_source(mgr, node);
if (!asset) {
return nullptr;
}
const std::string &name = asset->node_names[scene_graph.asset_index[node]];
return name.empty() ? nullptr : name.c_str();
}
mat4 Scene::instance_world(u32 node, u32 k) const {
const SceneGraph &sg = scene_graph;
if (node >= sg.node_count || !sg.is_node_alive(node)) {
return identity();
}
u32 base = sg.instance_base[node];
u32 count = sg.instance_count[node];
if (k >= count || base + k >= (u32)instance_locals.size()) {
return identity();
}
int p = sg.parent[node];
mat4 parent_world = (p >= 0) ? sg.worlds[p] : identity();
return trs_to_mat4(instance_locals[base + k]) * sg.locals[node] * parent_world;
}
void Scene::evaluate() {
scene_graph.update_world_transforms();
const SceneGraph &sg = scene_graph;
for (usize i = 0; i < cameras.size(); ++i) {
Camera &cam = cameras[i];
if (cam.node == NODE_INVALID || !sg.is_node_alive(cam.node)) {
continue;
}
TRS trs = mat4_to_trs(sg.get_world(cam.node));
cam.position = trs.translation;
cam.view = look_at(cam.position, cam.position + cam.forward(), vec3(0.0f, 0.0f, 1.0f));
cam.proj = perspective(radians(cam.fov_y_deg), cam.aspect, cam.near_plane, cam.far_plane);
cam.view_proj = cam.view * cam.proj;
cam.inv_view = inverse(cam.view);
cam.inv_proj = inverse(cam.proj);
cam.inv_view_proj = inverse(cam.view_proj);
}
for (usize i = 0; i < directional_lights.size(); ++i) {
Light &dl = directional_lights[i];
if (dl.node == NODE_INVALID || !sg.is_node_alive(dl.node)) {
continue;
}
TRS trs = mat4_to_trs(sg.get_world(dl.node));
rtm::vector4f local_fwd = rtm::vector_set(1.0f, 0.0f, 0.0f, 0.0f);
rtm::vector4f world_fwd = rtm::quat_mul_vector3(local_fwd, trs.rotation.q);
dl.direction = vec3::from_vec4(world_fwd).normalized();
}
for (usize i = 0; i < point_lights.size(); ++i) {
PointLight &pl = point_lights[i];
if (pl.node == NODE_INVALID || !sg.is_node_alive(pl.node)) {
continue;
}
TRS trs = mat4_to_trs(sg.get_world(pl.node));
pl.position = trs.translation;
}
}