#include <ultra64.h>
#include <macros.h>
#include <stdio.h>
#include "gd_types.h"
#include "bad_declarations.h"

#include "gd_main.h"
#include "draw_objects.h"
#include "objects.h"
#include "particles.h"
#include "dynlist_proc.h"
#include "old_menu.h"
#include "debug_utils.h"
#include "joints.h"
#include "skin.h"
#include "gd_math.h"
#include "shape_helper.h"
#include "renderer.h"

/**
 * @file dynlist_proc.c
 *
 * Functions for parsing and processing Goddard's DynList object format.
 * It also has utility functions for abstracting at runtime over the various
 * flavors of `GdObj`s.
 */

// constants
/// Size of the dynamic object name buffer
#define DYNOBJ_NAME_SIZE 8
/// Total number of dynamic `GdObj`s that can be created
#define DYNOBJ_LIST_SIZE 3000
/// Maximum number of verticies supported when adding vertices node to an `ObjShape`
#define VTX_BUF_SIZE 3000

// types
/// Information about a dynamically created `GdObj`
struct DynObjInfo {
    char name[DYNOBJ_NAME_SIZE];
    struct GdObj *obj;
    s32 num;
    s32 unk;
};
/// @name DynList Accessors
/// Accessor marcos for easy interpretation of data in a `DynList` packet
///@{
#define Dyn1AsInt(dyn) ((dyn)->w1.word)
#define Dyn1AsPtr(dyn) ((dyn)->w1.ptr)
#define Dyn1AsStr(dyn) ((dyn)->w1.str)
#define Dyn1AsID(dyn) ((DynId)((dyn)->w1.ptr))

#define Dyn2AsInt(dyn) ((dyn)->w2.word)
#define Dyn2AsPtr(dyn) ((dyn)->w2.ptr)
#define Dyn2AsStr(dyn) ((dyn)->w2.str)
#define Dyn2AsID(dyn) ((DynId)((dyn)->w2.ptr))

#define DynVec(dyn) (&(dyn)->vec)
#define DynVecX(dyn) ((dyn)->vec.x)
#define DynVecY(dyn) ((dyn)->vec.y)
#define DynVecZ(dyn) ((dyn)->vec.z)
///@}

// data
static struct DynObjInfo *sGdDynObjList = NULL; // @ 801A8250; info for all loaded/made dynobjs
static struct GdObj *sDynListCurObj = NULL;     // @ 801A8254
static struct GdPlaneF sGdNullPlaneF = {        // @ 801A8258
    { 0.0, 0.0, 0.0 },
    { 0.0, 0.0, 0.0 }
};
static s32 sGdDynObjIdIsInt = FALSE; // @ 801A8270; str (0) or int (1) for Dyn Obj ID

// bss
static char sIntDynIdBuffer[DYNOBJ_NAME_SIZE]; ///< buffer for returning formated string from
                                               ///< `print_int_dynid()`
static struct DynObjInfo sNullDynObjInfo;      // @ 801B9F08
static char sDynIdBuf[DYNOBJ_NAME_SIZE];       // @ 801B9F20; small buf for printing dynid to?
static s32
    sUnnamedObjCount;      // @ 801B9F28; used to print empty string ids (not NULL char *) to sDynIdBuf
static s32 sLoadedDynObjs; // @ 801B9F2C; total loaded dynobjs
static struct DynObjInfo *sDynListCurInfo; // @ 801B9F30; info for most recently added object
static struct DynObjInfo
    *sParentNetInfo; ///< Information for `ObjNet` made by `d_add_net_with_subgroup()`
static struct DynObjInfo *sStashedDynObjInfo; // @ 801B9F38
static struct GdObj *sStashedDynObj;          // @ 801B9F3C
static s32 sDynNetCount;                      // @ 801B9F40
static char sDynNetIdBuf[0x20];               // @ 801B9F48
static char sBackBuf[0x100];                  // @ 801B9F68

// necessary foreward declarations
void d_add_net_with_subgroup(s32, DynId);
void d_end_net_subgroup(DynId);
void d_attach_joint_to_net(s32, DynId);
void d_addto_group(DynId);
void d_link_with(DynId);
void d_link_with_ptr(void *);
void d_set_normal(f32, f32, f32);
void d_make_vertex(struct GdVec3f *);
void d_set_rotation(f32, f32, f32);
void d_center_of_gravity(f32, f32, f32);
void d_set_shape_offset(f32, f32, f32);
void d_clear_flags(s32);
void d_attach(DynId);
void d_attach_to(s32, struct GdObj *);
void d_attachto_dynid(s32, DynId);
void d_set_att_offset(const struct GdVec3f *);
void d_set_nodegroup(DynId);
void d_set_matgroup(DynId);
void d_set_skinshape(DynId);
void d_set_planegroup(DynId);
void d_set_shapeptr(DynId);
void d_friction(f32, f32, f32);
void d_set_spring(f32);
void d_set_ambient(f32, f32, f32);
void d_set_control_type(s32);
void d_set_skin_weight(s32, f32);
void d_set_id(s32);
void d_set_material(void *, s32);
void d_map_materials(DynId);
void d_map_vertices(DynId);
void d_set_texture_st(f32, f32);
void d_use_texture(void *);
void d_make_netfromshapeid(DynId);
void d_make_netfromshape_ptrptr(struct ObjShape **);
void add_to_dynobj_list(struct GdObj *, DynId);

/**
 * Store the active dynamic `GdObj` into a one object stash.
 */
void push_dynobj_stash(void) {
    sStashedDynObjInfo = sDynListCurInfo;
    sStashedDynObj = sDynListCurObj;
}

/**
 * Set the stashed `GdObj` as the active dynamic `GdObj`.
 */
void pop_dynobj_stash(void) {
    sDynListCurObj = sStashedDynObj;
    sDynListCurInfo = sStashedDynObjInfo;
}

/**
 * Reset dynlist related variables to a starting state
 */
void reset_dynlist(void) {
    sUnnamedObjCount = 0;
    sLoadedDynObjs = 0;
    sDynIdBuf[0] = '\0';
    sGdDynObjList = NULL;
    sDynListCurObj = NULL;
    sDynNetCount = 0;
    sGdDynObjIdIsInt = FALSE;
    gd_strcpy(sNullDynObjInfo.name, "NullObj");
}

/**
 * Parse a `DynList` array into active `GdObj`s.
 *
 * @returns Pointer to current dynamically created dynamic `GdObj`.
 *          Normally the dynlist specifically sets an object for return.
 */
struct GdObj *proc_dynlist(struct DynList *dylist) {
    UNUSED u32 pad[2];

    if (dylist++->cmd != 0xD1D4) {
        fatal_printf("proc_dynlist() not a valid dyn list");
    }

