scene.cpp

cross platform rendering playground

src/scene/scene.cpp

14.36 KB
#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;
    }
}