src/scene_node.c

Tue, 07 Apr 2026 20:33:29 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 07 Apr 2026 20:33:29 +0200
changeset 305
67a590cf2d15
parent 303
21ff357e773c
permissions
-rw-r--r--

implement add/remove to node dictionary

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * Copyright 2023 Mike Becker. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "ascension/scene_node.h"
#include "ascension/context.h"
#include "ascension/error.h"

#include <cx/tree.h>
#include <cx/linked_list.h>
#include <cx/hash_map.h>
#include <cx/printf.h>

#include <assert.h>

AscSceneNodeDict *asc_scene_node_dict_create(void) {
    return cxHashMapCreate(cxDefaultAllocator, sizeof(AscSceneNodeDictEntry), 0);
}

void asc_scene_node_dict_free(AscSceneNodeDict *dict) {
    cxMapFree(dict);
}

static void asc_scene_node_dict_add(AscSceneNodeDict *dict, AscSceneNode *node) {
    if (dict == NULL) return;

    // set a reference to the dict in the node
    node->dict = dict;

    // also add child nodes which have a dedicated name
    AscSceneNode *child = node->children;
    while (child != NULL) {
        asc_scene_node_dict_add(dict, child);
        child = child->next;
    }

    if (!asc_test_flag(node->flags, ASC_SCENE_NODE_HAS_NAME)) return;

    AscSceneNodeDictEntry *entry = cxMapGet(dict, node->name);
    if (entry == NULL) {
        entry = cxMapEmplace(dict, node->name);
        entry->count = 1;
        entry->node = cxMallocDefault(sizeof(AscSceneNode*));
        entry->node[0] = node;
        asc_dprintf("Dict add: %" CX_PRIstr,
                CX_SFMT(node->name));
        return;
    }
    if (cxReallocateArrayDefault(&entry->node, entry->count + 1, sizeof(AscSceneNode*))) {
        asc_error("Reallocating dict entry for node %" CX_PRIstr " failed.",
                CX_SFMT(node->name));
    }
    entry->node[entry->count++] = node;
    asc_dprintf("Dict add: %" CX_PRIstr " (now %z entries).",
        CX_SFMT(node->name), entry->count);
}

static void asc_scene_node_dict_remove(AscSceneNode *node) {
    // fast exit, node (and its children) do not belong to a dict
    if (node->dict == NULL) return;

    // remove the children, first (use standard recursion, depth will be manageable)
    // TODO: think about adding a flag for indicating if ANY child has a name, so that we can skip subtrees here
    AscSceneNode *child = node->children;
    while (child != NULL) {
        asc_scene_node_dict_remove(child);
        child = child->next;
    }

    if (!asc_test_flag(node->flags, ASC_SCENE_NODE_HAS_NAME)) return;

    AscSceneNodeDictEntry *entry = cxMapGet(node->dict, node->name);
    if (entry == NULL) return;

    // last node with that name, remove node
    if (entry->count == 1) {
        if (*entry->node == node) {
            cxMapRemove(node->dict, node->name);
            asc_dprintf("Dict remove: %" CX_PRIstr,
                CX_SFMT(node->name));
        } else {
            asc_error("BUG: Node %" CX_PRIstr " not found in its own dict.",
                CX_SFMT(node->name));
        }
        return;
    }

    // find index of the particular node
    unsigned idx;
    for (idx = 0 ; idx < entry->count ; idx++) {
        if (entry->node[idx] == node) break;
    }
    if (idx == entry->count) {
        asc_error("BUG: Node %" CX_PRIstr " not found in its own dict.",
                CX_SFMT(node->name));
        return;
    }
    entry->node[idx] = entry->node[--entry->count];
    if (cxReallocateArrayDefault(&entry->node, entry->count, sizeof(AscSceneNode*))) {
        asc_error("Shrinking dict for node %" CX_PRIstr " failed.",
                CX_SFMT(node->name));
    }
    asc_dprintf("Dict remove: %" CX_PRIstr " (%z others left).",
                CX_SFMT(node->name), entry->count);
}

static CxTreeIterator asc_scene_node_iterator(
        AscSceneNode *node,
        bool visit_on_exit
) {
    return cx_tree_iterator(
            node, visit_on_exit,
            offsetof(AscSceneNode, children),
            offsetof(AscSceneNode, next)
    );
}

AscSceneNode *asc_scene_node_empty(void) {
    AscSceneNode *node = cxZallocDefault(sizeof(AscSceneNode));
    asc_scene_node_init(node, .render_group = ASC_RENDER_GROUP_NONE);
    return node;
}

static void asc_scene_node_destroy(AscSceneNode *node) {
    cxListFree(node->behaviors);
    if (node->user_data_free_func != NULL) {
        node->user_data_free_func((void*)node->user_data_allocator, node->user_data);
    }
    if (node->destroy_func != NULL) {
        node->destroy_func(node);
    }
    if (node->name.ptr != NULL) {
        asc_dprintf("Destroy node: %"CX_PRIstr, CX_SFMT(node->name));
        cx_strfree(&node->name);
    }
}

void asc_scene_node_free(AscSceneNode *node) {
    if (node == NULL) return;

    // remove this node from its parent
    asc_scene_node_remove(node);

    // free the entire subtree
    CxTreeIterator iter = asc_scene_node_iterator(node, true);
    cx_foreach(AscSceneNode*, child, iter) {
        if (!iter.exiting) continue;
        asc_scene_node_destroy(child);
        cxFreeDefault(child);
    }
}