    while (dylist->cmd != 58) {
        switch (dylist->cmd) {
            case 43:
                d_copystr_to_idbuf(Dyn1AsStr(dylist));
                break;
            case 15:
                d_makeobj(Dyn2AsInt(dylist), Dyn1AsID(dylist));
                break;
            case 46:
                d_add_net_with_subgroup(Dyn2AsInt(dylist), Dyn1AsID(dylist));
                break;
            case 48:
                d_end_net_subgroup(Dyn1AsID(dylist));
                break;
            case 47:
                d_attach_joint_to_net(Dyn2AsInt(dylist), Dyn1AsID(dylist));
                break;
            case 16:
                d_start_group(Dyn1AsID(dylist));
                break;
            case 17:
                d_end_group(Dyn1AsID(dylist));
                break;
            case 18:
                d_addto_group(Dyn1AsID(dylist));
                break;
            case 30:
                d_use_obj(Dyn1AsID(dylist));
                break;
            case 28:
                d_link_with(Dyn1AsID(dylist));
                break;
            case 50:
                d_add_valptr(Dyn1AsID(dylist), (u32) DynVecY(dylist), Dyn2AsInt(dylist),
                             (size_t) DynVecX(dylist));
                break;
            case 29:
                d_link_with_ptr(Dyn1AsPtr(dylist));
                break;
            case 12:
                proc_dynlist(Dyn1AsPtr(dylist));
                break;
            case 0:
                dynid_is_int(Dyn2AsInt(dylist));
                break;
            case 1:
                d_set_init_pos(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 2:
                d_set_rel_pos(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 3:
                d_set_world_pos(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 4:
                d_set_normal(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 5:
                d_set_scale(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 49:
                d_make_vertex(DynVec(dylist));
                break;
            case 6:
                d_set_rotation(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 27:
                d_center_of_gravity(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 26:
                d_set_shape_offset(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 44:
                d_set_parm_f(Dyn2AsInt(dylist), DynVecX(dylist));
                break;
            case 45:
                d_set_parm_ptr(Dyn2AsInt(dylist), Dyn1AsPtr(dylist));
                break;
            case 8:
                d_set_flags(Dyn2AsInt(dylist));
                break;
            case 9:
                d_clear_flags(Dyn2AsInt(dylist));
                break;
            case 7:
                d_set_obj_draw_flag(Dyn2AsInt(dylist));
                break;
            case 39:
                d_attach(Dyn1AsID(dylist));
                break;
            case 40:
                d_attachto_dynid(Dyn2AsInt(dylist), Dyn1AsID(dylist));
                break;
            case 41:
                d_set_att_offset(DynVec(dylist));
                break;
            case 21:
                d_set_nodegroup(Dyn1AsID(dylist));
                break;
            case 20:
                d_set_matgroup(Dyn1AsID(dylist));
                break;
            case 22:
                d_set_skinshape(Dyn1AsID(dylist));
                break;
            case 23:
                d_set_planegroup(Dyn1AsID(dylist));
                break;
            case 24:
                d_set_shapeptrptr(Dyn1AsPtr(dylist));
                break;
            case 25:
                d_set_shapeptr(Dyn1AsID(dylist));
                break;
            case 19:
                d_set_type(Dyn2AsInt(dylist));
                break;
            case 13:
                d_set_colour_num(Dyn2AsInt(dylist));
                break;
            case 10:
                d_friction(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 11:
                d_set_spring(DynVecX(dylist));
                break;
            case 33:
                d_set_ambient(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 34:
                d_set_diffuse(DynVecX(dylist), DynVecY(dylist), DynVecZ(dylist));
                break;
            case 31:
                d_set_control_type(Dyn2AsInt(dylist));
                break;
            case 32:
                d_set_skin_weight(Dyn2AsInt(dylist), DynVecX(dylist));
                break;
            case 35:
                d_set_id(Dyn2AsInt(dylist));
                break;
            case 36:
                d_set_material(Dyn1AsPtr(dylist), Dyn2AsInt(dylist));
                break;
            case 37:
                d_map_materials(Dyn1AsID(dylist));
                break;
            case 38:
                d_map_vertices(Dyn1AsID(dylist));
                break;
            case 53:
                d_set_texture_st(DynVecX(dylist), DynVecY(dylist));
                break;
            case 52:
                d_use_texture(Dyn2AsPtr(dylist));
                break;
            case 54:
                d_make_netfromshapeid(Dyn1AsID(dylist));
                break;
            case 55:
                d_make_netfromshape_ptrptr(Dyn1AsPtr(dylist));
                break;
            default:
                fatal_printf("proc_dynlist(): unkown command");
        }
        dylist++;
    }

    return sDynListCurObj;
}

/**
 * Copy input `str` into a buffer that will be concatenated to a dynamic
 * `GdObj`'s name string when creating a new dynamic object. If input
 * is `NULL`, then a generic string is created based on the number of
 * unnamed objects.
 */
void d_copystr_to_idbuf(char *str) {
    if (str != NULL) {
        if (str[0] == '\0') {
            sprintf(sDynIdBuf, "__%d", ++sUnnamedObjCount);
        } else {
            gd_strcpy(sDynIdBuf, str);
        }
    } else {
        sDynIdBuf[0] = '\0';
    }
}

/**
 * Concatenate input `str` into a buffer that will be concatenated to a dynamic
 * `GdObj`'s name string when creating a new dynamic object. If input
 * is `NULL`, then a generic string is created based on the number of
 * unnamed objects.
 *
 * @note Not called
 */
void d_catstr_to_idbuf(char *str) {
    char buf[0xff + 1];

    if (str != NULL) {
        if (str[0] == '\0') {
            sprintf(buf, "__%d", ++sUnnamedObjCount);
        } else {
            gd_strcpy(buf, str);
        }
    } else {
        buf[0] = '\0';
    }

    gd_strcat(sDynIdBuf, buf);
}

/**
 * Stash the current string that is appended to a created dynamic `GdObj` name.
 */
void cpy_idbuf_to_backbuf(void) {
    gd_strcpy(sBackBuf, sDynIdBuf);
}

/**
 * Pop the stash for the string that is appended to a created dynamic `GdObj` name.
 */
void cpy_backbuf_to_idbuf(void) {
    gd_strcpy(sDynIdBuf, sBackBuf);
}

/**
 * Get the `DynObjInfo` struct for object `id`
 *
 * @param id Either a string or integer id for a dynamic `GdObj`
 * @returns pointer to that object's information
 */
struct DynObjInfo *get_dynobj_info(DynId id) {
    struct DynObjInfo *foundDynobj;
    char buf[0x100];
    s32 i;

    if (sLoadedDynObjs == 0) {
        return NULL;
    }

    if (sGdDynObjIdIsInt) {
        sprintf(buf, "N%d", DynIdAsInt(id));
    } else {
        gd_strcpy(buf, DynIdAsStr(id)); // strcpy
    }

    gd_strcat(buf, sDynIdBuf); // strcat
    foundDynobj = NULL;
    for (i = 0; i < sLoadedDynObjs; i++) {
        if (gd_str_not_equal(sGdDynObjList[i].name, buf) == 0) // strcmp equal
        {
            foundDynobj = &sGdDynObjList[i];
            break;
        }
    }

    return foundDynobj;
}

/**
 * Reset the number of created dynamic objects and
 * free the dynamic object information list (`sGdDynObjList`).
 * The objects themselves still exist, though.
 *
 * @note Not called
 */
void reset_dynamic_objs(void) {
    UNUSED s32 pad;

    if (sLoadedDynObjs == 0) {
        return;
    }

    gd_free(sGdDynObjList);
    sLoadedDynObjs = 0;
    sGdDynObjList = NULL;
}

/**
 * Create an `ObjNet` and an associated node `ObjGroup`. This function creates
 * its own naming string to append to later created dynamic objects.
 */
void d_add_net_with_subgroup(UNUSED s32 a0, DynId id) {
    d_makeobj(D_NET, id);
    d_set_obj_draw_flag(OBJ_NOT_DRAWABLE);
    // this creates a string to append to the names of the objs created after this
    sprintf(sDynNetIdBuf, "c%d", ++sDynNetCount);
    d_set_type(4);
    cpy_idbuf_to_backbuf();
    d_copystr_to_idbuf(sDynNetIdBuf);
    d_start_group(id);
    cpy_backbuf_to_idbuf();
    d_use_obj(id);
    sParentNetInfo = sDynListCurInfo;
}

/**
 * End the `ObjNet`+`ObjGroup` set created by `d_add_net_with_subgroup()`.
 */
void d_end_net_subgroup(DynId id) {
    d_use_obj(id);
    cpy_idbuf_to_backbuf();
    d_copystr_to_idbuf(sDynNetIdBuf);
    d_end_group(id);
    d_set_nodegroup(id);
    cpy_backbuf_to_idbuf();
    sParentNetInfo = NULL;
}

/**
 * Create an `ObjJoint` and add that to the `ObjNet` created by
 * `d_add_net_with_subgroup()`.
 *
 * @param arg0 Not used
 * @param id   Id for created `ObjJoint`
 */
void d_attach_joint_to_net(UNUSED s32 arg0, DynId id) {
    UNUSED struct DynObjInfo *curInfo = sDynListCurInfo;
    UNUSED u32 pad[2];

    d_makeobj(D_JOINT, id);
    d_set_type(3);
    d_set_shapeptrptr(NULL);
    d_attach_to(0xD, sParentNetInfo->obj);
    sParentNetInfo = sDynListCurInfo;
}

/**
 * Create a new `ObjNet` linked with the dynamic `ObjShape` `id`.
 * The newly made net is added to the dynamic object list.
 */
void d_make_netfromshapeid(DynId id) {
    struct DynObjInfo *dyninfo = get_dynobj_info(id);
    struct ObjNet *net;

    if (dyninfo == NULL) {
        fatal_printf("dMakeNetFromShape(\"%s\"): Undefined object", DynIdAsStr(id));
    }

    net = make_netfromshape((struct ObjShape *) dyninfo->obj);
    add_to_dynobj_list(&net->header, NULL);
}

/**
 * Create a new `ObjNet` linked with the doubly indirected `ObjShape`.
 * The newly made net is added to the dynamic object list, but
 * the shape is not moved into the dynamic list.
 */
void d_make_netfromshape_ptrptr(struct ObjShape **shapePtr) {
    UNUSED u32 pad;
    struct ObjNet *net = make_netfromshape(*shapePtr);

    printf("dMakeNetFromShapePtrPtr\n");

    add_to_dynobj_list(&net->header, NULL);
}

/**
 * Add `newobj` identified by `id` to the dynamic `GdObj` list. Once a `GdObj`
 * is in the dynamic list, it can referred to by its `id` when that object is
 * needed later.
 */
void add_to_dynobj_list(struct GdObj *newobj, DynId id) {
    UNUSED u32 pad;
    char idbuf[0x100];

    start_memtracker("dynlist");

    if (sGdDynObjList == NULL) {
        sGdDynObjList = gd_malloc_temp(DYNOBJ_LIST_SIZE * sizeof(struct DynObjInfo));
        if (sGdDynObjList == NULL) {
            fatal_printf("dMakeObj(): Cant allocate dynlist memory");
        }
    }

    stop_memtracker("dynlist");

    if (sGdDynObjIdIsInt) {
        sprintf(idbuf, "N%d", DynIdAsInt(id));
        id = NULL;
    } else {
        sprintf(idbuf, "U%d", ((u32) sLoadedDynObjs) + 1);
    }

    if (DynIdAsStr(id) != NULL) {
        if (get_dynobj_info(id) != NULL) {
            fatal_printf("dMakeObj(\"%s\"): Object with same name already exists", DynIdAsStr(id));
        }
        gd_strcpy(sGdDynObjList[sLoadedDynObjs].name, DynIdAsStr(id));
    } else {
        gd_strcpy(sGdDynObjList[sLoadedDynObjs].name, idbuf);
    }

    gd_strcat(sGdDynObjList[sLoadedDynObjs].name, sDynIdBuf);

    if (gd_strlen(sGdDynObjList[sLoadedDynObjs].name) > (DYNOBJ_NAME_SIZE - 1)) {
        fatal_printf("dyn list obj name too long '%s'", sGdDynObjList[sLoadedDynObjs].name);
    }

    sGdDynObjList[sLoadedDynObjs].num = sLoadedDynObjs;
    sDynListCurInfo = &sGdDynObjList[sLoadedDynObjs];
    sGdDynObjList[sLoadedDynObjs++].obj = newobj;

    // A good place to bounds-check your array is
    // after you finish writing a new member to it.
    if (sLoadedDynObjs >= DYNOBJ_LIST_SIZE) {
        fatal_printf("dMakeObj(): Too many dynlist objects");
    }

    sDynListCurObj = newobj;
}

/**
 * Format `id` into string, if `DynId`s are currently being interpreted
 * as numbers.
 *
 * @returns pointer to global buffer for id
 * @retval NULL if `id` is `NULL` or if `DynId`s are interpreted as strings
 */
char *print_int_dynid(DynId id) {
    if (id && sGdDynObjIdIsInt) {
        sprintf(sIntDynIdBuffer, "N%d", DynIdAsInt(id));
        return sIntDynIdBuffer;
    }

    return NULL;
}

/**
 * Create a new `GdObj` of `type` and add that object to the
 * dynamic object list with `id`. Created objects have default
 * parameters, which are usually 0 or NULL.
 *
 * @returns pointer to created object
 */
struct GdObj *d_makeobj(enum DObjTypes type, DynId id) {
    struct GdObj *dobj;
    UNUSED struct ObjGroup *dgroup;

    switch (type) {
        case D_CAR_DYNAMICS:
            fatal_printf("dmakeobj() Car dynamics are missing!");
            break;
        case D_JOINT:
            dobj = &make_joint(0, 0.0f, 0.0f, 0.0f)->header;
            break;
        case D_ANOTHER_JOINT:
            dobj = &make_joint(0, 0.0f, 0.0f, 0.0f)->header;
            break;
        case D_NET:
            dobj = &make_net(0, NULL, NULL, NULL, NULL)->header;
            break;
        case D_GROUP:
            dobj = &make_group(0)->header;
            dgroup = (struct ObjGroup *) dobj;
            break;
        case D_DATA_GRP:
            d_makeobj(D_GROUP, id);
            ((struct ObjGroup *) sDynListCurObj)->linkType = 1;
//! @bug Returns garbage when making `D_DATA_GRP` object
#ifdef AVOID_UB
            return NULL;
#else
            return;
#endif
        case D_CAMERA:
            dobj = &make_camera(0, NULL)->header;
            break;
        case D_BONE:
            dobj = &make_bone(0, NULL, NULL, 0)->header;
            break;
        case D_PARTICLE:
            dobj = &make_particle(0, 0, 0.0f, 0.0f, 0.0f)->header;
            break;
        case D_VERTEX:
            dobj = &gd_make_vertex(0.0f, 0.0f, 0.0f)->header;
            break;
        case D_FACE:
            dobj = &make_face_with_colour(1.0, 1.0, 1.0)->header;
            break;
        case D_PLANE:
            dobj = &make_plane(FALSE, NULL)->header;
            break;
        case D_MATERIAL:
            dobj = &make_material(0, NULL, 0)->header;
            break;
        case D_SHAPE:
            dobj = &make_shape(0, print_int_dynid(id))->header;
            break;
        case D_GADGET:
            dobj = &make_gadget(0, 0)->header;
            break;
        case D_LABEL:
            //! @bug When making a `D_LABEL`, the call to `make_label()`
            //!      compiles incorrectly due to Goddard only declaring
            //!      the functions, not prototyping the functions
            dobj = &make_label(NULL, NULL, 8, 0, 0, 0)->header;
            break;
        case D_VIEW:
            dobj = &make_view(NULL,
                              (VIEW_2_COL_BUF | VIEW_ALLOC_ZBUF | VIEW_UNK_2000 | VIEW_UNK_4000
                               | VIEW_1_CYCLE | VIEW_MOVEMENT | VIEW_DRAW),
                              2, 0, 0, 0, 0, NULL)
                        ->header;
            break;
        case D_ANIMATOR:
            dobj = &make_animator()->header;
            break;
        case D_LIGHT:
            dobj = &make_light(0, NULL, 0)->header;
            addto_group(gGdLightGroup, dobj);
            break;
        default:
            fatal_printf("dMakeObj(): Unkown object type");
    }

    add_to_dynobj_list(dobj, id);
    return dobj;
}

/**
 * Attach dynamic object `id` to the current active `ObjJoint` object.
 *
 * @note This function doesn't actually do anything.
 */
void d_attach(DynId id) {
    struct DynObjInfo *info;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dAttach(\"%s\"): Undefined object", DynIdAsStr(id));
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dAttach()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Attach the current dynamic `GdObj` into the proper subgroup of `obj` and set
 * the "attach flags" of the current dynamic object to `flag`
 */
void d_attach_to(s32 flag, struct GdObj *obj) {
    UNUSED u32 pad4C;
    struct ObjGroup *attgrp;
    UNUSED u32 pad[2];
    UNUSED struct DynObjInfo *curInfo = sDynListCurInfo;
    struct GdVec3f dynobjPos; // transformed into attach offset
    struct GdVec3f objPos;

    push_dynobj_stash();

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    // find or generate attachment groups
    switch (obj->type) {
        case OBJ_TYPE_JOINTS:
            if ((attgrp = ((struct ObjJoint *) obj)->unk1F8) == NULL) {
                attgrp = ((struct ObjJoint *) obj)->unk1F8 = make_group(0);
            }
            break;
        case OBJ_TYPE_NETS:
            if ((attgrp = ((struct ObjNet *) obj)->unk1D4) == NULL) {
                attgrp = ((struct ObjNet *) obj)->unk1D4 = make_group(0);
            }
            break;
        case OBJ_TYPE_PARTICLES:
            if ((attgrp = ((struct ObjParticle *) obj)->unkB4) == NULL) {
                attgrp = ((struct ObjParticle *) obj)->unkB4 = make_group(0);
            }
            break;
        case OBJ_TYPE_ANIMATORS:
            if ((attgrp = ((struct ObjAnimator *) obj)->unk30) == NULL) {
                attgrp = ((struct ObjAnimator *) obj)->unk30 = make_group(0);
            }
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dAttachTo()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }

    if (group_contains_obj(attgrp, sDynListCurObj)) {
        return;
    }

    addto_group(attgrp, sDynListCurObj);

    if (flag & 9) {
        d_get_world_pos(&dynobjPos);
        set_cur_dynobj(obj);
        d_get_world_pos(&objPos);

        dynobjPos.x -= objPos.x;
        dynobjPos.y -= objPos.y;
        dynobjPos.z -= objPos.z;
    }

    pop_dynobj_stash();
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unk1FC = flag;
            ((struct ObjJoint *) sDynListCurObj)->unk20C = obj;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk1E4 = flag;
            ((struct ObjNet *) sDynListCurObj)->unk1E8 = obj;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) sDynListCurObj)->unkB8 = flag;
            ((struct ObjParticle *) sDynListCurObj)->unkBC = obj;
            break;
        case OBJ_TYPE_ANIMATORS:
            ((struct ObjAnimator *) sDynListCurObj)->unk34 = flag;
            ((struct ObjAnimator *) sDynListCurObj)->unk44 = obj;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dAttachTo()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }

    if (flag & 9) {
        d_set_att_offset(&dynobjPos);
    }
}

/**
 * Attach the current dynamic object to dynamic object `id`. This function
 * is a wrapper around `d_attach_to()`
 */
void d_attachto_dynid(s32 flag, DynId id) {
    struct DynObjInfo *info;

    if (id == NULL) {
        return;
    }
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dAttachTo(\"%s\"): Undefined object", DynIdAsStr(id));
    }

    d_attach_to(flag, info->obj);
}

/**
 * Helper function to copy bytes. Where's memcpy when you need it?
 */
void copy_bytes(u8 *src, u8 *dst, s32 num) {
    if (num == 0) {
        return;
    }
    while (num--) {
        *dst++ = *src++;
    }
}

/**
 * Allocate the animation data for `animator` onto the goddard heap.
 * Animation data of type `::GD_ANIM_9H` is converted to a `AnimMtxVec` struct,
 * rather than solely byted copied like the other types.
 */
void alloc_animdata(struct ObjAnimator *animator) {
    UNUSED u32 pad5C;
    // probably should be three GdVec3fs, not triangle...
    // vec0 = position; vec1 = scale? rotation?; vec2 = translation
    struct GdTriangleF tri;           //+58; temp float for converting half to f32?
    s16(*halfarr)[9];                 //+54; data to convert into a AnimMtxVec
    struct AnimDataInfo *curAnimSrc;  //+50; source animation data...
    struct AnimDataInfo *animDst;     //+4c; destination anim data II
    struct AnimDataInfo *animDataArr; //+48; start of allocated anim data memory
    struct ObjGroup *animgrp;         //+44
    s32 datasize;                     //+40; anim data allocation size?
    s32 dataIdx;                      //+3C; byte count?
    s32 animCnt;                      //+38; count of animdata "info" structs
    s32 i;                            //+34
    void *allocSpace;                 //+30; allocated animdata space
    f32 allocMtxScale = 0.1f;         //+2C; scale postion/rotation of GD_ANIM_9H data
    struct AnimMtxVec *curMtxVec;     //+28
    UNUSED u32 pad20;

    start_memtracker("animdata");

    if ((animgrp = animator->animdata) == NULL) {
        fatal_printf("no anim group");
    }

    if ((curAnimSrc = (struct AnimDataInfo *) animgrp->link1C->obj) == NULL) {
        fatal_printf("no animation data");
    }

    // count number of array-ed animation data structs
    animDst = curAnimSrc;
    animCnt = 0;
    while (animDst++->count >= 0) {
        animCnt++;
    }

    animDst = gd_malloc_perm(animCnt * sizeof(struct AnimDataInfo)); // gd_alloc_perm
    if ((animDataArr = animDst) == NULL) {
        fatal_printf("cant allocate animation data");
    }

    for (i = 0; i < animCnt; i++) {
        allocSpace = NULL;
        if (curAnimSrc->type != 0) {
            switch (curAnimSrc->type) {
                case GD_ANIM_CAMERA:
                    datasize = sizeof(s16[6]);
                    break;
                case GD_ANIM_3H_SCALED:
                    datasize = sizeof(s16[3]);
                    break;
                case GD_ANIM_3H:
                    datasize = sizeof(s16[3]);
                    break;
                case GD_ANIM_6H_SCALED:
                    datasize = sizeof(s16[6]);
                    break;
                case GD_ANIM_TRI_F_2:
                    datasize = sizeof(f32[3][3]);
                    break;
                /* This function will convert the s16[9] array into a struct AnimMtxVec */
                case GD_ANIM_9H:
                    datasize = sizeof(struct AnimMtxVec);
                    break;
                case GD_ANIM_MATRIX:
                    datasize = sizeof(Mat4f);
                    break;
                default:
                    fatal_printf("unknown anim type for allocation");
                    break;
            }

            allocSpace = gd_malloc_perm(curAnimSrc->count * datasize); // gd_alloc_perm
            if (allocSpace == NULL) {
                fatal_printf("cant allocate animation data");
            }

            if (curAnimSrc->type == GD_ANIM_9H) {
                for (dataIdx = 0; dataIdx < curAnimSrc->count; dataIdx++) {
                    halfarr = &((s16(*)[9]) curAnimSrc->data)[dataIdx];
                    curMtxVec = &((struct AnimMtxVec *) allocSpace)[dataIdx];

                    tri.p0.x = (f32)(*halfarr)[0] * allocMtxScale;
                    tri.p0.y = (f32)(*halfarr)[1] * allocMtxScale;
                    tri.p0.z = (f32)(*halfarr)[2] * allocMtxScale;
                    tri.p1.x = (f32)(*halfarr)[3] * allocMtxScale;
                    tri.p1.y = (f32)(*halfarr)[4] * allocMtxScale;
                    tri.p1.z = (f32)(*halfarr)[5] * allocMtxScale;
                    tri.p2.x = (f32)(*halfarr)[6];
                    tri.p2.y = (f32)(*halfarr)[7];
                    tri.p2.z = (f32)(*halfarr)[8];

                    gd_set_identity_mat4(&curMtxVec->matrix);
                    gd_rot_mat_about_vec(&curMtxVec->matrix, &tri.p1);
                    gd_add_vec3f_to_mat4f_offset(&curMtxVec->matrix, &tri.p2);

                    ((struct AnimMtxVec *) allocSpace)[dataIdx].vec.x = tri.p0.x;
                    ((struct AnimMtxVec *) allocSpace)[dataIdx].vec.y = tri.p0.y;
                    ((struct AnimMtxVec *) allocSpace)[dataIdx].vec.z = tri.p0.z;
                }
                curAnimSrc->type = GD_ANIM_MTX_VEC;
            } else {
                copy_bytes(curAnimSrc->data, allocSpace, curAnimSrc->count * datasize);
            }
        }

        animDst[i].type = curAnimSrc->type;
        animDst[i].count = curAnimSrc->count;
        animDst[i].data = allocSpace;

        curAnimSrc++; // next anim data struct
    }

    animgrp->link1C->obj = (void *) animDataArr;
    stop_memtracker("animdata");
}

/**
 * Generate or create the various `ObjVertex`, `ObjFace`, and/or
 * `ObjMaterial` when groups of those structures are attached to
 * `shape`. This function is called when `d_set_nodegroup()`,
 * `d_set_planegroup()`, or `d_set_matgroup()` are called
 * when an `ObjShape` is the active dynamic object.
 *
 * @note Face/vertices need to be set before materials
 */
void chk_shapegen(struct ObjShape *shape) {
    struct ObjFace *face;        // sp5C; made face
    struct ObjVertex *vtx;       // sp58; made gdvtx
    struct ObjVertex **vtxbuf;   // sp54; heap storage for made gd vtx
    struct ObjGroup *shapeMtls;  // sp50
    struct ObjGroup *shapeFaces; // sp4C
    struct ObjGroup *shapeVtx;   // sp48
    UNUSED u32 pad44;
    struct ObjGroup *madeFaces;  // sp40
    struct ObjGroup *madeVtx;    // sp3C
    u32 i;                       // sp38
    struct GdVtxData *vtxdata;   // sp34
    struct GdFaceData *facedata; // sp30
    struct GdObj *oldObjHead;    // sp2C

    start_memtracker("chk_shapegen");
    add_to_stacktrace("chk_shapegen");
    shapeMtls = shape->mtlGroup;
    shapeFaces = shape->faceGroup;
    shapeVtx = shape->vtxGroup;

    if (shapeVtx != NULL && shapeFaces != NULL) {
        if ((shapeVtx->linkType & 1) && (shapeFaces->linkType & 1)) //? needs the double if
        {
            // These Links point to special, compressed data structures
            vtxdata = (struct GdVtxData *) shapeVtx->link1C->obj;
            facedata = (struct GdFaceData *) shapeFaces->link1C->obj;
            if (facedata->type != 1) {
                fatal_printf("unsupported poly type");
            }

            if (vtxdata->type != 1) {
                fatal_printf("unsupported vertex type");
            }

            if (vtxdata->count >= VTX_BUF_SIZE) {
                fatal_printf("shapegen() too many vertices");
            }

            vtxbuf = gd_malloc_temp(VTX_BUF_SIZE * sizeof(struct ObjVertex *));
            oldObjHead = gGdObjectList;

            for (i = 0; i < vtxdata->count; i++) {
                vtx = gd_make_vertex(vtxdata->data[i][0], vtxdata->data[i][1], vtxdata->data[i][2]);
                vtx->normal.x = vtx->normal.y = vtx->normal.z = 0.0f;
                vtxbuf[i] = vtx;
            }

            madeVtx = make_group_of_type(OBJ_TYPE_VERTICES, oldObjHead, NULL);

            oldObjHead = gGdObjectList;
            for (i = 0; i < facedata->count; i++) {
                //! @bug Call to `make_face_with_colour()` compiles incorrectly
                //!      due to Goddard only declaring the functions,
                //!      not prototyping the functions
                face = make_face_with_colour(1.0, 1.0, 1.0);
                face->mtlId = (s32) facedata->data[i][0];
                add_3_vtx_to_face(face, vtxbuf[facedata->data[i][1]], vtxbuf[facedata->data[i][2]],
                                  vtxbuf[facedata->data[i][3]]);
                vtxbuf[facedata->data[i][1]]->normal.x += face->normal.x;
                vtxbuf[facedata->data[i][1]]->normal.y += face->normal.y;
                vtxbuf[facedata->data[i][1]]->normal.z += face->normal.z;

                vtxbuf[facedata->data[i][2]]->normal.x += face->normal.x;
                vtxbuf[facedata->data[i][2]]->normal.y += face->normal.y;
                vtxbuf[facedata->data[i][2]]->normal.z += face->normal.z;

                vtxbuf[facedata->data[i][3]]->normal.x += face->normal.x;
                vtxbuf[facedata->data[i][3]]->normal.y += face->normal.y;
                vtxbuf[facedata->data[i][3]]->normal.z += face->normal.z;
            }

            if (shape->flag & 0x10) {
                for (i = 0; i < vtxdata->count; i++) {
                    vtxbuf[i]->normal.x = vtxbuf[i]->pos.x;
                    vtxbuf[i]->normal.y = vtxbuf[i]->pos.y;
                    vtxbuf[i]->normal.z = vtxbuf[i]->pos.z;
                    gd_normalize_vec3f(&vtxbuf[i]->normal);
                }
            } else {
                for (i = 0; i < vtxdata->count; i++) {
                    gd_normalize_vec3f(&vtxbuf[i]->normal);
                }
            }

            gd_free(vtxbuf);
            madeFaces = make_group_of_type(OBJ_TYPE_FACES, oldObjHead, NULL);
            shape->faceGroup = madeFaces;
            shape->vtxGroup = madeVtx;
        }
    }

    if (shapeMtls != NULL) {
        if (shape->faceGroup) {
            map_face_materials(shape->faceGroup, shapeMtls);
        } else {
            fatal_printf("chk_shapegen() please set face group before mats");
        }
    }

    imout();
    stop_memtracker("chk_shapegen");
}

/**
 * Set the "node group" of the current dynamic object to dynamic object `id`.
 * The node group depends on the type of the current dynamic object:
 * * the vertex group is set for `ObjShape`
 * * the joints/weight group is set for `ObjNet`
 * * data is set for `ObjAnimator`
 * * something is set for `ObjGadget`
 */
void d_set_nodegroup(DynId id) {
    struct DynObjInfo *info; // sp2C
    UNUSED u32 pad[2];

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dSetNodeGroup(\"%s\"): Undefined group", DynIdAsStr(id));
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk1C8 = (struct ObjGroup *) info->obj;
            ((struct ObjNet *) sDynListCurObj)->unk1D0 = (struct ObjGroup *) info->obj;
            break;
        case OBJ_TYPE_SHAPES:
            ((struct ObjShape *) sDynListCurObj)->vtxGroup = (struct ObjGroup *) info->obj;
            chk_shapegen((struct ObjShape *) sDynListCurObj);
            break;
        case OBJ_TYPE_GADGETS:
            ((struct ObjGadget *) sDynListCurObj)->unk54 = (struct ObjGroup *) info->obj;
            break;
        case OBJ_TYPE_ANIMATORS:
            ((struct ObjAnimator *) sDynListCurObj)->animdata = (struct ObjGroup *) info->obj;
            alloc_animdata((struct ObjAnimator *) sDynListCurObj);
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetNodeGroup()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the material group of the current dynamic `ObjShape` to `id`.
 */
void d_set_matgroup(DynId id) {
    struct DynObjInfo *info; // sp1C

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dSetMatGroup(\"%s\"): Undefined group", DynIdAsStr(id));
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_SHAPES:
            ((struct ObjShape *) sDynListCurObj)->mtlGroup = (struct ObjGroup *) info->obj;
            chk_shapegen((struct ObjShape *) sDynListCurObj);
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetMatGroup()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * At one time in the past, this set the s and t value of the current
 * dynamic `ObjVertex`. However, this function does nothing now.
 * See `BetaVtx` for a possible remnant of vertex code that had
 * ST coordinates.
 */
void d_set_texture_st(UNUSED f32 s, UNUSED f32 t) {
    UNUSED u32 pad[2];

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_VERTICES:
            break; // ifdef-ed out?
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetTextureST()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the texture pointer of the current dynamic `ObjMaterial`.
 */
void d_use_texture(void *texture) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_MATERIALS:
            ((struct ObjMaterial *) sDynListCurObj)->texture = texture;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dUseTexture()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the current dynamic `ObjNet`'s skin group with the vertex group from
 * the dynamic `ObjShape` with `id`.
 */
void d_set_skinshape(DynId id) {
    struct DynObjInfo *info;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dSetSkinShape(\"%s\"): Undefined object", DynIdAsStr(id));
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->skinGrp = ((struct ObjShape *) info->obj)->vtxGroup;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetSkinShape()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Map the material ids for the `ObjFace`s in the current dynamic `ObjGroup`
 * to pointer to `ObjMaterial`s in the `ObjGroup` `id`.
 *
 * See `map_face_materials()` for more info.
 */
void d_map_materials(DynId id) {
    struct DynObjInfo *info;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dMapMaterials(\"%s\"): Undefined group", DynIdAsStr(id));
    }

    map_face_materials((struct ObjGroup *) sDynListCurObj, (struct ObjGroup *) info->obj);
}

/**
 * Map the vertex ids for the `ObjFace`s in the current dynamic `ObjGroup`
 * to pointer to `ObjVertex` in the `ObjGroup` `id`.
 *
 * See `map_vertices()` for more info.
 */
void d_map_vertices(DynId id) {
    struct DynObjInfo *info;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dMapVertices(\"%s\"): Undefined group", DynIdAsStr(id));
    }

    map_vertices((struct ObjGroup *) sDynListCurObj, (struct ObjGroup *) info->obj);
}

/**
 * In practice, this is used to set the faces of the current
 * active dynamic `ObjShape` to the dynamic group `id` of `ObjFace`s.
 * It also has interactions with `ObjNet`s, but there are no examples
 * of that usage in existing code.
 */
void d_set_planegroup(DynId id) {
    struct DynObjInfo *info; // sp2C
    UNUSED u32 pad[2];

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dSetPlaneGroup(\"%s\"): Undefined group", DynIdAsStr(id));
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk1CC = (struct ObjGroup *) info->obj;
            break;
        case OBJ_TYPE_SHAPES:
            ((struct ObjShape *) sDynListCurObj)->faceGroup = (struct ObjGroup *) info->obj;
            chk_shapegen((struct ObjShape *) sDynListCurObj);
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetPlaneGroup()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the shape pointer of the current active dynamic object to the
 * pointer pointed to by `shpPtrptr`.
 */
void d_set_shapeptrptr(struct ObjShape **shpPtrptr) {
    struct ObjShape *defaultptr = NULL;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    if (shpPtrptr == NULL) {
        shpPtrptr = &defaultptr;
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unk20 = *shpPtrptr;
            ((struct ObjJoint *) sDynListCurObj)->unk1C8 = 0;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk1A8 = *shpPtrptr;
            break;
        case OBJ_TYPE_BONES:
            ((struct ObjBone *) sDynListCurObj)->unkF0 = *shpPtrptr;
            break;
        case OBJ_TYPE_GADGETS:
            ((struct ObjGadget *) sDynListCurObj)->unk50 = *shpPtrptr;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) sDynListCurObj)->unk1C = *shpPtrptr;
            break;
        case OBJ_TYPE_LIGHTS:
            ((struct ObjLight *) sDynListCurObj)->unk9C = *shpPtrptr;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetShapePtrPtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the shape pointer of the current active dynamic object to dynamic
 * `ObjShape` `id`.
 */
void d_set_shapeptr(DynId id) {
    struct DynObjInfo *info; // sp24
    if (id == NULL) {
        return;
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dSetShapePtr(\"%s\"): Undefined object", DynIdAsStr(id));
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unk20 = (struct ObjShape *) info->obj;
            ((struct ObjJoint *) sDynListCurObj)->unk1C8 = 0;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk1A8 = (struct ObjShape *) info->obj;
            break;
        case OBJ_TYPE_BONES:
            ((struct ObjBone *) sDynListCurObj)->unkF0 = (struct ObjShape *) info->obj;
            break;
        case OBJ_TYPE_GADGETS:
            ((struct ObjGadget *) sDynListCurObj)->unk50 = (struct ObjShape *) info->obj;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) sDynListCurObj)->unk1C = (struct ObjShape *) info->obj;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetShapePtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the current active dynamic object to object `id`.
 */
struct GdObj *d_use_obj(DynId id) {
    struct DynObjInfo *info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dUseObj(\"%s\"): Undefined object", DynIdAsStr(id));
    }

    sDynListCurObj = info->obj;
    sDynListCurInfo = info;

    return info->obj;
}

/**
 * Set the current active dynamic object to `obj`. This object can
 * any type of `GdObj`, not just an object created through the
 * dynmaic object system.
 */
void set_cur_dynobj(struct GdObj *obj) {
    sDynListCurObj = obj;
    sDynListCurInfo = &sNullDynObjInfo;
}

/**
 * Start a dynamic `ObjGroup` identified with `id`.
 */
void d_start_group(DynId id) {
    d_makeobj(D_GROUP, id);
}

/**
 * Add all dynamic objects created between the start of dynamic `ObjGroup` `id`
 * and this call.
 */
void d_end_group(DynId id) {
    UNUSED u32 pad;
    struct DynObjInfo *info = get_dynobj_info(id); // sp20;
    struct ObjGroup *dynGrp;                       // sp1C
    s32 i;                                         // sp18

    if (info == NULL) {
        fatal_printf("dEndGroup(\"%s\"): Undefined group", DynIdAsStr(id));
    }

    dynGrp = (struct ObjGroup *) info->obj;
    for (i = info->num + 1; i < sLoadedDynObjs; i++) {
        if (sGdDynObjList[i].obj->type != OBJ_TYPE_GROUPS) {
            addto_group(dynGrp, sGdDynObjList[i].obj);
        }
    }
}

/**
 * Add the current dynamic object to the dynamic `ObjGroup` `id`.
 */
void d_addto_group(DynId id) {
    UNUSED u32 pad;
    struct DynObjInfo *info = get_dynobj_info(id); // sp20
    struct ObjGroup *targetGrp;

    if (info == NULL) {
        fatal_printf("dAddToGroup(\"%s\"): Undefined group", DynIdAsStr(id));
    }

    targetGrp = (struct ObjGroup *) info->obj;
    addto_group(targetGrp, sDynListCurObj);
}

/**
 * Set if `DynId` should be treated as integer values,
 * or as `char *` string pointers.
 *
 * @param isIntBool `TRUE` to interpret ids as integers
 */
void dynid_is_int(s32 isIntBool) {
    sGdDynObjIdIsInt = isIntBool;
}

/**
 * Set the initial position of the current dynamic object
 * to `(x, y, z)`.
 */
void d_set_init_pos(f32 x, f32 y, f32 z) {
    UNUSED u32 pad2c[3];
    struct GdObj *dynobj = sDynListCurObj; // sp28
    UNUSED u32 pad[1];

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk14.x = x;
            ((struct ObjJoint *) dynobj)->unk14.y = y;
            ((struct ObjJoint *) dynobj)->unk14.z = z;

            ((struct ObjJoint *) dynobj)->unk3C.x = x;
            ((struct ObjJoint *) dynobj)->unk3C.y = y;
            ((struct ObjJoint *) dynobj)->unk3C.z = z;

            ((struct ObjJoint *) dynobj)->unk54.x = x;
            ((struct ObjJoint *) dynobj)->unk54.y = y;
            ((struct ObjJoint *) dynobj)->unk54.z = z;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) dynobj)->unk14.x = x;
            ((struct ObjNet *) dynobj)->unk14.y = y;
            ((struct ObjNet *) dynobj)->unk14.z = z;

            ((struct ObjNet *) dynobj)->unk20.x = x;
            ((struct ObjNet *) dynobj)->unk20.y = y;
            ((struct ObjNet *) dynobj)->unk20.z = z;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) dynobj)->unk20.x = x;
            ((struct ObjParticle *) dynobj)->unk20.y = y;
            ((struct ObjParticle *) dynobj)->unk20.z = z;
            break;
        case OBJ_TYPE_CAMERAS:
            ((struct ObjCamera *) dynobj)->unk14.x = x;
            ((struct ObjCamera *) dynobj)->unk14.y = y;
            ((struct ObjCamera *) dynobj)->unk14.z = z;
            break;
        case OBJ_TYPE_VERTICES:
            d_set_rel_pos(x, y, z);

            ((struct ObjVertex *) dynobj)->initPos.x = x;
            ((struct ObjVertex *) dynobj)->initPos.y = y;
            ((struct ObjVertex *) dynobj)->initPos.z = z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetInitPos()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the velocity of the current active dynamic object. The
 * values of the input `GdVec3f` are copied into the object.
 */
void d_set_velocity(const struct GdVec3f *vel) {
    struct GdObj *dynobj = sDynListCurObj;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk78.x = vel->x;
            ((struct ObjJoint *) dynobj)->unk78.y = vel->y;
            ((struct ObjJoint *) dynobj)->unk78.z = vel->z;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) dynobj)->unk50.x = vel->x;
            ((struct ObjNet *) dynobj)->unk50.y = vel->y;
            ((struct ObjNet *) dynobj)->unk50.z = vel->z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetVelocity()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Read the velocity value of the current dynamic object into `dst`
 *
 * @param[out] dst values are copied to this `GdVec3f`
 */
void d_get_velocity(struct GdVec3f *dst) {
    struct GdObj *dynobj = sDynListCurObj;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            dst->x = ((struct ObjJoint *) dynobj)->unk78.x;
            dst->y = ((struct ObjJoint *) dynobj)->unk78.y;
            dst->z = ((struct ObjJoint *) dynobj)->unk78.z;
            break;
        case OBJ_TYPE_NETS:
            dst->x = ((struct ObjNet *) dynobj)->unk50.x;
            dst->y = ((struct ObjNet *) dynobj)->unk50.y;
            dst->z = ((struct ObjNet *) dynobj)->unk50.z;
            break;
        default:
            dst->x = dst->y = dst->z = 0.0f;
            break;
    }
}

/**
 * Set the torque vectore for the current dynamic object.
 * Values from input `GdVec3f` are copied into the object.
 *
 * @note Not called
 */
void d_set_torque(const struct GdVec3f *src) {
    struct GdObj *dynobj = sDynListCurObj;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) dynobj)->unkA4.x = src->x;
            ((struct ObjNet *) dynobj)->unkA4.y = src->y;
            ((struct ObjNet *) dynobj)->unkA4.z = src->z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetTorque()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Get the initial position of the current dynamic object and
 * store in `dst`.
 */
void d_get_init_pos(struct GdVec3f *dst) {
    struct GdObj *dynobj = sDynListCurObj;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            dst->x = ((struct ObjJoint *) dynobj)->unk54.x;
            dst->y = ((struct ObjJoint *) dynobj)->unk54.y;
            dst->z = ((struct ObjJoint *) dynobj)->unk54.z;
            break;
        case OBJ_TYPE_NETS:
            dst->x = ((struct ObjNet *) dynobj)->unk20.x;
            dst->y = ((struct ObjNet *) dynobj)->unk20.y;
            dst->z = ((struct ObjNet *) dynobj)->unk20.z;
            break;
        case OBJ_TYPE_VERTICES:
            dst->x = ((struct ObjVertex *) dynobj)->initPos.x;
            dst->y = ((struct ObjVertex *) dynobj)->initPos.y;
            dst->z = ((struct ObjVertex *) dynobj)->initPos.z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetInitPos()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Get the initial rotation of the current dynamic object and
 * store in `dst`.
 */
void d_get_init_rot(struct GdVec3f *dst) {
    struct GdObj *dynobj = sDynListCurObj;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            dst->x = ((struct ObjJoint *) dynobj)->unk6C.x;
            dst->y = ((struct ObjJoint *) dynobj)->unk6C.y;
            dst->z = ((struct ObjJoint *) dynobj)->unk6C.z;
            break;
        case OBJ_TYPE_NETS:
            dst->x = ((struct ObjNet *) dynobj)->unk68.x;
            dst->y = ((struct ObjNet *) dynobj)->unk68.y;
            dst->z = ((struct ObjNet *) dynobj)->unk68.z;
            break;
        case OBJ_TYPE_LIGHTS:
            dst->x = dst->y = dst->z = 0.0f;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetInitRot()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the position of the current dynamic object.
 *
 * @note This function automatically adjusts the three zoom levels
 *       for an `ObjCamera`.
 */
void d_set_rel_pos(f32 x, f32 y, f32 z) {
    struct GdObj *dynobj = sDynListCurObj; // sp34
    UNUSED struct GdVec3f unusedVec;       // sp28

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk3C.x = x;
            ((struct ObjJoint *) dynobj)->unk3C.y = y;
            ((struct ObjJoint *) dynobj)->unk3C.z = z;
            break;
        case OBJ_TYPE_CAMERAS:
            unusedVec.x = x;
            unusedVec.y = y;
            unusedVec.z = z;

            ((struct ObjCamera *) dynobj)->unk40.x = x;
            ((struct ObjCamera *) dynobj)->unk40.y = y;
            ((struct ObjCamera *) dynobj)->unk40.z = z;

            ((struct ObjCamera *) dynobj)->positions[0].x = x;
            ((struct ObjCamera *) dynobj)->positions[0].y = y;
            ((struct ObjCamera *) dynobj)->positions[0].z = z;

            ((struct ObjCamera *) dynobj)->positions[1].x = x * 1.5; //? 1.5f
            ((struct ObjCamera *) dynobj)->positions[1].y = y * 1.5; //? 1.5f
            ((struct ObjCamera *) dynobj)->positions[1].z = z * 1.5; //? 1.5f

            ((struct ObjCamera *) dynobj)->positions[2].x = x * 2.0f;
            ((struct ObjCamera *) dynobj)->positions[2].y = y * 2.0f;
            ((struct ObjCamera *) dynobj)->positions[2].z = z * 2.0f;

            ((struct ObjCamera *) dynobj)->zoomLevels = 2;
            break;
        case OBJ_TYPE_VERTICES:
            ((struct ObjVertex *) dynobj)->pos.x = x;
            ((struct ObjVertex *) dynobj)->pos.y = y;
            ((struct ObjVertex *) dynobj)->pos.z = z;
            break;
        case OBJ_TYPE_LABELS:
            ((struct ObjLabel *) dynobj)->vec14.x = x;
            ((struct ObjLabel *) dynobj)->vec14.y = y;
            ((struct ObjLabel *) dynobj)->vec14.z = z;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) dynobj)->unk20.x = x;
            ((struct ObjParticle *) dynobj)->unk20.y = y;
            ((struct ObjParticle *) dynobj)->unk20.z = z;
            break;
        case OBJ_TYPE_NETS:
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetRelPos()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Offset the current position of the current dynamic object.
 */
void d_addto_rel_pos(struct GdVec3f *src) {
    struct GdObj *dynobj = sDynListCurObj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_VERTICES:
            ((struct ObjVertex *) dynobj)->pos.x += src->x;
            ((struct ObjVertex *) dynobj)->pos.y += src->y;
            ((struct ObjVertex *) dynobj)->pos.z += src->z;
            break;
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk3C.x += src->x;
            ((struct ObjJoint *) dynobj)->unk3C.y += src->y;
            ((struct ObjJoint *) dynobj)->unk3C.z += src->z;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) dynobj)->unk20.x += src->x;
            ((struct ObjParticle *) dynobj)->unk20.y += src->y;
            ((struct ObjParticle *) dynobj)->unk20.z += src->z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dAddToRelPos()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Store the current dynamic object's position into `dst`.
 */
void d_get_rel_pos(struct GdVec3f *dst) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_VERTICES:
            dst->x = ((struct ObjVertex *) sDynListCurObj)->pos.x;
            dst->y = ((struct ObjVertex *) sDynListCurObj)->pos.y;
            dst->z = ((struct ObjVertex *) sDynListCurObj)->pos.z;
            break;
        case OBJ_TYPE_JOINTS:
            dst->x = ((struct ObjJoint *) sDynListCurObj)->unk3C.x;
            dst->y = ((struct ObjJoint *) sDynListCurObj)->unk3C.y;
            dst->z = ((struct ObjJoint *) sDynListCurObj)->unk3C.z;
            break;
        case OBJ_TYPE_CAMERAS:
            dst->x = ((struct ObjCamera *) sDynListCurObj)->unk40.x;
            dst->y = ((struct ObjCamera *) sDynListCurObj)->unk40.y;
            dst->z = ((struct ObjCamera *) sDynListCurObj)->unk40.z;
            break;
        case OBJ_TYPE_PARTICLES:
            dst->x = ((struct ObjParticle *) sDynListCurObj)->unk20.x;
            dst->y = ((struct ObjParticle *) sDynListCurObj)->unk20.y;
            dst->z = ((struct ObjParticle *) sDynListCurObj)->unk20.z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetRelPos()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Return a pointer to the attached object group of the current
 * dynamic object.
 */
struct ObjGroup *d_get_att_objgroup(void) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            return ((struct ObjJoint *) sDynListCurObj)->unk1F8;
            break; // lol
        case OBJ_TYPE_NETS:
            return ((struct ObjNet *) sDynListCurObj)->unk1D4;
            break; // lol
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetAttObjGroup()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    // No null return due to `fatal_printf()` being a non-returning function?
}

/**
 * Return a pointer to the attached object of the current dynamic object.
 */
struct GdObj *d_get_att_to_obj(void) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            return ((struct ObjJoint *) sDynListCurObj)->unk20C;
            break; // lol
        case OBJ_TYPE_NETS:
            return ((struct ObjNet *) sDynListCurObj)->unk1E8;
            break; // lol
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetAttToObj()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    // No null return due to `fatal_printf()` being a non-returning function?
}

/**
 * Store the current dynamic object's scale into `dst`.
 */
void d_get_scale(struct GdVec3f *dst) {
    struct GdObj *dynobj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            dst->x = ((struct ObjJoint *) dynobj)->unk9C.x;
            dst->y = ((struct ObjJoint *) dynobj)->unk9C.y;
            dst->z = ((struct ObjJoint *) dynobj)->unk9C.z;
            break;
        case OBJ_TYPE_NETS:
            dst->x = ((struct ObjNet *) dynobj)->unk1AC.x;
            dst->y = ((struct ObjNet *) dynobj)->unk1AC.y;
            dst->z = ((struct ObjNet *) dynobj)->unk1AC.z;
            break;
        case OBJ_TYPE_LIGHTS:
            dst->x = 1.0f;
            dst->y = 1.0f;
            dst->z = 1.0f;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetScale()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the offset of the attached object on the current dynamic object.
 */
void d_set_att_offset(const struct GdVec3f *off) {
    struct GdObj *dynobj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk200.x = off->x;
            ((struct ObjJoint *) dynobj)->unk200.y = off->y;
            ((struct ObjJoint *) dynobj)->unk200.z = off->z;

            ((struct ObjJoint *) dynobj)->unk54.x = off->x;
            ((struct ObjJoint *) dynobj)->unk54.y = off->y;
            ((struct ObjJoint *) dynobj)->unk54.z = off->z;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) dynobj)->unk1D8.x = off->x;
            ((struct ObjNet *) dynobj)->unk1D8.y = off->y;
            ((struct ObjNet *) dynobj)->unk1D8.z = off->z;

            ((struct ObjNet *) dynobj)->unk20.x = off->x;
            ((struct ObjNet *) dynobj)->unk20.y = off->y;
            ((struct ObjNet *) dynobj)->unk20.z = off->z;
            break;
        case OBJ_TYPE_PARTICLES:
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetAttOffset()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * An incorrectly-coded recursive function that was presumably supposed to
 * set the offset of an attached object. Now, it will only call itself
 * until it encounters a NULL pointer, which will trigger a `fatal_printf()`
 * call.
 *
 * @note Not called
 */
void d_set_att_to_offset(UNUSED u32 a) {
    struct GdObj *dynobj; // sp3c
    UNUSED u8 pad[24];

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    push_dynobj_stash();
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            set_cur_dynobj(((struct ObjJoint *) dynobj)->unk20C);
            break;
        case OBJ_TYPE_NETS:
            set_cur_dynobj(((struct ObjNet *) dynobj)->unk1E8);
            break;
        case OBJ_TYPE_PARTICLES:
            set_cur_dynobj(((struct ObjParticle *) dynobj)->unkBC);
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetAttToOffset()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }

    if (sDynListCurObj == NULL) {
        fatal_printf("dSetAttOffset(): Object '%s' isnt attached to anything",
                     sStashedDynObjInfo->name);
    }
    d_set_att_to_offset(a);
    pop_dynobj_stash();
}