void asc_scene_node_init_(AscSceneNode *node, struct asc_scene_node_init_args args) {
    if (args.name != NULL) {
        asc_scene_node_name(node, args.name);
    }
    node->render_group = args.render_group;
    if (args.render_group != ASC_RENDER_GROUP_NONE) {
        assert(args.update_func != NULL);
        assert(args.draw_func != NULL);
        assert(args.destroy_func != NULL);
    }
    node->update_func = args.update_func;
    node->destroy_func = args.destroy_func;
    node->draw_func = args.draw_func;

    if (args.pos2d.x != 0 || args.pos2d.y != 0) {
        node->position = ASC_VEC3F(args.pos2d.x, args.pos2d.y, ASC_SCENE_2D_DEPTH_OFFSET);
    } else if (args.pos3d.x != 0 || args.pos3d.y != 0 || args.pos3d.z != 0) {
        node->position = args.pos3d;
    }

    if (args.origin2d.x != 0 || args.origin2d.y != 0) {
        node->origin = ASC_VEC3F(args.origin2d.x, args.origin2d.y, 0.f);
    } else if (args.origin3d.x != 0 || args.origin3d.y != 0 || args.origin3d.z != 0) {
        node->origin = args.origin3d;
    }

    if (args.scale2d.width != 0 && args.scale2d.height != 0) {
        node->scale = ASC_VEC3F(args.scale2d.width, args.scale2d.height, 1.f);
    } else if (args.scale3d.x != 0 && args.scale3d.height != 0 && args.scale3d.depth != 0) {
        node->scale = args.scale3d;
    } else {
        node->scale = ASC_VEC3F_1;
    }

    if (asc_memcmpz(args.rotation, ASC_TRANSFORM_SIZE)) {
        asc_mat4f_unit(node->rotation);
    } else {
        asc_transform_copy(node->rotation, args.rotation);
    }

    asc_scene_node_update(node);
}

void asc_scene_node_calculate_transform(AscSceneNode *node) {
    asc_transform temp, temp2;

    // move the point of origin
    asc_transform_translate3f(temp, asc_vec3f_neg(node->origin));

    // apply the rotation
    asc_transform_apply(node->transform, node->rotation, temp);

    // apply the scale
    asc_transform_scale3f(temp, node->scale);
    asc_transform_apply(temp2, temp, node->transform);

    // apply the translation
    asc_transform_translate3f(temp, node->position);
    asc_transform_apply(node->transform, temp, temp2);
}

void asc_scene_node_name(AscSceneNode *node, const char *name) {
    asc_scene_node_dict_remove(node);
    cx_strfree(&node->name);
    if (name == NULL) {
        asc_clear_flag(node->flags, ASC_SCENE_NODE_HAS_NAME);
    } else {
        asc_set_flag(node->flags, ASC_SCENE_NODE_HAS_NAME);
        node->name = cx_strdup(name);
        asc_scene_node_dict_add(node->dict, node);
    }
}

cxstring asc_scene_node_get_name(AscSceneNode *node) {
    if (node->name.ptr != NULL) return cx_strcast(node->name);

    AscSceneNode *parent = node->parent;
    while (parent != NULL && parent->name.ptr == NULL) {
        parent = parent->parent;
    }
    if (parent == NULL) {
        cx_sprintf(&node->name.ptr, &node->name.length,
            "%"PRIxPTR " - w/o named parent", (uintptr_t)node);
    } else {
        cx_sprintf(&node->name.ptr, &node->name.length,
            "%"PRIxPTR " - child of %"CX_PRIstr, (uintptr_t)node, CX_SFMT(parent->name));
    }

    return cx_strcast(node->name);
}

void asc_scene_node_add(AscSceneNode * restrict parent, AscSceneNode * restrict node) {
    asc_scene_node_dict_add(parent->dict, node);
    cx_tree_add(
            parent, node,
            offsetof(AscSceneNode, parent),
            offsetof(AscSceneNode, children),
            offsetof(AscSceneNode, last_child),
            offsetof(AscSceneNode, prev),
            offsetof(AscSceneNode, next)
    );
    asc_scene_node_update_transform(node);
}

void asc_scene_node_remove(AscSceneNode *node) {
    asc_scene_node_dict_remove(node);
    cx_tree_remove(
            node,
            offsetof(AscSceneNode, parent),
            offsetof(AscSceneNode, children),
            offsetof(AscSceneNode, last_child),
            offsetof(AscSceneNode, prev),
            offsetof(AscSceneNode, next)
    );
    asc_scene_node_update_transform(node);
}

void asc_scene_node_update(AscSceneNode *node) {
    asc_set_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS);
}

void asc_scene_node_update_transform(AscSceneNode *node) {
    // fast skip if node is already marked
    if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
        return;
    }

    CxTreeIterator iter = asc_scene_node_iterator(node, false);
    cx_foreach(AscSceneNode*, n, iter) {
        if (asc_test_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) {
            cxTreeIteratorContinue(iter);
        }
        asc_set_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM);
    }
}

void *asc_scene_node_allocate_data(AscSceneNode *node, size_t n) {
    if (node->user_data != NULL) {
        asc_wprintf("Node %"CX_PRIstr" already has user data which is now destroyed!", CX_SFMT(node->name));
        if (node->user_data_free_func != NULL) {
            node->user_data_free_func((void*)node->user_data_allocator, node->user_data);
        }
    }
    node->user_data = cxZallocDefault(n);
    node->user_data_allocator = cxDefaultAllocator;
    node->user_data_free_func = (cx_destructor_func2) cxFree;
    return node->user_data;
}

mercurial