/**
 * Store the offset of the attached object into `dst`.
 *
 * @note Not called
 */
void d_get_att_offset(struct GdVec3f *dst) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            dst->x = ((struct ObjJoint *) sDynListCurObj)->unk200.x;
            dst->y = ((struct ObjJoint *) sDynListCurObj)->unk200.y;
            dst->z = ((struct ObjJoint *) sDynListCurObj)->unk200.z;
            break;
        case OBJ_TYPE_NETS:
            dst->x = ((struct ObjNet *) sDynListCurObj)->unk1D8.x;
            dst->y = ((struct ObjNet *) sDynListCurObj)->unk1D8.y;
            dst->z = ((struct ObjNet *) sDynListCurObj)->unk1D8.z;
            break;
        case OBJ_TYPE_PARTICLES:
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetAttOffset()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Get the attached object flags for the current dynamic object.
 */
s32 d_get_att_flags(void) {
    s32 attflag; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            attflag = ((struct ObjJoint *) sDynListCurObj)->unk1FC;
            break;
        case OBJ_TYPE_NETS:
            attflag = ((struct ObjNet *) sDynListCurObj)->unk1E4;
            break;
        case OBJ_TYPE_PARTICLES:
            attflag = ((struct ObjParticle *) sDynListCurObj)->unkB8;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetAttFlags()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }

    return attflag;
}

/**
 * Set the world position of the current dynamic object.
 *
 * @note Sets the upper left coordinates of an `ObjView`
 */
void d_set_world_pos(f32 x, f32 y, f32 z) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_CAMERAS:
            ((struct ObjCamera *) sDynListCurObj)->unk14.x = x;
            ((struct ObjCamera *) sDynListCurObj)->unk14.y = y;
            ((struct ObjCamera *) sDynListCurObj)->unk14.z = z;
            break;
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unk14.x = x;
            ((struct ObjJoint *) sDynListCurObj)->unk14.y = y;
            ((struct ObjJoint *) sDynListCurObj)->unk14.z = z;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk14.x = x;
            ((struct ObjNet *) sDynListCurObj)->unk14.y = y;
            ((struct ObjNet *) sDynListCurObj)->unk14.z = z;
            break;
        case OBJ_TYPE_GADGETS:
            ((struct ObjGadget *) sDynListCurObj)->unk14.x = x;
            ((struct ObjGadget *) sDynListCurObj)->unk14.y = y;
            ((struct ObjGadget *) sDynListCurObj)->unk14.z = z;
            break;
        case OBJ_TYPE_VIEWS:
            ((struct ObjView *) sDynListCurObj)->upperLeft.x = x;
            ((struct ObjView *) sDynListCurObj)->upperLeft.y = y;
            ((struct ObjView *) sDynListCurObj)->upperLeft.z = z;
            break;
        case OBJ_TYPE_VERTICES:
            ((struct ObjVertex *) sDynListCurObj)->pos.x = x;
            ((struct ObjVertex *) sDynListCurObj)->pos.y = y;
            ((struct ObjVertex *) sDynListCurObj)->pos.z = z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetWorldPos()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the normal of the current dynamic `ObjVertex`. The input `x, y, z` values
 * are normalized into a unit vector before setting the vertex normal.
 */
void d_set_normal(f32 x, f32 y, f32 z) {
    struct GdVec3f normal; // sp1C

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    normal.x = x;
    normal.y = y;
    normal.z = z;
    gd_normalize_vec3f(&normal);

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_VERTICES:
            ((struct ObjVertex *) sDynListCurObj)->normal.x = normal.x;
            ((struct ObjVertex *) sDynListCurObj)->normal.y = normal.y;
            ((struct ObjVertex *) sDynListCurObj)->normal.z = normal.z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetNormal()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Get a pointer to the world position vector of the active
 * dynamic object. This is a pointer inside the actual object.
 *
 * @note Not called.
 */
struct GdVec3f *d_get_world_pos_ptr(void) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_VERTICES:
            return &((struct ObjVertex *) sDynListCurObj)->pos;
            break;
        case OBJ_TYPE_PARTICLES:
            return &((struct ObjParticle *) sDynListCurObj)->unk20;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetWorldPosPtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    // No null return due to `fatal_printf()` being a non-returning function?
}

/**
 * Copy the world position of the current dynamic object into `dst`.
 */
void d_get_world_pos(struct GdVec3f *dst) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_VERTICES:
            dst->x = ((struct ObjVertex *) sDynListCurObj)->pos.x;
            dst->y = ((struct ObjVertex *) sDynListCurObj)->pos.y;
            dst->z = ((struct ObjVertex *) sDynListCurObj)->pos.z;
            break;
        case OBJ_TYPE_JOINTS:
            dst->x = ((struct ObjJoint *) sDynListCurObj)->unk14.x;
            dst->y = ((struct ObjJoint *) sDynListCurObj)->unk14.y;
            dst->z = ((struct ObjJoint *) sDynListCurObj)->unk14.z;
            break;
        case OBJ_TYPE_NETS:
            dst->x = ((struct ObjNet *) sDynListCurObj)->unk14.x;
            dst->y = ((struct ObjNet *) sDynListCurObj)->unk14.y;
            dst->z = ((struct ObjNet *) sDynListCurObj)->unk14.z;
            break;
        case OBJ_TYPE_PARTICLES:
            dst->x = ((struct ObjParticle *) sDynListCurObj)->unk20.x;
            dst->y = ((struct ObjParticle *) sDynListCurObj)->unk20.y;
            dst->z = ((struct ObjParticle *) sDynListCurObj)->unk20.z;
            break;
        case OBJ_TYPE_CAMERAS:
            dst->x = ((struct ObjCamera *) sDynListCurObj)->unk14.x;
            dst->y = ((struct ObjCamera *) sDynListCurObj)->unk14.y;
            dst->z = ((struct ObjCamera *) sDynListCurObj)->unk14.z;
            break;
        case OBJ_TYPE_BONES:
            dst->x = ((struct ObjBone *) sDynListCurObj)->unk14.x;
            dst->y = ((struct ObjBone *) sDynListCurObj)->unk14.y;
            dst->z = ((struct ObjBone *) sDynListCurObj)->unk14.z;
            break;
        case OBJ_TYPE_SHAPES:
            dst->x = dst->y = dst->z = 0.0f;
            break;
        case OBJ_TYPE_LABELS:
            dst->x = dst->y = dst->z = 0.0f;
            break;
        case OBJ_TYPE_GADGETS:
            dst->x = ((struct ObjGadget *) sDynListCurObj)->unk14.x;
            dst->y = ((struct ObjGadget *) sDynListCurObj)->unk14.y;
            dst->z = ((struct ObjGadget *) sDynListCurObj)->unk14.z;
            break;
        case OBJ_TYPE_PLANES:
            dst->x = ((struct ObjPlane *) sDynListCurObj)->plane28.p0.x;
            dst->y = ((struct ObjPlane *) sDynListCurObj)->plane28.p0.y;
            dst->z = ((struct ObjPlane *) sDynListCurObj)->plane28.p0.z;

            dst->x += ((struct ObjPlane *) sDynListCurObj)->plane28.p1.x;
            dst->y += ((struct ObjPlane *) sDynListCurObj)->plane28.p1.y;
            dst->z += ((struct ObjPlane *) sDynListCurObj)->plane28.p1.z;

            dst->x *= 0.5; //? 0.5f
            dst->y *= 0.5; //? 0.5f
            dst->z *= 0.5; //? 0.5f
            break;
        case OBJ_TYPE_ZONES:
            dst->x = ((struct ObjZone *) sDynListCurObj)->unk14.p0.x;
            dst->y = ((struct ObjZone *) sDynListCurObj)->unk14.p0.y;
            dst->z = ((struct ObjZone *) sDynListCurObj)->unk14.p0.z;

            dst->x += ((struct ObjZone *) sDynListCurObj)->unk14.p1.x;
            dst->y += ((struct ObjZone *) sDynListCurObj)->unk14.p1.y;
            dst->z += ((struct ObjZone *) sDynListCurObj)->unk14.p1.z;

            dst->x *= 0.5; //? 0.5f
            dst->y *= 0.5; //? 0.5f
            dst->z *= 0.5; //? 0.5f
            break;
        case OBJ_TYPE_LIGHTS:
            dst->x = ((struct ObjLight *) sDynListCurObj)->position.x;
            dst->y = ((struct ObjLight *) sDynListCurObj)->position.y;
            dst->z = ((struct ObjLight *) sDynListCurObj)->position.z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetWorldPos()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Create a new dynamic `ObjVertex` at point `pos`.
 *
 * @param[in] pos values are copied to set vertex position
 */
void d_make_vertex(struct GdVec3f *pos) {
    d_makeobj(D_VERTEX, AsDynId(NULL));
    d_set_init_pos(pos->x, pos->y, pos->z);
}

/**
 * Scale the current dynamic object by factor `(x, y, z)`.
 *
 * @note Sets the lower right coordinates of an `ObjView`
 */
void d_set_scale(f32 x, f32 y, f32 z) {
    struct GdObj *initDynobj; // sp24;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    initDynobj = sDynListCurObj;
    push_dynobj_stash();
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) initDynobj)->unk9C.x = x;
            ((struct ObjJoint *) initDynobj)->unk9C.y = y;
            ((struct ObjJoint *) initDynobj)->unk9C.z = z;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) initDynobj)->unk1AC.x = x;
            ((struct ObjNet *) initDynobj)->unk1AC.y = y;
            ((struct ObjNet *) initDynobj)->unk1AC.z = z;
            break;
        case OBJ_TYPE_VIEWS:
            ((struct ObjView *) initDynobj)->lowerRight.x = x;
            ((struct ObjView *) initDynobj)->lowerRight.y = y;
            ((struct ObjView *) initDynobj)->lowerRight.z = z;
            break;
        case OBJ_TYPE_PARTICLES:
            break;
        case OBJ_TYPE_GADGETS:
            if (((struct ObjGadget *) initDynobj)->unk50 != NULL) {
                scale_verts_in_shape(((struct ObjGadget *) initDynobj)->unk50, x, y, z);
            }
            ((struct ObjGadget *) initDynobj)->unk40.x = x;
            ((struct ObjGadget *) initDynobj)->unk40.y = y;
            ((struct ObjGadget *) initDynobj)->unk40.z = z;
            break;
        case OBJ_TYPE_LIGHTS:
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetScale()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    pop_dynobj_stash();
}

/**
 * Set the rotation value of the current active dynamic object.
 */
void d_set_rotation(f32 x, f32 y, f32 z) {
    struct GdObj *dynobj; // sp2C
    UNUSED u32 pad;

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk6C.x = x;
            ((struct ObjJoint *) dynobj)->unk6C.y = y;
            ((struct ObjJoint *) dynobj)->unk6C.z = z;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) dynobj)->unk68.x = x;
            ((struct ObjNet *) dynobj)->unk68.y = y;
            ((struct ObjNet *) dynobj)->unk68.z = z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetRotation()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the center of gravity of the current dynamic `ObjNet`.
 */
void d_center_of_gravity(f32 x, f32 y, f32 z) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unkB0.x = x;
            ((struct ObjNet *) sDynListCurObj)->unkB0.y = y;
            ((struct ObjNet *) sDynListCurObj)->unkB0.z = z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dCofG()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the shape offset of the current dynamic `ObjJoint`.
 */
void d_set_shape_offset(f32 x, f32 y, f32 z) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unkC0.x = x;
            ((struct ObjJoint *) sDynListCurObj)->unkC0.y = y;
            ((struct ObjJoint *) sDynListCurObj)->unkC0.z = z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dShapeOffset()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Create a new `ObjValPtr` to dynamic object `objId` and attach
 * that valptr to the current dynamic object.
 *
 * @param type `::ValPtrType`
 */
void d_add_valptr(DynId objId, u32 vflags, s32 type, size_t offset) {
    struct GdObj *dynobj;      // sp2C
    struct ObjValPtrs *valptr; // sp28
    struct DynObjInfo *info;   // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;

    if (vflags == 0x40000) {
        info = get_dynobj_info(objId);
        if (info == NULL) {
            fatal_printf("dAddValPtr(\"%s\"): Undefined object", DynIdAsStr(objId));
        }

        valptr = make_valptrs(info->obj, vflags, type, offset);
    } else {
        valptr = make_valptrs(objId, vflags, type, offset);
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_GADGETS:
            if (((struct ObjGadget *) dynobj)->unk4C == NULL) {
                ((struct ObjGadget *) dynobj)->unk4C = make_group(0);
            }
            addto_group(((struct ObjGadget *) dynobj)->unk4C, &valptr->header);
            break;
        case OBJ_TYPE_LABELS:
            ((struct ObjLabel *) dynobj)->valptr = valptr;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dAddValPtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Add a value processing function (`valptrproc_t`) to the current
 * dynamic `ObjLabel`.
 */
void d_add_valproc(valptrproc_t proc) {
    struct GdObj *dynobj; // sp1C

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_LABELS:
            ((struct ObjLabel *) dynobj)->valfn = proc;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dAddValProc()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Link a variable pointer to the current active dynamic object.
 * In the final game, this is used to link arrays of raw vertex, face,
 * or animation data to `ObjGroup`s, or to link joints to `ObjAnimator`s.
 */
void d_link_with_ptr(void *ptr) {
    struct GdObj *dynobj;      // sp34
    struct ObjValPtrs *valptr; // sp30
    struct Links *link;        // sp2C

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    add_to_stacktrace("dLinkWithPtr");
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_CAMERAS:
            ((struct ObjCamera *) dynobj)->unk30 = ptr;
            break;
        case OBJ_TYPE_GROUPS:
            link = make_link_to_obj(NULL, ptr);
            ((struct ObjGroup *) dynobj)->link1C = link;
            break;
        case OBJ_TYPE_BONES:
            add_joint2bone((struct ObjBone *) dynobj, ptr);
            break;
        case OBJ_TYPE_VIEWS:
            ((struct ObjView *) dynobj)->components = ptr;
            ((struct ObjView *) dynobj)->unk1C =
                setup_view_buffers(((struct ObjView *) dynobj)->namePtr, ((struct ObjView *) dynobj),
                                   (s32)((struct ObjView *) dynobj)->upperLeft.x,
                                   (s32)((struct ObjView *) dynobj)->upperLeft.y,
                                   (s32)((struct ObjView *) dynobj)->lowerRight.x,
                                   (s32)((struct ObjView *) dynobj)->lowerRight.y);
            reset_nets_and_gadgets(((struct ObjView *) dynobj)->components);
            break;
        case OBJ_TYPE_FACES:
            if (((struct ObjFace *) dynobj)->vtxCount >= 4) {
                fatal_printf("too many points");
            }

            ((struct ObjFace *) dynobj)->vertices[((struct ObjFace *) dynobj)->vtxCount] = ptr;
            ((struct ObjFace *) dynobj)->vtxCount++;

            if (((struct ObjFace *) dynobj)->vtxCount >= 3) {
                calc_face_normal((struct ObjFace *) dynobj);
            }

            break;
        case OBJ_TYPE_ANIMATORS:
            if (((struct ObjAnimator *) dynobj)->unk14 == NULL) {
                ((struct ObjAnimator *) dynobj)->unk14 = make_group(0);
            }

            addto_group(((struct ObjAnimator *) dynobj)->unk14, ptr);
            break;
        case OBJ_TYPE_LABELS:
            valptr = make_valptrs(ptr, OBJ_TYPE_ALL, 0, 0);
            ((struct ObjLabel *) dynobj)->valptr = valptr;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dLinkWithPtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    imout();
}

/**
 * Link the dynamic object `id` to the current dynamic object by wrapping
 * `d_link_with_ptr()`.
 */
void d_link_with(DynId id) {
    struct DynObjInfo *info;                       // sp1C
    struct DynObjInfo *origInfo = sDynListCurInfo; // sp18

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    if (id == NULL) {
        return;
    }

    info = get_dynobj_info(id);
    if (info == NULL) {
        fatal_printf("dLinkWith(\"%s\"): Undefined object", DynIdAsStr(id));
    }

    d_link_with_ptr(info->obj);
    set_cur_dynobj(origInfo->obj);
    sDynListCurInfo = origInfo;
}

/**
 * Set the object specific flags of the current dynamic object.
 */
void d_set_flags(s32 flags) {
    struct GdObj *dynobj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk1BC |= flags;
            break;
        case OBJ_TYPE_BONES:
            ((struct ObjBone *) dynobj)->unk104 |= flags;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) dynobj)->unk34 |= flags;
            break;
        case OBJ_TYPE_CAMERAS:
            ((struct ObjCamera *) dynobj)->unk2C |= flags;
            break;
        case OBJ_TYPE_VIEWS:
            ((struct ObjView *) dynobj)->flags |= flags;
            break;
        case OBJ_TYPE_SHAPES:
            ((struct ObjShape *) dynobj)->flag |= flags;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) dynobj)->unk54 |= flags;
            break;
        case OBJ_TYPE_LIGHTS:
            ((struct ObjLight *) dynobj)->flags |= flags;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetFlags()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Clear object specific flags from the current dynamic object.
 */
void d_clear_flags(s32 flags) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unk1BC &= ~flags;
            break;
        case OBJ_TYPE_BONES:
            ((struct ObjBone *) sDynListCurObj)->unk104 &= ~flags;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk34 &= ~flags;
            break;
        case OBJ_TYPE_CAMERAS:
            ((struct ObjCamera *) sDynListCurObj)->unk2C &= ~flags;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) sDynListCurObj)->unk54 &= ~flags;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dClrFlags()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set variable float parameters on the current dynamic object.
 * These are mainly used for `ObjGadget`s to set the drawing size
 * range.
 */
void d_set_parm_f(enum DParmF param, f32 val) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_SHAPES:
            switch (param) {
                case PARM_F_ALPHA:
                    ((struct ObjShape *) sDynListCurObj)->unk58 = val;
                    break;
                default:
                    fatal_printf("%s: Object '%s'(%x) does not support this function.",
                                 "dSetParmf() - unsupported parm.", sDynListCurInfo->name,
                                 sDynListCurObj->type);
            }
            break;
        case OBJ_TYPE_GADGETS:
            switch (param) {
                case PARM_F_RANGE_LEFT:
                    ((struct ObjGadget *) sDynListCurObj)->unk38 = val;
                    break;
                case PARM_F_RANGE_RIGHT:
                    ((struct ObjGadget *) sDynListCurObj)->unk3C = val;
                    break;
                case PARM_F_VARVAL:
                    ((struct ObjGadget *) sDynListCurObj)->varval.f = val;
                    break;
                default:
                    fatal_printf("%s: Object '%s'(%x) does not support this function.",
                                 "dSetParmf() - unsupported parm.", sDynListCurInfo->name,
                                 sDynListCurObj->type);
            }
            break;
        case OBJ_TYPE_VERTICES:
            switch (param) {
                case PARM_F_ALPHA:
                    ((struct ObjVertex *) sDynListCurObj)->alpha = val;
                    break;
                default:
                    fatal_printf("%s: Object '%s'(%x) does not support this function.",
                                 "dSetParmf() - unsupported parm.", sDynListCurInfo->name,
                                 sDynListCurObj->type);
            }
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetParmf()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set various pointer parameters for the current dynamic object.
 * Normally, this is used to set `char *` pointer for various objects,
 * but it can also set the vertices for an `ObjFace`.
 */
void d_set_parm_ptr(enum DParmPtr param, void *ptr) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_LABELS:
            switch (param) {
                case PARM_PTR_CHAR:
                    ((struct ObjLabel *) sDynListCurObj)->fmtstr = ptr;
                    break;
                default:
                    fatal_printf("Bad parm");
            }
            break;
        case OBJ_TYPE_VIEWS:
            switch (param) {
                case PARM_PTR_CHAR:
                    ((struct ObjView *) sDynListCurObj)->namePtr = ptr;
                    break;
                default:
                    fatal_printf("Bad parm");
            }
            break;
        case OBJ_TYPE_FACES:
            switch (param) {
                case PARM_PTR_OBJ_VTX:
                    if (((struct ObjFace *) sDynListCurObj)->vtxCount > 3) {
                        fatal_printf("dsetparmp() too many points");
                    }
                    ((struct ObjFace *) sDynListCurObj)
                        ->vertices[((struct ObjFace *) sDynListCurObj)->vtxCount++] = ptr;
                    break;
                default:
                    fatal_printf("Bad parm");
            }
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetParmp()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the generic drawing flags for the current dynamic object.
 */
void d_set_obj_draw_flag(enum ObjDrawingFlags flag) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    sDynListCurObj->drawFlags |= flag;
}

/**
 * Set an object specific type field for the current dynamic object.
 */
void d_set_type(s32 type) {
    struct GdObj *dynobj = sDynListCurObj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) dynobj)->netType = type;
            break;
        case OBJ_TYPE_GADGETS:
            ((struct ObjGadget *) dynobj)->unk24 = type;
            break;
        case OBJ_TYPE_GROUPS:
            ((struct ObjGroup *) dynobj)->debugPrint = type;
            break;
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->unk1CC = type;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) dynobj)->unk60 = type;
            break;
        case OBJ_TYPE_MATERIALS:
            ((struct ObjMaterial *) dynobj)->type = type;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetType()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the specific object ID field for the current dynamic object.
 */
void d_set_id(s32 id) {
    struct GdObj *dynobj = sDynListCurObj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_MATERIALS:
            ((struct ObjMaterial *) dynobj)->id = id;
            break;
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) dynobj)->id = id;
            break;
        case OBJ_TYPE_VERTICES:
            ((struct ObjVertex *) dynobj)->id = id;
            break;
        case OBJ_TYPE_LIGHTS:
            ((struct ObjLight *) dynobj)->id = id;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetID()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

// TODO: enumerate colors?
/**
 * Set the colour of the current dynamic object. The input color is an index
 * for `gd_get_colour()`
 */
void d_set_colour_num(s32 colornum) {
    struct GdColour *rgbcolor; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unk1C8 = colornum;
            break;
        case OBJ_TYPE_PARTICLES:
            ((struct ObjParticle *) sDynListCurObj)->unk58 = colornum;
            break;
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk40 = colornum;
            break;
        case OBJ_TYPE_GADGETS:
            ((struct ObjGadget *) sDynListCurObj)->unk5C = colornum;
            break;
        case OBJ_TYPE_FACES:
            rgbcolor = gd_get_colour(colornum);
            if (rgbcolor != NULL) {
                ((struct ObjFace *) sDynListCurObj)->colour.r = rgbcolor->r;
                ((struct ObjFace *) sDynListCurObj)->colour.g = rgbcolor->g;
                ((struct ObjFace *) sDynListCurObj)->colour.b = rgbcolor->b;
                ((struct ObjFace *) sDynListCurObj)->colNum = colornum;
            } else {
                fatal_printf("dSetColNum: Unkown colour number");
            }
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dColourNum()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the material ID of the current dynamic `ObjFace`.
 */
void d_set_material(UNUSED void *a0, s32 mtlId) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_FACES:
            ((struct ObjFace *) sDynListCurObj)->mtlId = mtlId;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetMaterial()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the friction vec of the current dynamic `ObjJoint`.
 */
void d_friction(f32 x, f32 y, f32 z) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            ((struct ObjJoint *) sDynListCurObj)->unkDC.x = x;
            ((struct ObjJoint *) sDynListCurObj)->unkDC.y = y;
            ((struct ObjJoint *) sDynListCurObj)->unkDC.z = z;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dFriction()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the spring constant of the current dynamic `ObjBone`.
 */
void d_set_spring(f32 spring) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_BONES:
            ((struct ObjBone *) sDynListCurObj)->unk110 = spring;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetSpring()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the ambient color of the current dynamic `ObjMaterial`.
 */
void d_set_ambient(f32 r, f32 g, f32 b) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_MATERIALS:
            ((struct ObjMaterial *) sDynListCurObj)->Ka.r = r;
            ((struct ObjMaterial *) sDynListCurObj)->Ka.g = g;
            ((struct ObjMaterial *) sDynListCurObj)->Ka.b = b;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetAmbient()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the diffuse color of the current dynamic `ObjMaterial` or `ObjLight`.
 */
void d_set_diffuse(f32 r, f32 g, f32 b) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_MATERIALS:
            ((struct ObjMaterial *) sDynListCurObj)->Kd.r = r;
            ((struct ObjMaterial *) sDynListCurObj)->Kd.g = g;
            ((struct ObjMaterial *) sDynListCurObj)->Kd.b = b;
            break;
        case OBJ_TYPE_LIGHTS:
            ((struct ObjLight *) sDynListCurObj)->diffuse.r = r;
            ((struct ObjLight *) sDynListCurObj)->diffuse.g = g;
            ((struct ObjLight *) sDynListCurObj)->diffuse.b = b;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetDiffuse()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the control type of the current dynamic `ObjNet`.
 */
void d_set_control_type(s32 ctrltype) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            ((struct ObjNet *) sDynListCurObj)->unk210 = ctrltype;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dControlType()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Get a pointer to a `GdPlaneF` in the current dynamic object.
 * If the current object does not have a plane, a pointer to
 * a global plane at (0,0) is returned.
 */
struct GdPlaneF *d_get_plane(void) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            return &((struct ObjNet *) sDynListCurObj)->unkBC;
            break;
        case OBJ_TYPE_PLANES:
            return &((struct ObjPlane *) sDynListCurObj)->plane28;
            break;
        case OBJ_TYPE_ZONES:
            return &((struct ObjZone *) sDynListCurObj)->unk14;
            break;
        default:
            return &sGdNullPlaneF;
    }
}

/**
 * Copy the matrix from the current dynamic object into `dst`.
 */
void d_get_matrix(Mat4f *dst) {
    struct GdObj *dynobj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            gd_copy_mat4f(&((struct ObjNet *) dynobj)->mat128, dst);
            break;
            break; // lol
        case OBJ_TYPE_JOINTS:
            gd_copy_mat4f(&((struct ObjJoint *) dynobj)->matE8, dst);
            break;
        case OBJ_TYPE_CAMERAS:
            gd_copy_mat4f(&((struct ObjCamera *) dynobj)->unkE8, dst);
            break;
        case OBJ_TYPE_PARTICLES:
            gd_set_identity_mat4(dst);
            break;
        case OBJ_TYPE_SHAPES:
            gd_set_identity_mat4(dst);
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetMatrix()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the matrix of the current dynamic object by copying `src` into the object.
 */
void d_set_matrix(Mat4f *src) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            gd_copy_mat4f(src, &((struct ObjNet *) sDynListCurObj)->mat128);
            //! @bug When setting an `ObjNet` matrix, the source is copied twice
            //!      due to a probable copy-paste line repeat error
            gd_copy_mat4f(src, &((struct ObjNet *) sDynListCurObj)->mat128);
            break;
        case OBJ_TYPE_JOINTS:
            gd_copy_mat4f(src, &((struct ObjJoint *) sDynListCurObj)->matE8);
            break;
        case OBJ_TYPE_CAMERAS:
            gd_copy_mat4f(src, &((struct ObjCamera *) sDynListCurObj)->unk64);
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetMatrix()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Set the rotation matrix of the current dynamic object by copying
 * the input matrix `src`.
 */
void d_set_rot_mtx(Mat4f *src) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            gd_copy_mat4f(src, &((struct ObjJoint *) sDynListCurObj)->mat128);
            break;
        case OBJ_TYPE_NETS:
            gd_copy_mat4f(src, &((struct ObjNet *) sDynListCurObj)->mat168);
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetRMatrix()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Get a pointer to the current dynamic object's rotation matrix.
 */
Mat4f *d_get_rot_mtx_ptr(void) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            return &((struct ObjJoint *) sDynListCurObj)->mat128;
        case OBJ_TYPE_NETS:
            return &((struct ObjNet *) sDynListCurObj)->mat168;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetRMatrixPtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    // No null return due to `fatal_printf()` being a non-returning function?
}

/**
 * Copy `src` into the identity matrix of the current dynamic object.
 */
void d_set_idn_mtx(Mat4f *src) {
    struct GdObj *dynobj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            gd_copy_mat4f(src, &((struct ObjNet *) dynobj)->matE8);
            break;
        case OBJ_TYPE_JOINTS:
            gd_copy_mat4f(src, &((struct ObjJoint *) dynobj)->mat168);
            break;
        case OBJ_TYPE_LIGHTS:
            ((struct ObjLight *) dynobj)->position.x = (*src)[3][0];
            ((struct ObjLight *) dynobj)->position.y = (*src)[3][1];
            ((struct ObjLight *) dynobj)->position.z = (*src)[3][2];
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetIMatrix()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}

/**
 * Get a pointer to the current dynamic object's matrix.
 */
Mat4f *d_get_matrix_ptr(void) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            return &((struct ObjNet *) sDynListCurObj)->mat128;
            break;
        case OBJ_TYPE_CAMERAS:
            return &((struct ObjCamera *) sDynListCurObj)->unk64;
            break;
        case OBJ_TYPE_BONES:
            return &((struct ObjBone *) sDynListCurObj)->mat70;
            break;
        case OBJ_TYPE_JOINTS:
            return &((struct ObjJoint *) sDynListCurObj)->matE8;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetMatrixPtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    // No null return due to `fatal_printf()` being a non-returning function?
}

/**
 * Get a pointer to the current dynamic object's identity matrix.
 */
Mat4f *d_get_idn_mtx_ptr(void) {
    struct GdObj *dynobj; // sp24

    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    dynobj = sDynListCurObj;
    switch (sDynListCurObj->type) {
        case OBJ_TYPE_NETS:
            return &((struct ObjNet *) dynobj)->matE8;
            break;
        case OBJ_TYPE_JOINTS:
            return &((struct ObjJoint *) dynobj)->mat168;
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dGetIMatrixPtr()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
    // No null return due to `fatal_printf()` being a non-returning function?
}

/**
 * Use the dynamic object system to calculate the distance between
 * two `GdObj`s. The objects don't have to be dynamic objects.
 */
f32 d_calc_world_dist_btwn(struct GdObj *obj1, struct GdObj *obj2) {
    struct GdVec3f obj1pos; // sp34
    struct GdVec3f obj2pos; // sp28
    struct GdVec3f posdiff; // sp1C

    set_cur_dynobj(obj1);
    d_get_world_pos(&obj1pos);
    set_cur_dynobj(obj2);
    d_get_world_pos(&obj2pos);

    posdiff.x = obj2pos.x - obj1pos.x;
    posdiff.y = obj2pos.y - obj1pos.y;
    posdiff.z = obj2pos.z - obj1pos.z;

    return gd_vec3f_magnitude(&posdiff);
}

/**
 * Create a new weight for the current dynamic `ObjJoint`. The input weight value
 * is out of 100.
 */
void d_set_skin_weight(s32 id, f32 percentWeight) {
    if (sDynListCurObj == NULL) {
        fatal_printf("proc_dynlist(): No current object");
    }

    switch (sDynListCurObj->type) {
        case OBJ_TYPE_JOINTS:
            set_skin_weight((struct ObjJoint *) sDynListCurObj, id, NULL,
                            percentWeight / 100.0); //? 100.0f
            break;
        default:
            fatal_printf("%s: Object '%s'(%x) does not support this function.", "dSetSkinWeight()",
                         sDynListCurInfo->name, sDynListCurObj->type);
    }
}
