#include "common.h"
#include "model.h"
#include "ld_addrs.h"
#include "stdlib/stdarg.h"
#include "entity.h"
#include "hud_element.h"
#include "effects.h"
#include "nu/nusys.h"
#include "model_clear_render_tasks.h"

#if VERSION_IQUE
// TODO: remove if sections are split in iQue release
extern Addr entity_jan_iwa_ROM_START;
extern Addr entity_jan_iwa_ROM_END;
extern Addr entity_default_ROM_START;
extern Addr entity_default_ROM_END;
extern Addr entity_sbk_omo_ROM_START;
extern Addr entity_sbk_omo_ROM_END;
#endif

extern Addr MapTextureMemory;

#ifdef SHIFT
extern Addr WorldEntityHeapBottom;
extern Addr WorldEntityHeapBase;
#define WORLD_ENTITY_HEAP_BOTTOM (s32) WorldEntityHeapBottom
#define WORLD_ENTITY_HEAP_BASE (s32) WorldEntityHeapBase
// TODO this only refers to one of 3 overlays which happen to share the same address space
// but don't necessarily have to
#define AREA_SPECIFIC_ENTITY_VRAM (s32) entity_default_VRAM
#else
#define WORLD_ENTITY_HEAP_BOTTOM 0x80250000
#define WORLD_ENTITY_HEAP_BASE 0x80267FF0
#define AREA_SPECIFIC_ENTITY_VRAM 0x802BAE00
#endif

typedef struct Fog {
    /* 0x00 */ s32 enabled;
    /* 0x04 */ s32 r;
    /* 0x08 */ s32 g;
    /* 0x0C */ s32 b;
    /* 0x10 */ s32 a;
    /* 0x14 */ s32 startDistance;
    /* 0x18 */ s32 endDistance;
} Fog; // size = 0x1C

extern s32 D_801516FC;

extern Gfx D_8014B7F8[];
extern Gfx D_8014B820[];
extern Gfx D_8014B848[];
extern Gfx D_8014B870[];
extern Gfx D_8014B898[];
extern Gfx D_8014B8C0[];
extern Gfx D_8014B8E8[];
extern Gfx D_8014B910[];
extern Gfx D_8014B938[];
extern Gfx D_8014B960[];
extern Gfx D_8014B988[];
extern Gfx D_8014B9B0[];
extern Gfx D_8014B9D8[];
extern Gfx D_8014BA00[];
extern Gfx D_8014BA20[];
extern Gfx D_8014BA48[];
extern Gfx D_8014BA70[];
extern Gfx D_8014BA98[];
extern Gfx D_8014BAC0[];
extern Gfx D_8014BAE8[];
extern Gfx D_8014BB10[];
extern Gfx D_8014BB38[];
extern Gfx D_8014BB60[];
extern Gfx D_8014BB88[];
extern Gfx D_8014BBB0[];
extern Gfx D_8014BBD8[];
extern Gfx D_8014BC00[];
extern Gfx D_8014BC28[];
extern Gfx D_8014BC50[];
extern Gfx D_8014BC78[];
extern Gfx D_8014BCA0[];
extern Gfx D_8014BCC8[];
extern Gfx D_8014BCF8[];
extern Gfx D_8014BD18[];
extern Gfx D_8014BD40[];
extern Gfx D_8014BD68[];
extern Gfx D_8014BD88[];
extern Gfx D_8014BDB0[];
extern Gfx D_8014BDD8[];
extern Gfx D_8014BE00[];
extern Gfx D_8014BE28[];
extern Gfx D_8014BE50[];
extern Gfx D_8014BE78[];
extern Gfx D_8014BEA0[];
extern Gfx D_8014BEC8[];
extern Gfx D_8014BEF0[];
extern Gfx D_8014BF18[];
extern Gfx D_8014BF40[];
extern Gfx D_8014BF68[];
extern Gfx D_8014BF90[];
extern Gfx D_8014BFB8[];
extern Gfx D_8014BFE0[];
extern Gfx D_8014C008[];
extern Gfx D_8014C028[];
extern Gfx D_8014C050[];
extern Gfx D_8014C078[];
extern Gfx D_8014C098[];
extern Gfx D_8014C0C0[];
extern Gfx D_8014C0E8[];
extern Gfx D_8014C110[];
extern Gfx D_8014C138[];
extern Gfx D_8014C160[];

s32 D_8014AFB0 = 0xFF;

s32 D_8014AFB4[] = {0, 0, 0};

Gfx* D_8014AFC0[] = { D_8014B7F8, D_8014B910, D_8014B820, D_8014B938, D_8014B848, D_8014B960, D_8014B870, D_8014B988, D_8014B898, D_8014BA20, D_8014B9B0, D_8014BAC0, D_8014B8C0, D_8014B9D8, D_8014B8E8, D_8014BA00, D_8014BB60, D_8014BC78, D_8014BB88, D_8014BCA0, D_8014BBB0, D_8014BCC8, D_8014BBD8, D_8014BCF8, D_8014BC00, D_8014BD88, D_8014BD18, D_8014BC28, D_8014BD40, D_8014BC50, D_8014BD68, D_8014BE78, D_8014BF90, D_8014BEA0, D_8014BFB8, D_8014BEC8, D_8014BFE0, D_8014BEF0, D_8014C008, D_8014BF18, D_8014C098, D_8014C028, D_8014BF40, D_8014C050, D_8014BF68, D_8014C078, D_8014BA48, D_8014BA70, D_8014BA98, D_8014BDB0, D_8014BDD8, D_8014BE00, D_8014C0C0, D_8014C0E8, D_8014C110, D_8014BB10, D_8014BB38, D_8014BE28, D_8014BE50, D_8014C138, D_8014C160, NULL };

Gfx D_8014B0B8[21][5] = {
    {
        gsDPSetCombineMode(G_CC_SHADE, G_CC_SHADE),
        gsDPSetCombineMode(G_CC_SHADE, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_SHADE, G_CC_PASS2),
        gsDPSetCombineLERP(SHADE, 0, PRIMITIVE_ALPHA, PRIMITIVE, 0, 0, 0, SHADE, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(SHADE, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, SHADE, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
    }, {
        gsDPSetCombineMode(G_CC_MODULATEIA, G_CC_MODULATEIA),
        gsDPSetCombineMode(G_CC_MODULATEIDECALA, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_MODULATEIA, G_CC_PASS2),
        gsDPSetCombineLERP(TEXEL0, 0, SHADE, 0, 0, 0, 0, TEXEL0, COMBINED, PRIMITIVE, PRIMITIVE_ALPHA, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, SHADE, 0, TEXEL0, 0, SHADE, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    }, {
        gsDPSetCombineMode(G_CC_BLENDRGBA, G_CC_BLENDRGBA),
        gsDPSetCombineMode(G_CC_BLENDRGBA, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_BLENDRGBA, G_CC_PASS2),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0_ALPHA, SHADE, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE_ALPHA, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0_ALPHA, SHADE, 0, 0, 0, SHADE, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_DECALRGBA, G_CC_DECALRGBA),
        gsDPSetCombineMode(G_CC_DECALRGBA, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_DECALRGBA, G_CC_PASS2),
        gsDPSetCombineLERP(TEXEL0, 0, PRIMITIVE_ALPHA, PRIMITIVE, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, TEXEL0, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, COMBINED, 0, SHADE, 0),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, COMBINED, 0, SHADE, 0),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, COMBINED, 0, SHADE, 0),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, COMBINED, 0, SHADE, 0),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, COMBINED, 0, SHADE, 0),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, COMBINED, 0, SHADE, 0),
    },
    {
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, SHADE, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
    },
    {
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
    },
    {
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
    },
    {
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
    },
};

Gfx D_8014B400[21][5] = {
    {
        gsDPSetCombineMode(G_CC_SHADE, G_CC_SHADE),
        gsDPSetCombineMode(G_CC_SHADE, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_SHADE, G_CC_PASS2),
        gsDPSetCombineLERP(SHADE, 0, PRIMITIVE_ALPHA, PRIMITIVE, 0, 0, 0, SHADE, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(SHADE, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, SHADE, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_MODULATEIDECALA, G_CC_MODULATEIDECALA),
        gsDPSetCombineMode(G_CC_MODULATEIDECALA, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_MODULATEIDECALA, G_CC_PASS2),
        gsDPSetCombineLERP(TEXEL0, 0, SHADE, 0, 0, 0, 0, TEXEL0, COMBINED, PRIMITIVE, PRIMITIVE_ALPHA, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, SHADE, 0, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_BLENDRGBA, G_CC_BLENDRGBA),
        gsDPSetCombineMode(G_CC_BLENDRGBA, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_BLENDRGBA, G_CC_PASS2),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0_ALPHA, SHADE, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE_ALPHA, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0_ALPHA, SHADE, 0, 0, 0, SHADE, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_DECALRGBA, G_CC_DECALRGBA),
        gsDPSetCombineMode(G_CC_DECALRGBA, G_CC_PASS2),
        gsDPSetCombineMode(G_CC_DECALRGBA, G_CC_PASS2),
        gsDPSetCombineLERP(TEXEL0, 0, PRIMITIVE_ALPHA, PRIMITIVE, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, TEXEL0, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEI2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEI2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineMode(G_CC_TRILERP, G_CC_MODULATEIA2),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, PRIMITIVE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, COMBINED, 0, SHADE, 0),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL1, 0, TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, 0, 0, 0, SHADE, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(0, 0, 0, 0, TEXEL0, TEXEL1, SHADE, 0, SHADE, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(TEXEL0, TEXEL1, SHADE_ALPHA, TEXEL1, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0, 1, TEXEL1, TEXEL0, COMBINED, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, 0, 0, 0, COMBINED, 0, 0, 0, COMBINED),
        gsDPSetCombineLERP(1, TEXEL0, PRIMITIVE, TEXEL0, 0, 0, 0, TEXEL0, COMBINED, 0, PRIMITIVE, ENVIRONMENT, 0, 0, 0, COMBINED),
    },
    {
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
        gsDPSetCombineLERP(TEXEL0, SHADE, TEXEL0, TEXEL0, 1, TEXEL1, TEXEL0, TEXEL1, TEXEL0, COMBINED, TEXEL0_ALPHA, TEXEL0, 1, TEXEL0, TEXEL1, TEXEL0),
    },
    {
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
    },
    {
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
    },
    {
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
        gsDPSetCombineLERP(NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1, NOISE, 0, SHADE_ALPHA, 0, 0, 0, 0, 1),
    },
};

void* mdl_textureBaseAddress = (void*) &MapTextureMemory;

u8 mdl_bgMultiplyColorA = 0;
u8 mdl_bgMultiplyColorR = 0;
u8 mdl_bgMultiplyColorG = 0;
u8 mdl_bgMultiplyColorB = 0;

s8 mdl_renderModelFogPrimColorR = 0;
s8 mdl_renderModelFogPrimColorG = 0;
s8 mdl_renderModelFogPrimColorB = 0;
s8 mdl_renderModelFogPrimColorA = 0;

s8 mdl_renderModelFogColorR = 0;
s8 mdl_renderModelFogColorG = 0;
s8 mdl_renderModelFogColorB = 0;
s8 mdl_renderModelFogColorA = 0; // unused?

s32 mdl_renderModelFogStart = 950;
s32 mdl_renderModelFogEnd = 1000;

s8 gRenderModelPrimR = 255;
s8 gRenderModelPrimG = 255;
s8 gRenderModelPrimB = 255;
s8 gRenderModelEnvR = 0;
s8 gRenderModelEnvG = 0;
s8 gRenderModelEnvB = 0;
s8 D_8014B766 = 0;
s8 D_8014B767 = 0;

Matrix4s mdl_RDPIdentity = {
    .whole = {
        {1, 0, 0, 0},
        {0, 1, 0, 0},
        {0, 0, 1, 0},
        {0, 0, 0, 1}
    },
    .frac = {
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0},
        {0, 0, 0, 0}
    }
};

// The depth buffer contains values encoded in a custom 18-bit floating-point format.
// There are 3 bits of exponent, 11 bits of mantissa, and 4 bits of "dz".
// However, two of the "dz" bits are inaccessible to the CPU because it can only access 8 of the 9
//   bits of each RDRAM byte (the N64 has 9-bit RAM).
// Therefore, the CPU sees it as a 16-bit value.

// Fields in floating point depth buffer format
#define DEPTH_EXPONENT_MASK 0xE000
#define DEPTH_MANTISSA_MASK 0x1FFC
#define DEPTH_DZ_MASK       0x0003

#define DEPTH_EXPONENT_SHIFT 13
#define DEPTH_MANTISSA_SHIFT 2
#define DEPTH_DZ_SHIFT       0

// Lookup table for converting depth buffer values to a 15.3 fixed-point format.
typedef struct DepthFloatFactors {
    /* 0x00 */ s32 shift;
    /* 0x04 */ s32 bias;
} DepthFloatFactors;

DepthFloatFactors depthFloatLookupTable[] = {
    { 6, 0x00000 },
    { 5, 0x20000 },
    { 4, 0x30000 },
    { 3, 0x38000 },
    { 2, 0x3C000 },
    { 1, 0x3E000 },
    { 0, 0x3F000 },
    { 0, 0x3F800 },
    { 0, 0x00000 },
};

// Maximum depth value after the viewport transform.
// The multiplication by 2 comes from transforming depth from (-0.5, 0.5) to (0.0, 1.0).
// The multiplication by 32 comes from scaling the RSP does to increase depth precision.
#define MAX_VIEWPORT_DEPTH (2 * 32 * ((G_MAXZ / 2)))

s32 gLastRenderTaskCount = 0;

// padding?
s32 D_8014B7F4 = 0;

Gfx D_8014B7F8[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD |
                          G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B820[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_OPA_DECAL, G_RM_AA_ZB_OPA_DECAL2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD |
                          G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B848[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_OPA_INTER, G_RM_AA_ZB_OPA_INTER2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD |
                          G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B870[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_TEX_EDGE, G_RM_AA_ZB_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B898[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_XLU_SURF, G_RM_AA_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B8C0[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_XLU_DECAL, G_RM_AA_ZB_XLU_DECAL2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B8E8[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_XLU_INTER, G_RM_AA_ZB_XLU_INTER2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B910[] = {
    gsDPSetRenderMode(G_RM_ZB_OPA_SURF, G_RM_ZB_OPA_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B938[] = {
    gsDPSetRenderMode(G_RM_ZB_OPA_DECAL, G_RM_ZB_OPA_DECAL2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B960[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_OPA_INTER, G_RM_AA_ZB_OPA_INTER2),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsDPSetCombineMode(G_CC_SHADE, G_CC_SHADE),
    gsSPEndDisplayList(),
};

Gfx D_8014B988[] = {
    gsDPSetRenderMode(G_RM_AA_ZB_TEX_EDGE, G_RM_AA_ZB_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B9B0[] = {
    gsDPSetRenderMode(G_RM_ZB_XLU_SURF, G_RM_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014B9D8[] = {
    gsDPSetRenderMode(G_RM_ZB_OVL_SURF, G_RM_ZB_OVL_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BA00[] = {
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BA20[] = {
    gsDPSetRenderMode(AA_EN | Z_CMP | Z_UPD | IM_RD | CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU | FORCE_BL |
                      GBL_c1(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA), AA_EN | Z_CMP | Z_UPD | IM_RD |
                      CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU | FORCE_BL |
                      GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA)),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BA48[] = {
    gsDPSetRenderMode(G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BA70[] = {
    gsDPSetRenderMode(G_RM_AA_TEX_EDGE, G_RM_AA_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BA98[] = {
    gsDPSetRenderMode(G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BAC0[] = {
    gsDPSetRenderMode(AA_EN | Z_CMP | Z_UPD | IM_RD | CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU | FORCE_BL |
                      GBL_c1(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA), AA_EN | Z_CMP | Z_UPD | IM_RD |
                      CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU | FORCE_BL |
                      GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA)),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BAE8[] = {
    gsDPSetRenderMode(IM_RD | CVG_DST_SAVE | ZMODE_XLU | FORCE_BL |
                      GBL_c1(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA), IM_RD | CVG_DST_SAVE | ZMODE_XLU |
                      FORCE_BL | GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA)),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BB10[] = {
    gsDPSetRenderMode(G_RM_ZB_CLD_SURF, G_RM_ZB_CLD_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BB38[] = {
    gsDPSetRenderMode(G_RM_CLD_SURF, G_RM_CLD_SURF2),
    gsDPSetCycleType(G_CYC_1CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BB60[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_OPA_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BB88[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_OPA_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BBB0[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_OPA_INTER2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BBD8[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BC00[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BC28[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_XLU_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BC50[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_XLU_INTER2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BC78[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_ZB_OPA_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BCA0[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_ZB_OPA_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BCC8[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_OPA_INTER2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsDPSetCombineMode(G_CC_SHADE, G_CC_SHADE),
    gsSPEndDisplayList(),
};

Gfx D_8014BCF8[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BD18[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BD40[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_ZB_XLU_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BD68[] = {
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BD88[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BDB0[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_OPA_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_CULL_BACK | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BDD8[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BE00[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_AA_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BE28[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_ZB_CLD_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BE50[] = {
    gsDPSetRenderMode(G_RM_PASS, G_RM_CLD_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BE78[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_OPA_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BEA0[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_OPA_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BEC8[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_OPA_INTER2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BEF0[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BF18[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BF40[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_XLU_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BF68[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_XLU_INTER2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BF90[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_ZB_OPA_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BFB8[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_ZB_OPA_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014BFE0[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_OPA_INTER2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C008[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C028[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C050[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_ZB_XLU_DECAL2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C078[] = {
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C098[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C0C0[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_OPA_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C0E8[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_TEX_EDGE2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C110[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_XLU_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_SHADE | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C138[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_ZB_CLD_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

Gfx D_8014C160[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_CLD_SURF2),
    gsDPSetCycleType(G_CYC_2CYCLE),
    gsSPClearGeometryMode(G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH),
    gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_LIGHTING | G_SHADING_SMOOTH),
    gsSPEndDisplayList(),
};

s32 mdl_renderTaskBasePriorities[RENDER_MODE_COUNT] = {
    [RENDER_MODE_SURF_SOLID_AA_ZB_LAYER0]   = -100000,
    [RENDER_MODE_SURFACE_OPA]               = 1000000,
    [RENDER_MODE_2]                         = 1000000,
    [RENDER_MODE_SURFACE_OPA_NO_AA]         = 1000000,
    [RENDER_MODE_SURFACE_OPA_NO_ZB]         =       0,
    [RENDER_MODE_DECAL_OPA]                 = 1000000,
    [RENDER_MODE_6]                         = 1000000,
    [RENDER_MODE_DECAL_OPA_NO_AA]           = 1000000,
    [RENDER_MODE_8]                         =       0,
    [RENDER_MODE_INTERSECTING_OPA]          = 1000000,
    [RENDER_MODE_A]                         = 1000000,
    [RENDER_MODE_B]                         = 1000000,
    [RENDER_MODE_C]                         =       0,
    [RENDER_MODE_ALPHATEST]                 = 1000000,
    [RENDER_MODE_E]                         = 1000000,
    [RENDER_MODE_ALPHATEST_ONESIDED]        = 1000000,
    [RENDER_MODE_ALPHATEST_NO_ZB]           =       0,
    [RENDER_MODE_SURFACE_XLU_LAYER1]        = 8000000,
    [RENDER_MODE_12]                        = 8000000,
    [RENDER_MODE_SURFACE_XLU_NO_AA]         = 8000000,
    [RENDER_MODE_SURFACE_XLU_NO_ZB]         =       0,
    [RENDER_MODE_SURFXLU_ZB_ZUPD]           = 8000000,
    [RENDER_MODE_SURFACE_XLU_LAYER2]        = 7500000,
    [RENDER_MODE_17]                        = 7500000,
    [RENDER_MODE_18]                        = 7500000,
    [RENDER_MODE_19]                        =       0,
    [RENDER_MODE_DECAL_XLU]                 = 7000000,
    [RENDER_MODE_1B]                        = 7000000,
    [RENDER_MODE_DECAL_XLU_NOAA]            = 7000000,
    [RENDER_MODE_1D]                        = 7000000,
    [RENDER_MODE_1E]                        = 6500000,
    [RENDER_MODE_1F]                        = 6500000,
    [RENDER_MODE_SHADOW]                    = 6500000,
    [RENDER_MODE_21]                        =       0,
    [RENDER_MODE_SURFACE_XLU_LAYER3]        = 6000000,
    [RENDER_MODE_23]                        = 6000000,
    [RENDER_MODE_24]                        = 6000000,
    [RENDER_MODE_25]                        =       0,
    [RENDER_MODE_INTERSECTING_XLU]          = 5500000,
    [RENDER_MODE_27]                        = 5500000,
    [RENDER_MODE_28]                        = 5500000,
    [RENDER_MODE_SURFXLU_AA_ZB_ZUPD]        = 8000000,
    [RENDER_MODE_2A]                        = 4000000,
    [RENDER_MODE_2B]                        = 4250000,
    [RENDER_MODE_2C]                        = 4500000,
    [RENDER_MODE_2D]                        = 4500000,
    [RENDER_MODE_CLOUD]                     = 8000000,
    [RENDER_MODE_CLOUD_NO_ZB]               =  700000,
};

s8 D_8014C248[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

// BSS
extern ModelCustomGfxBuilderList* gCurrentCustomModelGfxBuildersPtr;
extern s32 D_801512BC;
extern s32 D_80151304;
extern s32 D_80151344;
extern s32 entity_numEntities;
extern s32 gEntityHeapBase;
extern HudCacheEntry* gHudElementCacheTableRaster;
extern HudCacheEntry* gHudElementCacheTablePalette;
extern ModelNode** gCurrentModelTreeRoot;
extern ModelTransformGroupList* gCurrentTransformGroups;
extern s8 gMsgGlobalWaveCounter[0x4];
extern ModelCustomGfxList* gCurrentCustomModelGfxPtr;
extern s32 gLastCreatedEntityIndex;
extern GameMode gMainGameState[2]; // TODO rename

extern s32 gEntityHeapBottom;
extern s32 entity_numShadows;
extern s32 entity_area_specific_data_is_loaded;
extern s32 entity_updateCounter;

extern s32 wEntityDataLoadedSize;
extern s32 bEntityDataLoadedSize;

extern EntityBlueprint* wEntityBlueprint[30];
extern EntityBlueprint* bEntityBlueprint[4];

extern s32* D_801516F4;

extern TextureHeader gCurrentTileDescriptor;

extern ModelList wModelList;
extern ModelList bModelList;

extern ModelTransformGroupList wTransformGroups;
extern ModelTransformGroupList bTransformGroups;

extern ModelCustomGfxList wModelSpecialDls;
extern ModelCustomGfxList bModelSpecialDls;

extern ModelCustomGfxBuilderList wCustomModelGfxBuilders;
extern ModelCustomGfxBuilderList bCustomModelGfxBuilders;
extern ModelLocalVertexCopyList wModelLocalVtxBuffers;
extern ModelLocalVertexCopyList bModelLocalVtxBuffers;
extern ModelLocalVertexCopyList* gCurrentModelLocalVtxBuffers;

extern ModelNode* D_80152214;
extern ModelNode* D_80152218;
extern ModelTreeInfoList D_80152220;
extern ModelTreeInfoList D_80152A20;

extern s8 wBackgroundFogMode;
extern s8 bBackgroundFogMode;
extern s32 mdl_treeIterPos;
extern Fog wFogSettings;
extern Fog bFogSettings;
extern Fog* gCurrentFogSettings;
extern s32 texPannerMainU[MAX_TEX_PANNERS];
extern s32 texPannerMainV[MAX_TEX_PANNERS];
extern s32 texPannerAuxU[MAX_TEX_PANNERS];
extern s32 texPannerAuxV[MAX_TEX_PANNERS];
extern void* mdl_nextTextureAddress;
extern u16 mdl_currentTransformGroupChildIndex;
extern u16 D_80153226;
extern ModelNode* D_80153370;
extern u16 D_80153374;
extern u16 D_80153376;
extern u16 D_8015336E;
extern RenderTask* mdl_renderTaskLists[3];
extern s32 mdl_renderTaskQueueIdx;
extern s32 mdl_renderTaskCount;

extern TextureHandle mdl_textureHandles[128];

extern Addr BattleEntityHeapBottom; // todo ???

extern u16 depthCopyBuffer[16];

void update_shadows(void);
s32 step_entity_commandlist(Entity* entity);
void entity_swizzle_anim_pointers(EntityBlueprint* entityData, void* baseAnim, void* baseGfx);
void render_shadows(void);
void update_entity_transform_matrix(Entity* entity);
void update_shadow_transform_matrix(Shadow* shadow);
void update_entity_inverse_rotation_matrix(Entity* entity);
void delete_entity(s32 entityIndex);
void delete_entity_and_unload_data(s32 entityIndex);
void _delete_shadow(s32 shadowIndex);
void reload_world_entity_data(void);
s32 entity_get_collision_flags(Entity* entity);
void entity_free_static_data(EntityBlueprint* data);
s32 create_entity_shadow(Entity* entity, f32 x, f32 y, f32 z);
void update_entity_shadow_position(Entity* entity);
void func_80117D00(Model* model);
void appendGfx_model_group(void* model);
void render_transform_group_node(ModelNode* node);
void render_transform_group(void* group);
void func_801180E8(TextureHeader*, Gfx**, IMG_PTR raster, PAL_PTR palette, IMG_PTR auxRaster, PAL_PTR auxPalette, u8, u8, u16, u16);
void load_model_transforms(ModelNode* model, ModelNode* parent, Matrix4f mdlTxMtx, s32 treeDepth);
s32 is_identity_fixed_mtx(Mtx* mtx);
void build_custom_gfx(void);

MATCHING_BSS(0x3A0);

void update_entities(void) {
    s32 i;

    D_801512BC = 0;
    entity_numEntities = 0;
    entity_updateCounter++;

    for (i = 0; i < MAX_ENTITIES; i++) {
        Entity* entity = get_entity_by_index(i);

        if (entity != NULL) {
            entity_numEntities++;

            if (!(entity->flags & ENTITY_FLAG_SKIP_UPDATE)) {
                if (entity->flags & ENTITY_FLAG_BOUND_SCRIPT_DIRTY) {
                    entity->flags &= ~ENTITY_FLAG_BOUND_SCRIPT_DIRTY;
                    if (!(entity->flags & ENTITY_FLAG_8000)) {
                        entity->flags |= ENTITY_FLAG_2000000;
                    }
                    entity->boundScript = start_script(entity->boundScriptBytecode, EVT_PRIORITY_A, EVT_FLAG_RUN_IMMEDIATELY);
                }

                if (entity->flags & ENTITY_FLAG_2000000) {
                    if (does_script_exist(entity->boundScript->id)) {
                        if (entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                            update_model_animator(entity->virtualModelIndex);
                        } else {
                            exec_entity_model_commandlist(entity->virtualModelIndex);
                        }

                        if (entity->flags & ENTITY_FLAG_ALWAYS_FACE_CAMERA) {
                            entity->rotation.y = -gCameras[gCurrentCameraID].currentYaw;
                        }

                        if (!(entity->flags & ENTITY_FLAG_SKIP_UPDATE_TRANSFORM_MATRIX)) {
                            update_entity_transform_matrix(entity);
                        }
                        continue;
                    } else {
                        entity->flags &= ~ENTITY_FLAG_2000000;
                    }
                }

                if (entity->collisionTimer == 0) {
                    entity->collisionFlags = entity_get_collision_flags(entity);

                    if (entity->collisionFlags) {
                        EntityCallback handleCollision = entity->blueprint->fpHandleCollision;

                        if (handleCollision != NULL && handleCollision(entity) != 0) {
                            entity->collisionTimer = 10;
                            entity->flags |= ENTITY_FLAG_DETECTED_COLLISION;
                        }
                    }
                } else {
                    entity->collisionTimer--;
                    if (entity->flags & ENTITY_FLAG_CONTINUOUS_COLLISION) {
                        if (entity->collisionTimer == 0) {
                            entity->flags &= ~(ENTITY_FLAG_DISABLE_COLLISION | ENTITY_FLAG_CONTINUOUS_COLLISION);
                        } else {
                            entity->flags |= ENTITY_FLAG_DISABLE_COLLISION;
                        }
                    } else if (entity->collisionTimer == 0) {
                        entity->flags &= ~ENTITY_FLAG_DETECTED_COLLISION;
                        entity->flags &= ~ENTITY_FLAG_PARTNER_COLLISION;
                        entity->collisionFlags = 0;
                    }
                }

                if (entity->flags & ENTITY_FLAG_ALWAYS_FACE_CAMERA) {
                    entity->rotation.y = -gCameras[gCurrentCameraID].currentYaw;
                }

                if (!gGameStatusPtr->disableScripts) {
                    if (entity->updateScriptCallback != NULL) {
                        entity->updateScriptCallback(entity);
                    }

                    if (entity->scriptReadPos != NULL) {
                        if (entity->scriptDelay != 0) {
                            entity->scriptDelay--;
                            if (entity->scriptDelay == 0) {
                                while (step_entity_commandlist(entity));
                            }
                        }
                    }
                }

                if (!(entity->flags & ENTITY_FLAG_SKIP_UPDATE_TRANSFORM_MATRIX)) {
                    update_entity_transform_matrix(entity);
                }

                if (!(entity->flags & ENTITY_FLAG_DISABLE_COLLISION)) {
                    update_entity_inverse_rotation_matrix(entity);
                }

                if (entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    update_model_animator(entity->virtualModelIndex);
                } else {
                    exec_entity_model_commandlist(entity->virtualModelIndex);
                }

                if (entity->shadowIndex >= 0) {
                    update_entity_shadow_position(entity);
                }

                if (entity->flags & ENTITY_FLAG_PENDING_INSTANCE_DELETE) {
                    delete_entity(entity->listIndex);
                }

                if (entity->flags & ENTITY_FLAG_PENDING_FULL_DELETE) {
                    delete_entity_and_unload_data(entity->listIndex);
                }
            }
        }
    }

    update_shadows();
    gCurrentHiddenPanels.tryFlipTrigger = FALSE;
}

void update_shadows(void) {
    s32 i;

    entity_numShadows = 0;

    for (i = 0; i < MAX_SHADOWS; i++) {
        Shadow* shadow = get_shadow_by_index(i);

        if (shadow != NULL) {
            entity_numShadows++;

            if (!(shadow->flags & ENTITY_FLAG_SKIP_UPDATE)) {
                if (shadow->flags & ENTITY_FLAG_ALWAYS_FACE_CAMERA) {
                    shadow->rotation.y = -gCameras[gCurrentCameraID].currentYaw;
                }

                update_shadow_transform_matrix(shadow);

                if (shadow->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    update_model_animator(shadow->entityModelID);
                } else {
                    exec_entity_model_commandlist(shadow->entityModelID);
                }

                if (shadow->flags & ENTITY_FLAG_PENDING_INSTANCE_DELETE) {
                    _delete_shadow(shadow->listIndex);
                }
            }
        }
    }
}

void set_entity_commandlist(Entity* entity, s32* entityScript) {
    entity->scriptReadPos = entityScript;
    entity->scriptDelay = 1;
    entity->savedReadPos[0] = entity->scriptReadPos;
}

s32 step_entity_commandlist(Entity* entity) {
    s32* args = entity->scriptReadPos;
    s32 ret;
    s32 labelId;
    void (*tempfunc)(Entity*);

    switch (*args++) {
        case ENTITY_SCRIPT_OP_End:
            entity->scriptDelay = -1;
            entity->updateScriptCallback = NULL;
            entity->scriptReadPos = NULL;
            ret = FALSE;
            break;
        case ENTITY_SCRIPT_OP_Jump:
            entity->scriptReadPos = (s32*)*args;
            entity->scriptDelay = 1;
            entity->savedReadPos[0] = entity->scriptReadPos;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_Call:
            tempfunc = (void (*)(Entity*))(*args++);
            entity->scriptReadPos = args;
            (tempfunc)(entity);
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_SetCallback:
            entity->scriptDelay = *args++;
            entity->updateScriptCallback = (s32 (*)(Entity*)) *args++;
            entity->scriptReadPos = args++;
            ret = FALSE;
            break;
        case ENTITY_SCRIPT_OP_Goto:
            entity->scriptReadPos = entity->savedReadPos[*args];
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_Label:
            labelId = *args++;
            entity->savedReadPos[labelId] = args;
            entity->scriptReadPos = args;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_RestartBoundScript:
            if (entity->boundScriptBytecode != NULL) {
                entity->flags |= ENTITY_FLAG_BOUND_SCRIPT_DIRTY;
            }
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_SetFlags:
            entity->flags |= *args++;
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_ClearFlags:
            entity->flags &= ~*args++;
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_PlaySound:
            sfx_play_sound(*args++);
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        default:
            args++;
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
    }
    return ret;
}

void exec_entity_commandlist(Entity* entity) {
    while (step_entity_commandlist(entity));
}

void func_8010FD98(void* arg0, s32 alpha) {
    if (alpha >= 255) {
        gDPSetRenderMode(gMainGfxPos++, G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2);
        gDPSetCombineMode(gMainGfxPos++, G_CC_MODULATEIA, G_CC_MODULATEIA);
    } else {
        gDPSetCombineLERP(gMainGfxPos++, 0, 0, 0, TEXEL0, PRIMITIVE, 0, TEXEL0, 0, 0, 0, 0, TEXEL0, TEXEL0, 0, PRIMITIVE, 0);
        gDPSetPrimColor(gMainGfxPos++, 0, 0, 0, 0, 0, alpha);
    }
}

void func_8010FE44(void* arg0) {
    func_8010FD98(arg0, D_8014AFB0);
}

void entity_model_set_shadow_color(void* data) {
    s32 alpha = (s32)data;

    gDPSetCombineLERP(gMainGfxPos++, 0, 0, 0, 0, PRIMITIVE, 0, TEXEL0, 0, 0, 0, 0, 0, TEXEL0, 0, PRIMITIVE, 0);
    gDPSetPrimColor(gMainGfxPos++, 0, 0, 0, 0, 0, alpha);
}

void render_entities(void) {
    s32 i;

    for (i = 0; i < MAX_ENTITIES; i++) {
        Entity* entity = get_entity_by_index(i);

        if (entity != NULL) {
            if (!gGameStatusPtr->isBattle) {
                if (gEntityHideMode != ENTITY_HIDE_MODE_0 &&
                    !(entity->flags & ENTITY_FLAG_IGNORE_DISTANCE_CULLING) &&
                    dist2D(gPlayerStatusPtr->position.x,
                           gPlayerStatusPtr->position.z,
                           entity->position.x,
                           entity->position.z) > 200.0f
                ) {
                    continue;
                }

                if (gEntityHideMode == ENTITY_HIDE_MODE_1) {
                    if (!(entity->flags & ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1)) {
                        continue;
                    }
                } else if (gEntityHideMode == ENTITY_HIDE_MODE_2) {
                    if (!(entity->flags & ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2)) {
                        continue;
                    }
                }
            }

            if (!(entity->flags & ENTITY_FLAG_HIDDEN)) {
                if (entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    if (D_8014AFB0 == 0xFF) {
                        if (entity->renderSetupFunc != NULL) {
                            set_animator_render_callback(
                                entity->virtualModelIndex,
                                (void*)(u32) entity->listIndex,
                                (void (*)(void*)) entity->renderSetupFunc
                            );
                        }
                    } else {
                        set_animator_render_callback(
                            entity->virtualModelIndex,
                            (void*)(u32) entity->listIndex,
                            func_8010FE44
                        );
                    }

                    if (entity->gfxBaseAddr == NULL) {
                        render_animated_model(entity->virtualModelIndex, &entity->transformMatrix);
                    } else {
                        render_animated_model_with_vertices(entity->virtualModelIndex,
                                      &entity->transformMatrix,
                                      entity->vertexSegment,
                                      entity->gfxBaseAddr);
                    }
                } else {
                    if (D_8014AFB0 == 0xFF) {
                        if (entity->renderSetupFunc != NULL) {
                            bind_entity_model_setupGfx(
                                entity->virtualModelIndex,
                                (void*)(u32) entity->listIndex,
                                (void (*)(void*)) entity->renderSetupFunc
                            );
                        } else {
                            get_entity_model(entity->virtualModelIndex)->fpSetupGfxCallback = NULL;
                        }
                    } else {
                        bind_entity_model_setupGfx(entity->virtualModelIndex, (void*)(u32)entity->listIndex, func_8010FE44);
                    }

                    if (entity->gfxBaseAddr == NULL) {
                        draw_entity_model_A(entity->virtualModelIndex, &entity->transformMatrix);
                    } else {
                        draw_entity_model_B(entity->virtualModelIndex,
                                               &entity->transformMatrix,
                                               entity->vertexSegment,
                                               entity->gfxBaseAddr);
                    }
                }
            }
        }
    }

    render_shadows();
}

void render_shadows(void) {
    s32 i;

    for (i = 0; i < MAX_SHADOWS; i++) {
        Shadow* shadow = get_shadow_by_index(i);

        if (shadow != NULL) {
            if (shadow->flags & ENTITY_FLAG_HIDDEN) {
                if (shadow->flags & ENTITY_FLAG_FADING_AWAY) {
                    shadow->alpha -= 20;
                    if (shadow->alpha <= 20) {
                        shadow->flags |= ENTITY_FLAG_PENDING_INSTANCE_DELETE;
                    }
                }
            } else if (shadow->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                if (shadow->vertexArray == NULL) {
                    render_animated_model(shadow->entityModelID, &shadow->transformMatrix);
                } else {
                    render_animated_model_with_vertices(shadow->entityModelID,
                                  &shadow->transformMatrix,
                                  shadow->vertexSegment,
                                  shadow->vertexArray);
                }
            } else {
                if (shadow->flags & ENTITY_FLAG_FADING_AWAY) {
                    shadow->alpha -= 20;
                    if (shadow->alpha <= 20) {
                        shadow->flags |=  ENTITY_FLAG_PENDING_INSTANCE_DELETE;
                    }
                }

                bind_entity_model_setupGfx(shadow->entityModelID, (void*)(u32)shadow->alpha, entity_model_set_shadow_color);

                if (shadow->vertexArray == NULL) {
                    draw_entity_model_A(shadow->entityModelID, &shadow->transformMatrix);
                } else {
                    draw_entity_model_B(shadow->entityModelID,
                                           &shadow->transformMatrix,
                                           shadow->vertexSegment,
                                           shadow->vertexArray);
                }
            }
        }
    }
}

void update_entity_transform_matrix(Entity* entity) {
    Matrix4f sp18;
    Matrix4f sp58;
    Matrix4f sp98;
    Matrix4f spD8;
    Matrix4f sp118;
    Matrix4f sp158;
    Matrix4f sp198;

    if (entity->updateMatrixOverride != NULL) {
        entity->updateMatrixOverride(entity);
        return;
    }

    guTranslateF(sp58, entity->position.x, entity->position.y, entity->position.z);
    guRotateF(spD8, entity->rotation.x, 1.0f, 0.0f, 0.0f);
    guRotateF(sp118, entity->rotation.y, 0.0f, 1.0f, 0.0f);
    guRotateF(sp158, entity->rotation.z, 0.0f, 0.0f, 1.0f);
    guMtxCatF(sp158, spD8, sp18);
    guMtxCatF(sp18, sp118, sp98);
    guScaleF(sp198, entity->scale.x, entity->scale.y, entity->scale.z);
    guMtxCatF(sp198, sp98, sp18);
    guMtxCatF(sp18, sp58, sp98);
    guMtxF2L(sp98, &entity->transformMatrix);
}

void update_shadow_transform_matrix(Shadow* shadow) {
    Matrix4f sp18;
    Matrix4f sp58;
    Matrix4f sp98;
    Matrix4f spD8;
    Matrix4f sp118;
    Matrix4f sp158;
    Matrix4f sp198;

    guTranslateF(sp58, shadow->position.x, shadow->position.y, shadow->position.z);
    guRotateF(sp118, shadow->rotation.x, 1.0f, 0.0f, 0.0f);
    guRotateF(spD8, shadow->rotation.y, 0.0f, 1.0f, 0.0f);
    guRotateF(sp158, shadow->rotation.z, 0.0f, 0.0f, 1.0f);
    guMtxCatF(sp158, sp118, sp98);
    guMtxCatF(spD8, sp98, sp98);
    guScaleF(sp198, shadow->scale.x, shadow->scale.y, shadow->scale.z);
    guMtxCatF(sp198, sp98, sp18);
    guMtxCatF(sp18, sp58, sp98);
    guMtxF2L(sp98, &shadow->transformMatrix);
}

void update_entity_inverse_rotation_matrix(Entity* entity) {
    Matrix4f sp18;
    Matrix4f sp58;

    guRotateF(sp18, -entity->rotation.y, 0.0f, 1.0f, 0.0f);
    guRotateF(sp58, -entity->rotation.z, 0.0f, 0.0f, 1.0f);
    guMtxCatF(sp18, sp58, sp18);
    guRotateF(sp58, -entity->rotation.x, 1.0f, 0.0f, 0.0f);
    guMtxCatF(sp18, sp58, entity->inverseTransformMatrix);

    entity->effectiveSize = sqrtf(((SQ(entity->aabb.x) + SQ(entity->aabb.z)) * 0.25f) + SQ(entity->aabb.y));
}

Entity* get_entity_by_index(s32 index) {
    return (*gCurrentEntityListPtr)[index & 0xFFF];
}

Shadow* get_shadow_by_index(s32 index) {
    return (*gCurrentShadowListPtr)[index & 0xFFF];
}

EntityList* get_entity_list(void) {
    EntityList* ret;

    if (!gGameStatusPtr->isBattle) {
        ret = &gWorldEntityList;
    } else {
        ret = &gBattleEntityList;
    }
    return ret;
}

ShadowList* get_shadow_list(void) {
    ShadowList* ret;

    if (!gGameStatusPtr->isBattle) {
        ret = &gWorldShadowList;
    } else {
        ret = &gBattleShadowList;
    }
    return ret;
}

s32 entity_start_script(Entity* entity) {
    if (entity->boundScriptBytecode != NULL) {
        entity->flags |= ENTITY_FLAG_BOUND_SCRIPT_DIRTY;
        return 1;
    }
    return 0;
}

u32 get_entity_type(s32 index) {
    Entity* entity = get_entity_by_index(index);

    if (entity == NULL) {
        return -1;
    } else {
        return entity->blueprint->entityType;
    }
}

void delete_entity(s32 entityIndex) {
    Entity* entity = get_entity_by_index(entityIndex);

    if (entity->dataBuf.any != NULL) {
        heap_free(entity->dataBuf.any);
    }

    if (!(entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        free_entity_model_by_index(entity->virtualModelIndex);
    } else {
        delete_model_animator(get_animator_by_index(entity->virtualModelIndex));
    }

    if (entity->shadowIndex >= 0) {
        Shadow* shadow = get_shadow_by_index(entity->shadowIndex);

        shadow->flags |= ENTITY_FLAG_FADING_AWAY;
    }

    heap_free((*gCurrentEntityListPtr)[entityIndex]);
    (*gCurrentEntityListPtr)[entityIndex] = NULL;
}

void delete_entity_and_unload_data(s32 entityIndex) {
    Entity* entity = get_entity_by_index(entityIndex);

    if (entity->dataBuf.any != NULL) {
        heap_free(entity->dataBuf.any);
    }

    if (!(entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        free_entity_model_by_index(entity->virtualModelIndex);
    } else {
        delete_model_animator(get_animator_by_index(entity->virtualModelIndex));
    }

    entity_free_static_data(entity->blueprint);

    if (entity->shadowIndex >= 0) {
        Shadow* shadow = get_shadow_by_index(entity->shadowIndex);

        shadow->flags |= ENTITY_FLAG_FADING_AWAY;
    }

    heap_free((*gCurrentEntityListPtr)[entityIndex]);
    (*gCurrentEntityListPtr)[entityIndex] = NULL;
}

void _delete_shadow(s32 shadowIndex) {
    Shadow* shadow = get_shadow_by_index(shadowIndex);

    free_entity_model_by_index(shadow->entityModelID);
    heap_free((*gCurrentShadowListPtr)[shadowIndex]);
    (*gCurrentShadowListPtr)[shadowIndex] = NULL;
}

s32 entity_get_collision_flags(Entity* entity) {
    u32 listIndex = entity->listIndex;
    s32 entityFlags = 0;
    u32 flag;

    if (entity->flags & ENTITY_FLAG_PARTNER_COLLISION) {
        entityFlags = ENTITY_COLLISION_PARTNER;
        entity->flags &= ~ENTITY_FLAG_PARTNER_COLLISION;
    }

    flag = gCollisionStatus.currentFloor;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_TOUCH_FLOOR;
    }

    flag = gCollisionStatus.lastTouchedFloor;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_LAST_FLOOR;
    }

    flag = gCollisionStatus.currentCeiling;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_TOUCH_CEILING;
    }

    flag = gCollisionStatus.pushingAgainstWall;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_PUSHING_AGAINST;
    }

    flag = gCollisionStatus.lastWallHammered;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_HAMMER;
    }

    flag = gCollisionStatus.currentWall;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag && gPlayerStatusPtr->pressedButtons & BUTTON_A) {
        entityFlags |= ENTITY_COLLISION_PLAYER_TOUCH_WALL;
    }

    return entityFlags;
}

s32 entity_try_partner_interaction_trigger(s32 entityIdx) {
    s32 interacted = FALSE;
    u32 entityType = get_entity_type(entityIdx);
    s32 partnerID = get_current_partner_id();
    Entity* entity;

    switch (partnerID) {
        case PARTNER_BOMBETTE:
            switch (entityType) {
                default:
                    return FALSE;
                case ENTITY_TYPE_BLUE_SWITCH:
                case ENTITY_TYPE_RED_SWITCH:
                case ENTITY_TYPE_MULTI_TRIGGER_BLOCK:
                case ENTITY_TYPE_BRICK_BLOCK:
                case ENTITY_TYPE_MULTI_COIN_BRICK:
                case ENTITY_TYPE_YELLOW_BLOCK:
                case ENTITY_TYPE_SINGLE_TRIGGER_BLOCK:
                case ENTITY_TYPE_HIDDEN_YELLOW_BLOCK:
                case ENTITY_TYPE_HIDDEN_RED_BLOCK:
                case ENTITY_TYPE_RED_BLOCK:
                case ENTITY_TYPE_HAMMER1_BLOCK:
                case ENTITY_TYPE_HAMMER1_BLOCK_TINY:
                case ENTITY_TYPE_SUPER_BLOCK:
                case ENTITY_TYPE_BOMBABLE_ROCK:
                    entity = get_entity_by_index(entityIdx);
                    entity->flags |= ENTITY_FLAG_PARTNER_COLLISION;
                    interacted = TRUE;
            }
            break;
        case PARTNER_KOOPER:
             switch (entityType) {
                default:
                    return FALSE;
                case ENTITY_TYPE_BLUE_SWITCH:
                case ENTITY_TYPE_RED_SWITCH:
                case ENTITY_TYPE_MULTI_TRIGGER_BLOCK:
                case ENTITY_TYPE_BRICK_BLOCK:
                case ENTITY_TYPE_MULTI_COIN_BRICK:
                case ENTITY_TYPE_YELLOW_BLOCK:
                case ENTITY_TYPE_SINGLE_TRIGGER_BLOCK:
                case ENTITY_TYPE_HIDDEN_YELLOW_BLOCK:
                case ENTITY_TYPE_HIDDEN_RED_BLOCK:
                case ENTITY_TYPE_RED_BLOCK:
                case ENTITY_TYPE_HEALING_BLOCK:
                case ENTITY_TYPE_1C:
                case ENTITY_TYPE_SAVE_POINT:
                case ENTITY_TYPE_SUPER_BLOCK:
                    entity = get_entity_by_index(entityIdx);
                    entity->flags |= ENTITY_FLAG_PARTNER_COLLISION;
                    interacted = TRUE;
            }
            break;
    }
    return interacted;
}

s32 test_player_entity_aabb(Entity* entity) {
    f32 yTemp = entity->position.y - (gPlayerStatus.position.y + gPlayerStatus.colliderHeight);
    f32 xCollRadius;
    f32 zCollRadius;
    f32 xDist;
    f32 zDist;

    if (yTemp > 0.0f || gPlayerStatus.colliderHeight + entity->aabb.y < fabsf(yTemp)) {
        return 0;
    }

    xCollRadius = (gPlayerStatus.colliderDiameter + entity->aabb.x) * 0.5;
    xDist = fabsf(gPlayerStatus.position.x - entity->position.x);
    zCollRadius = ((gPlayerStatus.colliderDiameter + entity->aabb.z) * 0.5);
    zDist = fabsf(gPlayerStatus.position.z - entity->position.z);

    if (xCollRadius < xDist || zCollRadius < zDist) {
        return 0;
    }

    return 1;
}

s32 is_player_action_state(s8 actionState) {
    return actionState == gPlayerStatus.actionState;
}

void entity_set_render_script(Entity* entity, EntityModelScript* cmdList) {
    if (!(entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        set_entity_model_render_command_list(entity->virtualModelIndex, cmdList);
    }
}

void entity_reset_collision(Entity* entity) {
    entity->collisionTimer = 0;
    entity->flags &= ~ENTITY_FLAG_DETECTED_COLLISION;
}

void load_area_specific_entity_data(void) {
    if (!entity_area_specific_data_is_loaded) {
        if (gGameStatusPtr->areaID == AREA_JAN || gGameStatusPtr->areaID == AREA_IWA) {
            dma_copy(entity_jan_iwa_ROM_START, entity_jan_iwa_ROM_END, (void*)AREA_SPECIFIC_ENTITY_VRAM);
        } else if (gGameStatusPtr->areaID == AREA_SBK || gGameStatusPtr->areaID == AREA_OMO) {
            dma_copy(entity_sbk_omo_ROM_START, entity_sbk_omo_ROM_END, (void*)AREA_SPECIFIC_ENTITY_VRAM);
        } else {
            dma_copy(entity_default_ROM_START, entity_default_ROM_END, (void*)AREA_SPECIFIC_ENTITY_VRAM);
        }

        entity_area_specific_data_is_loaded = TRUE;
    }
}

void clear_entity_data(s32 arg0) {
    s32 i;

    D_801516FC = 1;
    entity_numEntities = 0;
    entity_numShadows = 0;
    entity_updateCounter = 0;
    D_80151304 = 0;

    if (!gGameStatusPtr->isBattle) {
        gEntityHideMode = ENTITY_HIDE_MODE_0;
    }

    entity_area_specific_data_is_loaded = FALSE;
    gCurrentHiddenPanels.panelsCount = 0;
    gCurrentHiddenPanels.activateISpy = FALSE;
    if (!arg0) {
        D_80151344 = 0;
    }
    D_8014AFB0 = 0xFF;

    if (!gGameStatusPtr->isBattle) {
        wEntityDataLoadedSize = 0;
        for (i = 0; i < MAX_ENTITIES; i++) {
            wEntityBlueprint[i] = NULL;
        }
    } else {
        bEntityDataLoadedSize = 0;
        for (i = 0; i < 4; i++) {
            bEntityBlueprint[i] = NULL;
        }
    }

    if (!gGameStatusPtr->isBattle) {
        gEntityHeapBottom = WORLD_ENTITY_HEAP_BOTTOM;
        gEntityHeapBase = WORLD_ENTITY_HEAP_BASE;
    } else {
        gEntityHeapBottom = (s32) BattleEntityHeapBottom;
        gEntityHeapBase = gEntityHeapBottom + 0x3000;
    }

    gCurrentEntityListPtr = get_entity_list();
    gCurrentShadowListPtr = get_shadow_list();

    for (i = 0; i < MAX_ENTITIES; i++) {
        (*gCurrentEntityListPtr)[i] = NULL;
    }

    for (i = 0; i < MAX_SHADOWS; i++) {
        (*gCurrentShadowListPtr)[i] = NULL;
    }
}

void init_entity_data(void) {
    if (!gGameStatusPtr->isBattle) {
        gEntityHeapBottom = WORLD_ENTITY_HEAP_BOTTOM;
        gEntityHeapBase = WORLD_ENTITY_HEAP_BASE;
        reload_world_entity_data();
    } else {
        s32 i;

        for (i = 0; i < 4; i++) {
            bEntityBlueprint[i] = 0;
        }
        gEntityHeapBottom = (s32) BattleEntityHeapBottom;
        gEntityHeapBase = gEntityHeapBottom + 0x3000;
    }
    gCurrentEntityListPtr = get_entity_list();
    gCurrentShadowListPtr = get_shadow_list();
    entity_numEntities = 0;
    entity_numShadows = 0;
}

void reload_world_entity_data(void) {
    s32 i;
    s32 totalSize = 0;
    s32 temp1;
    s32 dataLength;
    void* gfxData;
    void* animData;

    for (i = 0; i < MAX_ENTITIES; i++) {
        EntityBlueprint* bp = wEntityBlueprint[i];
        if (bp == NULL) {
            break;
        }

        if (!(bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
            void* gfxData;

            dataLength = ((bp->dma.end - bp->dma.start) >> 2);
            gfxData = (void*)(gEntityHeapBase - totalSize * 4 - dataLength * 4);
            totalSize += dma_copy(bp->dma.start, bp->dma.end, gfxData) >> 2;
        } else {
            DmaEntry* dmaList = bp->dmaList;

            if (bp->entityType == ENTITY_TYPE_RESET_MUNCHLESIA) {
                gfxData = (void*)gEntityHeapBottom;
                temp1 = dma_copy(dmaList[0].start, dmaList[0].end, gfxData) >> 2;
                dma_copy(dmaList[1].start, dmaList[1].end, (void*)(gEntityHeapBottom + temp1 * 4)) >> 2;
                animData = (void*)(gEntityHeapBottom + temp1 * 4);
                entity_swizzle_anim_pointers(bp, animData, gfxData);
            } else {
                s32 temp5;
                s32 q;

                dataLength = ((dmaList[0].end - dmaList[0].start) >> 2);
                q = gEntityHeapBase - totalSize * 4;
                gfxData = (void*)(q - dataLength * 4);
                totalSize += dma_copy(dmaList[0].start, dmaList[0].end, gfxData) >> 2;

                dataLength = ((dmaList[1].end - dmaList[1].start) >> 2);
                q = gEntityHeapBase - totalSize * 4;
                animData = (void*)(q - dataLength * 4);
                totalSize += dma_copy(dmaList[1].start, dmaList[1].end, animData) >> 2;

                entity_swizzle_anim_pointers(bp, animData, gfxData);
            }
        }
    }
}

void entity_swizzle_anim_pointers(EntityBlueprint* entityData, void* baseAnim, void* baseGfx) {
    StaticAnimatorNode* node;
    s32* ptr = (s32*)((s32)baseAnim + (s32)entityData->modelAnimationNodes);

    while (TRUE) {
        if (*ptr == -1) {
            *ptr = 0;
            return;
        }
        node = (StaticAnimatorNode*)((s32)baseAnim + ((*ptr) & 0xFFFF));
        *ptr++ = (s32)node;

        if ((s32)node->displayList != -1) {
            node->displayList = (Gfx*)((s32)baseGfx + ((s32)(node->displayList) & 0xFFFF));
        } else {
            node->displayList = NULL;
        }

        if ((s32)node->sibling != -1) {
            node->sibling = (StaticAnimatorNode*)((s32)baseAnim + ((s32)(node->sibling) & 0xFFFF));
        } else {
            node->sibling = NULL;
        }

        if ((s32)node->child != -1) {
            node->child = (StaticAnimatorNode*)((s32)baseAnim + ((s32)(node->child) & 0xFFFF));
        } else {
            node->child = NULL;
        }

        if ((s32)node->vtxList != -1) {
            node->vtxList = (Vtx*)((s32)baseGfx + ((s32)(node->vtxList) & 0xFFFFF));
        } else {
            node->vtxList = NULL;
        }
    }
}

s32 is_entity_data_loaded(Entity* entity, EntityBlueprint* blueprint, s32* loadedStart, s32* loadedEnd) {
    EntityBlueprint** blueprints;
    s32 i;
    s32 ret;
    s32 size;
    DmaEntry* entDmaList;

    *loadedStart = 0;
    *loadedEnd = 0;
    ret = FALSE;

    if (!gGameStatusPtr->isBattle) {
        blueprints = wEntityBlueprint;
    } else {
        blueprints = bEntityBlueprint;
    }

    for (i = 0; i < MAX_ENTITIES; i++, blueprints++) {
        EntityBlueprint* bp = *blueprints;
        if (bp == NULL) {
            blueprints[0] = blueprint;
            blueprints[1] = NULL;
            ret = TRUE;
            if (blueprint->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                s32 size;
                entDmaList = blueprint->dmaList;
                size = (entDmaList[0].end - entDmaList[0].start) >> 2;
                *loadedEnd = *loadedStart + size;
            }
            break;
        } else {
            DmaEntry* bpDmaList = bp->dmaList;
            do {} while (0); // TODO find better match
            entDmaList = blueprint->dmaList;
            if (bpDmaList == entDmaList) {
                if (blueprint->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    s32 size = (bpDmaList[0].end - bpDmaList[0].start) >> 2;
                    *loadedEnd = *loadedStart + size;
                }
                break;
            } else if (bp == blueprint) {
                if (bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    s32 size = (entDmaList[0].end - entDmaList[0].start) >> 2;
                    *loadedEnd = *loadedStart + size;
                }
                break;
            } else {
                if (bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    s32 size = (bpDmaList[0].end - bpDmaList[0].start) >> 2;
                    *loadedEnd = *loadedStart = *loadedStart + size;
                    size = (bpDmaList[1].end - bpDmaList[1].start) >> 2;
                    *loadedStart = *loadedStart + size;
                } else {
                    *loadedStart += (bp->dma.end - bp->dma.start) >> 2;
                }
            }
        }
    }

    return ret;
}

void load_simple_entity_data(Entity* entity, EntityBlueprint* bp, s32 listIndex) {
    s32 loadedStart;
    s32 loadedEnd;
    s32 entitySize;
    u32 temp;
    s32 totalSize;

    entity->vertexSegment = 0xA;
    if (!gGameStatusPtr->isBattle) {
        totalSize = wEntityDataLoadedSize;
    } else {
        totalSize = bEntityDataLoadedSize;
    }

    if (is_entity_data_loaded(entity, bp, &loadedStart, &loadedEnd)) {
        if (totalSize + ((bp->dma.end - bp->dma.start) >> 2) > 0x5FFCU) {
            get_entity_type(entity->listIndex);
            get_entity_type(entity->listIndex);
            PANIC();
        }
        entitySize = (bp->dma.end - bp->dma.start) >> 2;
        entity->gfxBaseAddr = (void*)(gEntityHeapBase - totalSize * 4 - entitySize * 4);
        totalSize += dma_copy(bp->dma.start, bp->dma.end, entity->gfxBaseAddr) >> 2;
        get_entity_type(entity->listIndex);
    } else {
        entitySize = (bp->dma.end - bp->dma.start) >> 2;
        entity->gfxBaseAddr = (void*)(gEntityHeapBase - loadedStart * 4 - entitySize * 4);
        get_entity_type(entity->listIndex);
    }

    if (!gGameStatusPtr->isBattle) {
        wEntityDataLoadedSize = totalSize;
    } else {
        bEntityDataLoadedSize = totalSize;
    }
}

void load_split_entity_data(Entity* entity, EntityBlueprint* entityData, s32 listIndex) {
    s32 swizzlePointers = FALSE;
    s32 loadedStart, loadedEnd;
    void* animBaseAddr;
    s16* animationScript;
    StaticAnimatorNode** animationNodes;
    s32 specialSize;
    s32 dma1size;
    s32 dma2size_1;
    s32 dma2size_2;
    s32 totalLoaded;

    if (entityData->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
        DmaEntry* dmaList = entityData->dmaList;
        entity->vertexSegment = 0xA;

        switch (entityData->entityType) {
            case ENTITY_TYPE_RESET_MUNCHLESIA:
            case ENTITY_TYPE_MUNCHLESIA_ENVELOP:
            case ENTITY_TYPE_MUNCHLESIA_CHEWING:
            case ENTITY_TYPE_MUNCHLESIA_RESET1:
                specialSize = 0x1000;
                break;
            case ENTITY_TYPE_MUNCHLESIA_GRAB:
            case ENTITY_TYPE_MUNCHLESIA_BEGIN_CHEW:
            case ENTITY_TYPE_MUNCHLESIA_SPIT_OUT:
            case ENTITY_TYPE_MUNCHLESIA_RESET2:
                specialSize = 0x2BC0;
                break;
            default:
                specialSize = 0;
                break;
        }

        if (specialSize != 0) {
            if (entityData->entityType == ENTITY_TYPE_RESET_MUNCHLESIA) {
                is_entity_data_loaded(entity, entityData, &loadedStart, &loadedEnd);
            }
            specialSize -= 0x1000;

            dma1size = dma_copy(dmaList[0].start, dmaList[0].end, (void*)(gEntityHeapBottom + specialSize * 4)) / 4;
            entity->gfxBaseAddr = (void*)(gEntityHeapBottom + specialSize * 4);
            dma_copy(dmaList[1].start, dmaList[1].end, (void*)(gEntityHeapBottom + specialSize * 4 + dma1size * 4));
            animBaseAddr = (void*)(gEntityHeapBottom + specialSize * 4 + dma1size * 4);
            swizzlePointers = TRUE;
        } else if (is_entity_data_loaded(entity, entityData, &loadedStart, &loadedEnd)) {
            if (!gGameStatusPtr->isBattle) {
                totalLoaded = wEntityDataLoadedSize;
            } else {
                totalLoaded = bEntityDataLoadedSize;
            }

            if ((totalLoaded + ((dmaList[0].end - dmaList[0].start) >> 2)) > 0x5FFCU) {
                get_entity_type(entity->listIndex);
                PANIC();
            }

            if ((totalLoaded + ((dmaList[1].end - dmaList[1].start) >> 2)) > 0x5FFCU) {
                get_entity_type(entity->listIndex);
                PANIC();
            }

            dma2size_1 = dma_copy(dmaList[0].start, dmaList[0].end, dmaList[0].start + ((gEntityHeapBase - totalLoaded * 4 - (s32)dmaList[0].end) >> 2) * 4) >> 2;
            entity->gfxBaseAddr = (void*)(gEntityHeapBase - totalLoaded * 4 - dma2size_1 * 4);
            totalLoaded += dma2size_1;

            dma2size_2 = dma_copy(dmaList[1].start, dmaList[1].end, dmaList[1].start + ((gEntityHeapBase - totalLoaded * 4 - (s32)dmaList[1].end) >> 2) * 4) >> 2;
            animBaseAddr = (void*)(gEntityHeapBase - totalLoaded * 4 - dma2size_2 * 4);
            totalLoaded += dma2size_2;
            get_entity_type(entity->listIndex);

            if (!gGameStatusPtr->isBattle) {
                wEntityDataLoadedSize = totalLoaded;
            } else {
                bEntityDataLoadedSize = totalLoaded;
            }
            swizzlePointers = TRUE;
        } else {
            u32 temp = (dmaList[0].end - dmaList[0].start) >> 2;
            entity->gfxBaseAddr = (void*)(gEntityHeapBase - loadedStart * 4 - temp * 4);
            temp = (dmaList[1].end - dmaList[1].start) >> 2;
            animBaseAddr = (void*)(gEntityHeapBase - loadedEnd * 4 - temp * 4);
            get_entity_type(entity->listIndex);
        }
    } else {
        entity->virtualModelIndex = create_model_animator(entityData->renderCommandList);
        load_model_animator_tree(entity->virtualModelIndex, entityData->modelAnimationNodes);
        update_model_animator(entity->virtualModelIndex);
        return;
    }
    animationScript = entityData->renderCommandList;
    animationNodes = (StaticAnimatorNode**)((s32)animBaseAddr + (s32)entityData->modelAnimationNodes);
    if (swizzlePointers) {
        entity_swizzle_anim_pointers(entityData, animBaseAddr, entity->gfxBaseAddr);
    }
    entity->virtualModelIndex = create_mesh_animator(animationScript, animBaseAddr);
    load_mesh_animator_tree(entity->virtualModelIndex, animationNodes);
    update_model_animator(entity->virtualModelIndex);
    entity->flags |= ENTITY_FLAG_HAS_ANIMATED_MODEL;
}

s32 func_80111790(EntityBlueprint* data) {
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentEntityListPtr); i++) {
        Entity* entity = (*gCurrentEntityListPtr)[i];

        if (entity != NULL && entity->blueprint->dma.start != NULL) {
            if (entity->blueprint->dma.start == entity->blueprint) {
                return TRUE;
            }
        }
    }
    return FALSE;
}

void entity_free_static_data(EntityBlueprint* data) {
    s32 freeSlot;
    s32 size;
    EntityBlueprint* bp;

    for (freeSlot = 0; freeSlot < MAX_ENTITIES; freeSlot++) {
        bp = wEntityBlueprint[freeSlot];
        if (bp == NULL) {
            break;
        }
    }

    if (freeSlot < MAX_ENTITIES) {
        bp = wEntityBlueprint[freeSlot - 1];
        if (bp == data) {
            if (bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                DmaEntry* dmaList = bp->dmaList;
                size = ((dmaList[0].end - dmaList[0].start) >> 2);
                size += ((dmaList[1].end - dmaList[1].start) >> 2);
                if (!func_80111790(bp)) {
                    wEntityBlueprint[freeSlot - 1] = NULL;
                    wEntityDataLoadedSize -= size;
                }
            } else {
                size = (bp->dma.end - bp->dma.start) >> 2;
                if (!func_80111790(bp)) {
                    wEntityBlueprint[freeSlot - 1] = NULL;
                    wEntityDataLoadedSize -= size;
                }
            }
        }
    }
}

s32 create_entity(EntityBlueprint* bp, ...) {
    va_list ap;
    EntityBlueprint** bpPtr;
    f32 x, y, z;
    f32 rotY;
    s32 listIndex;
    Entity* entity;
    s32* args;

    va_start(ap, bp);
    // needed to match
    bpPtr = &bp;
    *bpPtr = bp;

    load_area_specific_entity_data();

    x = va_arg(ap, s32);
    y = va_arg(ap, s32);
    z = va_arg(ap, s32);
    rotY = va_arg(ap, s32);

    args = &CreateEntityVarArgBuffer[2];

    *args-- = 0;
    *args-- = 0;
    *args = 0;

    for (listIndex = 3; listIndex > 0; listIndex--) {
        s32 arg = va_arg(ap, s32);

        if (arg == MAKE_ENTITY_END) {
            break;
        }
        *args++ = arg;
    }

    va_end(ap);

    for (listIndex = 0; listIndex < ARRAY_COUNT(*gCurrentEntityListPtr); listIndex++) {
        if ((*gCurrentEntityListPtr)[listIndex] == NULL) {
            break;
        }
    }

    if (listIndex >= MAX_ENTITIES) {
        return -1;
    }

    (*gCurrentEntityListPtr)[listIndex] = entity = heap_malloc(sizeof(*entity));
    mem_clear(entity, sizeof(*entity));
    entity->dataBuf.any = NULL;
    if (bp->typeDataSize != 0) {
        entity->dataBuf.any = heap_malloc(bp->typeDataSize);
        mem_clear(entity->dataBuf.any, bp->typeDataSize);
    }
    entity->type = bp->entityType;
    entity->listIndex = listIndex;
    entity->boundScript = NULL;
    entity->updateMatrixOverride = NULL;
    entity->blueprint = bp;
    entity->scriptReadPos = bp->updateEntityScript;
    entity->scriptDelay = entity->scriptReadPos != NULL ? 1 : 0;
    entity->savedReadPos[0] = bp->updateEntityScript;
    entity->updateScriptCallback = NULL;
    entity->flags = bp->flags | ENTITY_FLAG_CREATED;
    entity->collisionFlags = 0;
    entity->collisionTimer = 0;
    entity->renderSetupFunc = NULL;
    entity->position.x = x;
    entity->position.y = y;
    entity->position.z = z;
    entity->rotation.x = 0.0f;
    entity->rotation.y = rotY;
    entity->rotation.z = 0.0f;
    entity->scale.x = 1.0f;
    entity->scale.y = 1.0f;
    entity->scale.z = 1.0f;
    entity->aabb.x = bp->aabbSize[0];
    entity->aabb.y = bp->aabbSize[1];
    entity->aabb.z = bp->aabbSize[2];
    entity->unk_05 = 1;
    entity->unk_08 = -1;
    entity->alpha = 255;
    entity->virtualModelIndex = -1;
    entity->shadowIndex = -1;
    entity->gfxBaseAddr = NULL;

    if (!(bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        if (bp->dma.start != 0) {
            load_simple_entity_data(entity, bp, listIndex);
        }
        if (bp->renderCommandList != NULL) {
            entity->virtualModelIndex = load_entity_model(bp->renderCommandList);
            exec_entity_model_commandlist(entity->virtualModelIndex);
        }
    } else {
        load_split_entity_data(entity, bp, listIndex);
    }

    if (bp->entityType != ENTITY_TYPE_SHADOW && (entity->flags & (ENTITY_FLAG_FIXED_SHADOW_SIZE | ENTITY_FLAG_HAS_SHADOW))) {
        create_entity_shadow(entity, x, y, z);
    }

    switch (bp->entityType) {
        case ENTITY_TYPE_BLUE_SWITCH:
        case ENTITY_TYPE_RED_SWITCH:
        case ENTITY_TYPE_SIMPLE_SPRING:
        case ENTITY_TYPE_SCRIPT_SPRING:
        case ENTITY_TYPE_STAR_BOX_LAUCHER:
            entity->flags |= ENTITY_FLAG_4000;
            break;
    }

    if (bp->fpInit != NULL) {
        bp->fpInit(entity);
    }

    update_entity_transform_matrix(entity);
    return entity->listIndex;
}

s32 create_shadow_from_data(ShadowBlueprint* bp, f32 x, f32 y, f32 z) {
    Shadow* shadow;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentShadowListPtr); i++) {
        if ((*gCurrentShadowListPtr)[i] == NULL) {
            break;
        }
    }

    ASSERT(i < ARRAY_COUNT(*gCurrentShadowListPtr));

    shadow = heap_malloc(sizeof(*shadow));
    (*gCurrentShadowListPtr)[i] = shadow;
    mem_clear(shadow, sizeof(*shadow));
    shadow->listIndex = i;
    shadow->flags = bp->flags | ENTITY_FLAG_CREATED;
    shadow->alpha = 128;
    shadow->unk_06 = 0x80;
    shadow->position.x = x;
    shadow->position.y = y;
    shadow->position.z = z;
    shadow->scale.x = 1.0f;
    shadow->scale.y = 1.0f;
    shadow->scale.z = 1.0f;

    if (bp->animModelNode != NULL) {
        shadow->flags |= ENTITY_FLAG_HAS_ANIMATED_MODEL;
        shadow->entityModelID = create_model_animator(bp->renderCommandList);
        load_model_animator_tree(shadow->entityModelID, bp->animModelNode);
    } else {
        shadow->entityModelID = load_entity_model(bp->renderCommandList);
    }

    if (bp->onCreateCallback != NULL) {
        bp->onCreateCallback(shadow);
    }
    update_shadow_transform_matrix(shadow);
    return shadow->listIndex;
}

s32 MakeEntity(Evt* script, s32 isInitialCall) {
    Bytecode* args = script->ptrReadPos;
    EntityBlueprint* entityData;
    s32 x, y, z;
    s32 flags;
    s32 nextArg;
    s32 entityIndex;
    s32 endOfArgs;
    s32* varArgBufPos;

    if (isInitialCall != TRUE) {
        return ApiStatus_DONE2;
    }

    entityData = (EntityBlueprint*)evt_get_variable(script, *args++);
    varArgBufPos = &CreateEntityVarArgBuffer[2];
    endOfArgs = MAKE_ENTITY_END;
    x = evt_get_variable(script, *args++);
    y = evt_get_variable(script, *args++);
    z = evt_get_variable(script, *args++);
    flags = evt_get_variable(script, *args++);

    *varArgBufPos-- = 0;
    *varArgBufPos-- = 0;
    *varArgBufPos = 0;

    do {
        nextArg = evt_get_variable(script, *args++);

        if (nextArg != endOfArgs) {
            *varArgBufPos++ = nextArg;
        }
    } while (nextArg != endOfArgs);

    entityIndex = create_entity(entityData, x, y, z, flags, CreateEntityVarArgBuffer[0], CreateEntityVarArgBuffer[1], CreateEntityVarArgBuffer[2], endOfArgs);
    gLastCreatedEntityIndex = entityIndex;
    script->varTable[0] = entityIndex;
    return ApiStatus_DONE2;
}

ApiStatus SetEntityCullMode(Evt* script, s32 isInitialCall) {
    Entity* entity = get_entity_by_index(gLastCreatedEntityIndex);
    Bytecode* args = script->ptrReadPos;
    s32 mode = evt_get_variable(script, *args++);

    if (mode == 0) {
        entity->flags |= ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1;
    } else if (mode == 1) {
        entity->flags |= ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2;
    } else if (mode == 2) {
        entity->flags |= ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2 | ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1;
    } else {
        entity->flags |= ENTITY_FLAG_IGNORE_DISTANCE_CULLING | ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2 |
                         ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1;
    }
    return ApiStatus_DONE2;
}

ApiStatus UseDynamicShadow(Evt* script, s32 isInitialCall) {
    Entity* entity = get_entity_by_index(gLastCreatedEntityIndex);
    Bytecode* args = script->ptrReadPos;

    if (evt_get_variable(script, *args++)) {
        Shadow* shadow;

        entity->flags |= ENTITY_FLAG_HAS_DYNAMIC_SHADOW;
        shadow = get_shadow_by_index(entity->shadowIndex);
        shadow->flags |= ENTITY_FLAG_SHADOW_POS_DIRTY;
    } else {
        entity->flags &= ~ENTITY_FLAG_HAS_DYNAMIC_SHADOW;
    }

    return ApiStatus_DONE2;
}

ApiStatus AssignScript(Evt* script, s32 isInitialCall) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        EvtScript* toBind = (EvtScript*)evt_get_variable(script, *args++);

        get_entity_by_index(gLastCreatedEntityIndex)->boundScriptBytecode = toBind;
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

ApiStatus AssignSwitchFlag(Evt* script, s32 isInitialCall) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        s32 areaFlag = evt_get_variable(script, *args++);
        Entity* entity = get_entity_by_index(gLastCreatedEntityIndex);
        SwitchData* data = entity->dataBuf.swtch;

        data->areaFlagIndex = areaFlag;
        if (get_area_flag(areaFlag) != 0) {
            entity->flags |= ENTITY_FLAG_PENDING_INSTANCE_DELETE;
        }
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

ApiStatus AssignBlockFlag(Evt* script, s32 isInitialCall) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        s32 index = evt_get_variable_index(script, *args++);

        BlockData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.block;
        data->gameFlagIndex = index;

        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

ApiStatus AssignChestFlag(Evt* script, s32 isInitialCall) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        ChestData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.chest;
        data->gameFlagIndex = evt_get_variable_index(script, *args);

        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

ApiStatus AssignPanelFlag(Evt* script, s32 isInitialCall) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        HiddenPanelData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.hiddenPanel;

        data->pickupVar = evt_get_variable_index(script, *args++);
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

ApiStatus AssignCrateFlag(Evt* script, s32 isInitialCall) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        WoodenCrateData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.crate;

        data->globalFlagIndex = evt_get_variable_index(script, *args++);
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

s32 create_entity_shadow(Entity* entity, f32 x, f32 y, f32 z) {
    u16 staticFlags = entity->blueprint->flags;
    s32 type;
    s16 shadowIndex;

    if (staticFlags & ENTITY_FLAG_FIXED_SHADOW_SIZE) {
        if (staticFlags & ENTITY_FLAG_SQUARE_SHADOW) {
            type = 2;
        } else {
            type = 3;
        }
    } else {
        type = ((staticFlags >> 11) ^ 1) & 1;
    }

    shadowIndex = create_shadow_type(type, x, y, z);
    entity->shadowIndex = shadowIndex;

    get_shadow_by_index(shadowIndex)->flags |= ENTITY_FLAG_DARK_SHADOW | ENTITY_FLAG_SHADOW_POS_DIRTY;

    return entity->shadowIndex;
}

s32 create_shadow_type(s32 type, f32 x, f32 y, f32 z) {
    s32 isFixedSize = FALSE;
    ShadowBlueprint* bp = &CircularShadowA;
    s32 shadowIndex;

    switch (type) {
        case 2:
            isFixedSize = TRUE;
        case 0:
            bp = &CircularShadowA;
            break;
        case 3:
            isFixedSize = TRUE;
        case 1:
            bp = &SquareShadow;
            break;
        case 5:
            isFixedSize = TRUE;
        case 4:
            bp = &CircularShadowB;
            break;
    }

    shadowIndex = create_shadow_from_data(bp, x, y, z);

    if (isFixedSize) {
        get_shadow_by_index(shadowIndex)->flags |= ENTITY_FLAG_FIXED_SHADOW_SIZE;
    }

    return shadowIndex;
}

void delete_shadow(s32 shadowIndex) {
    _delete_shadow(shadowIndex);
}

void update_entity_shadow_position(Entity* entity) {
    Shadow* shadow = get_shadow_by_index(entity->shadowIndex);

    if (shadow != NULL) {
        f32 rayX;
        f32 rayY;
        f32 rayZ;
        f32 hitYaw;
        f32 hitPitch;
        f32 hitLength;
        f32 origHitLength;

        if (entity->alpha < 255) {
            shadow->alpha = entity->alpha / 2;
        } else {
            u8 alphaTemp;

            if (shadow->flags & ENTITY_FLAG_DARK_SHADOW) {
                alphaTemp = 160;
            } else {
                alphaTemp = 128;
            }
            shadow->alpha = alphaTemp;
        }

        if (!(entity->flags & ENTITY_FLAG_HAS_DYNAMIC_SHADOW)) {
            if (shadow->flags & ENTITY_FLAG_SHADOW_POS_DIRTY) {
                shadow->flags &= ~ENTITY_FLAG_SHADOW_POS_DIRTY;
            } else {
                return;
            }
        }

        rayX = entity->position.x;
        rayY = entity->position.y;
        rayZ = entity->position.z;

        if (!entity_raycast_down(&rayX, &rayY, &rayZ, &hitYaw, &hitPitch, &hitLength) && hitLength == 32767.0f) {
            hitLength = 0.0f;
        }

        origHitLength = hitLength;

        if (shadow->flags & ENTITY_FLAG_FIXED_SHADOW_SIZE) {
            hitLength = 212.5f;
            shadow->scale.x = entity->aabb.x / hitLength;
            shadow->scale.z = entity->aabb.z / hitLength;
        } else {
            hitLength = ((hitLength / 150.0f) + 0.95) * 250.0;
            shadow->scale.x = (entity->aabb.x / hitLength) * entity->scale.x;
            shadow->scale.z = (entity->aabb.z / hitLength) * entity->scale.z;
        }

        shadow->position.x = entity->position.x;
        shadow->position.z = entity->position.z;
        shadow->position.y = rayY;
        entity->shadowPosY = rayY;
        shadow->rotation.x = hitYaw;
        shadow->rotation.z = hitPitch;
        shadow->rotation.y = entity->rotation.y;

        if (entity->position.y < rayY) {
            shadow->flags |= ENTITY_FLAG_SKIP_UPDATE;
            entity->position.y = rayY + 10.0f;
        } else {
            shadow->flags &= ~ENTITY_FLAG_SKIP_UPDATE;
        }

        shadow->flags = (shadow->flags & ~ENTITY_FLAG_HIDDEN) | ((u16)entity->flags & ENTITY_FLAG_HIDDEN);
        if (!(entity->flags & ENTITY_FLAG_400) && origHitLength == 0.0f) {
            shadow->flags |= ENTITY_FLAG_HIDDEN;
        }
    } else {
        entity->shadowPosY = 0.0f;
    }
}

s32 entity_raycast_down(f32* x, f32* y, f32* z, f32* hitYaw, f32* hitPitch, f32* hitLength) {
    f32 hitX, hitY, hitZ;
    f32 hitDepth;
    f32 hitNx, hitNy, hitNz;
    s32 entityID;
    s32 colliderID;
    s32 hitID;
    s32 ret;

    hitDepth = 32767.0f;
    *hitLength = 32767.0f;
    entityID = test_ray_entities(*x, *y, *z, 0.0f, -1.0f, 0.0f, &hitX, &hitY, &hitZ, &hitDepth, &hitNx, &hitNy, &hitNz);
    hitID = -1;
    ret = FALSE;

    if ((entityID >= 0) && ((get_entity_type(entityID) != ENTITY_TYPE_PUSH_BLOCK) || (hitNx == 0.0f && hitNz == 0.0f && hitNy == 1.0))) {
        hitID = entityID | COLLISION_WITH_ENTITY_BIT;
    }

    colliderID = test_ray_colliders(0x10000, *x, *y, *z, 0.0f, -1.0f, 0.0f, &hitX, &hitY, &hitZ, &hitDepth, &hitNx,
                                    &hitNy, &hitNz);
    if (colliderID >= 0) {
        hitID = colliderID;
    }

    if (hitID >= 0) {
        *hitLength = hitDepth;
        *y = hitY;
        *hitYaw = -atan2(0.0f, 0.0f, hitNz * 100.0f, hitNy * 100.0f);
        *hitPitch = -atan2(0.0f, 0.0f, hitNx * 100.0f, hitNy * 100.0f);
        ret = TRUE;
    } else {
        *hitYaw = 0.0f;
        *hitPitch = 0.0f;
    }
    return ret;
}

void set_standard_shadow_scale(Shadow* shadow, f32 height) {
    if (!gGameStatusPtr->isBattle) {
        shadow->scale.x = 0.13 - (height / 2600.0f);
    } else {
        shadow->scale.x = 0.12 - (height / 3600.0f);
    }

    if (shadow->scale.x < 0.01) {
        shadow->scale.x = 0.01f;
    }
    shadow->scale.z = shadow->scale.x;
}

void set_npc_shadow_scale(Shadow* shadow, f32 height, f32 npcRadius) {
    if (!gGameStatusPtr->isBattle) {
        shadow->scale.x = 0.13 - (height / 2600.0f);
    } else {
        shadow->scale.x = 0.12 - (height / 3600.0f);
    }

    if (shadow->scale.x < 0.01) {
        shadow->scale.x = 0.01f;
    }

    if (npcRadius > 60.0f) {
        shadow->scale.z = shadow->scale.x * 2.0f;
    } else {
        shadow->scale.z = shadow->scale.x;
    }
}

void set_peach_shadow_scale(Shadow* shadow, f32 scale) {
    PlayerStatus* playerStatus = &gPlayerStatus;
    f32 phi_f2 = 0.12f;

    if (!gGameStatusPtr->isBattle) {
        switch (playerStatus->anim) {
            case 0xC0018:
            case 0xC0019:
            case 0xC001A:
            case 0xD0008:
                shadow->scale.x = 0.26f - (scale / 2600.0f);
                if (shadow->scale.x < 0.01) {
                    shadow->scale.x = 0.01f;
                }
                shadow->scale.z = 0.13f - (scale / 2600.0f);
                if (shadow->scale.z < 0.01) {
                    shadow->scale.z = 0.01f;
                }
                return;
        }

        phi_f2 = 0.16f;
    }

    shadow->scale.x = phi_f2 - (scale / 3600.0f);
    if (shadow->scale.x < 0.01) {
        shadow->scale.x = 0.01f;
    }
    shadow->scale.z = shadow->scale.x;
}

s32 is_block_on_ground(Entity* block) {
    f32 x = block->position.x;
    f32 y = block->position.y;
    f32 z = block->position.z;
    f32 hitYaw;
    f32 hitPitch;
    f32 hitLength;
    s32 ret;

    entity_raycast_down(&x, &y, &z, &hitYaw, &hitPitch, &hitLength);

    ret = hitLength;
    if (ret == 32767) {
        ret = FALSE;
    }

    return ret;
}

void state_delegate_NOP(void) {
}

void clear_game_modes(void) {
    GameMode* gameMode;
    s32 i;

    for (gameMode = gMainGameState, i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
        gameMode->flags = 0;
    }
}

GameMode* set_next_game_mode(GameMode* arg0) {
    GameMode* gameMode;
    s32 i;

    for (gameMode = gMainGameState, i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
        if (gameMode->flags == 0) {
            break;
        }
    }

    ASSERT(i < ARRAY_COUNT(gMainGameState));

    gameMode->flags = 1 | 2;
    gameMode->init = arg0->init;
    gameMode->step = arg0->step;
    gameMode->render = arg0->render;
    gameMode->unk_0C = NULL;

    if (gameMode->init == NULL) {
        gameMode->init = state_delegate_NOP;
    }
    if (gameMode->step == NULL) {
        gameMode->step = state_delegate_NOP;
    }
    if (gameMode->unk_0C == NULL) {
        gameMode->unk_0C = state_delegate_NOP;
    }
    if (gameMode->render == NULL) {
        gameMode->render = state_delegate_NOP;
    }

    gameMode->renderAux = state_delegate_NOP;
    gameMode->init();

    return gameMode;
}

GameMode* set_game_mode_slot(s32 i, GameMode* mode) {
    GameMode* gameMode = &gMainGameState[i];

    ASSERT(i < ARRAY_COUNT(gMainGameState));

    gameMode->flags = 2 | 1;
    gameMode->init = mode->init;
    gameMode->step = mode->step;
    gameMode->render = mode->render;
    gameMode->unk_0C = NULL;
    if (gameMode->init == NULL) gameMode->init = state_delegate_NOP;
    if (gameMode->step == NULL) gameMode->step = state_delegate_NOP;
    if (gameMode->unk_0C == NULL) gameMode->unk_0C = state_delegate_NOP;
    if (gameMode->render == NULL) gameMode->render = state_delegate_NOP;

    gameMode->renderAux = state_delegate_NOP;
    gameMode->init();

    return gameMode;
}

void game_mode_set_fpDrawAuxUI(s32 i, void (*fn)(void)) {
    GameMode* gameMode = &gMainGameState[i];

    ASSERT(i < ARRAY_COUNT(gMainGameState));

    gameMode->renderAux = fn;
    gameMode->flags |= 0x20;

    if (fn == NULL) {
        gameMode->renderAux = state_delegate_NOP;
    }
}

void func_80112DD4(s32 i) {
    gMainGameState[i].flags |= 4;
}

void func_80112DFC(s32 i) {
    gMainGameState[i].flags |= 8;
}

void func_80112E24(s32 i) {
    gMainGameState[i].flags &= ~0x1C;
}

void func_80112E4C(s32 i) {
    gMainGameState[i].flags &= ~0x0C;
    gMainGameState[i].flags |= 0x10;
}

void step_current_game_mode(void) {
    GameMode* gameMode = gMainGameState;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
        if (gameMode->flags != 0) {
            if (!(gameMode->flags & 4)) {
                if (!(gameMode->flags & 8)) {
                    gameMode->flags &= ~2;
                    gameMode->step();
                }
            }
        }
    }
}

void state_do_unk(void) {
    GameMode* gameMode = gMainGameState;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
        if (gameMode->flags != 0) {
            if (!(gameMode->flags & 4)) {
                if (!(gameMode->flags & 0x10)) {
                    gameMode->unk_0C();
                }
            }
        }
    }
}

void state_render_backUI(void) {
    GameMode* gameMode = gMainGameState;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
        if (gameMode->flags != 0) {
            if (!(gameMode->flags & 4)) {
                if (!(gameMode->flags & 0x10)) {
                    gameMode->render();
                }
            }
        }
    }
}

void state_render_frontUI(void) {
    GameMode* gameMode = gMainGameState;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
        if (gameMode->flags != 0) {
            if (!(gameMode->flags & 4)) {
                if (!(gameMode->flags & 2)) {
                    if (gameMode->flags & 0x20) {
                        gameMode->renderAux();
                    }
                }
            }
        }
    }

    // re-initialization needed - evidence of inlining? or just copy/pasting?
    gameMode = &gMainGameState[0];
    for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
        if (gameMode->flags != 0) {
            if (!(gameMode->flags & 4)) {
                if (!(gameMode->flags & 2)) {
                    if (gameMode->flags & 0x10) {
                        gameMode->render();
                    }
                }
            }
        }
    }
}

void appendGfx_model(void* data) {
    Model* model = data;
    s32 mtxPushMode;
    TextureHandle* textureHandle;
    TextureHeader* textureHeader;
    u32 extraTileType;
    s8 renderMode;
    s32 texturingMode;
    s32 renderModeIdx;
    s32 new_var;
    s32 flags = model->flags;

    ModelNode* modelNode;
    u16 customGfxIndex;
    s32 mtxLoadMode;
    s32 combineSubType;
    ModelNodeProperty* prop;
    s32 temp;

    s32 fogMin, fogMax;
    s32 fogR, fogG, fogB, fogA;
    Gfx** gfxPos = &gMainGfxPos;

    mtxPushMode = G_MTX_PUSH;
    mtxLoadMode = G_MTX_LOAD;
    modelNode = model->modelNode;

    if (model->textureID != 0) {
        textureHandle = &mdl_textureHandles[model->textureID + model->textureVariation];
        textureHeader = &textureHandle->header;

        if (textureHandle->gfx != NULL) {
            extraTileType = textureHandle->header.extraTiles;
        } else {
            textureHeader = NULL;
        }
    } else {
        textureHandle = NULL;
        textureHeader = NULL;
    }

    renderMode = model->renderMode;
    combineSubType = 0;
    if (textureHeader != NULL) {
        switch (extraTileType) {
            case EXTRA_TILE_NONE:
                texturingMode = 1;
                break;
            case EXTRA_TILE_MIPMAPS:
            case EXTRA_TILE_AUX_SAME_AS_MAIN:
            case EXTRA_TILE_AUX_INDEPENDENT:
                texturingMode = 2;
                break;
            default:
                texturingMode = 1;
                break;
        }
    } else {
        texturingMode = 1;
    }
    if ((textureHeader != NULL || renderMode <= RENDER_MODE_ALPHATEST_NO_ZB) && gCurrentFogSettings->enabled && !(flags & MODEL_FLAG_FLAG_40)) {
        texturingMode = 3;
        combineSubType = 1;
    }

    // fog mode
    switch ((u32)(model->customGfxIndex >> 4)) {
        case FOG_MODE_1:
            texturingMode += 3;
            combineSubType = 2;
            break;
        case FOG_MODE_2:
            if (renderMode <= RENDER_MODE_ALPHATEST_NO_ZB) {
                gDPSetPrimColor((*gfxPos)++, 0, 0, mdl_renderModelFogPrimColorR,
                                                   mdl_renderModelFogPrimColorG,
                                                   mdl_renderModelFogPrimColorB,
                                                   mdl_renderModelFogPrimColorA);
                gDPSetFogColor((*gfxPos)++, mdl_renderModelFogColorR,
                                            mdl_renderModelFogColorG,
                                            mdl_renderModelFogColorB, 0);
                gSPFogPosition((*gfxPos)++, mdl_renderModelFogStart, mdl_renderModelFogEnd);
                texturingMode += 9;
                combineSubType = 3;
            }
            break;
        case FOG_MODE_3:
            texturingMode = 2;
            combineSubType = 4;
            gDPSetPrimColor((*gfxPos)++, 0, 0, gRenderModelPrimR,
                                               gRenderModelPrimG,
                                               gRenderModelPrimB, 255);
            gDPSetEnvColor((*gfxPos)++, gRenderModelEnvR,
                                        gRenderModelEnvG,
                                        gRenderModelEnvB, 255);
            break;
    }

    gDPPipeSync((*gfxPos)++);

    if (model->groupData != NULL) {
        Lightsn* lightningGroup = model->groupData->lightingGroup;
        if (model->groupData->lightingGroup != NULL) {
            switch (model->groupData->numLights) {
                case 0:
                    gSPSetLights0((*gfxPos)++, lightningGroup[0]);
                    break;
                case 1:
                    gSPSetLights1((*gfxPos)++, lightningGroup[0]);
                    break;
                case 2:
                    gSPSetLights2((*gfxPos)++, lightningGroup[0]);
                    break;
                case 3:
                    gSPSetLights3((*gfxPos)++, lightningGroup[0]);
                    break;
                case 4:
                    gSPSetLights4((*gfxPos)++, lightningGroup[0]);
                    break;
                case 5:
                    gSPSetLights5((*gfxPos)++, lightningGroup[0]);
                    break;
                case 6:
                    gSPSetLights6((*gfxPos)++, lightningGroup[0]);
                    break;
                case 7:
                    gSPSetLights7((*gfxPos)++, lightningGroup[0]);
                    break;
            }
        }
    }

    if (textureHeader != NULL) {
        switch (extraTileType) {
            case EXTRA_TILE_AUX_INDEPENDENT:
            case EXTRA_TILE_4:
                prop = get_model_property(modelNode, MODEL_PROP_KEY_SPECIAL);
                if (prop != NULL) {
                    s32 v1 = prop->data.s;
                    u16 a2 = prop->dataType;
                    s32 a1 = prop->dataType;
                    func_801180E8(textureHeader, gfxPos, textureHandle->raster, textureHandle->palette, textureHandle->auxRaster, textureHandle->auxPalette,
                                (v1 >> 12) & 0xF, (v1 >> 16) & 0xF,
                                a2 & 0xFFF, (a1 >> 12) & 0xFFF);

                } else {
                    gSPDisplayList((*gfxPos)++, textureHandle->gfx);
                }
                break;
            default:
                gSPDisplayList((*gfxPos)++, textureHandle->gfx);
                break;
        }
    } else {
        gSPTexture((*gfxPos)++, 0, 0, 0, G_TX_RENDERTILE, G_OFF);
        gDPSetCombineMode((*gfxPos)++, G_CC_SHADE, G_CC_SHADE);
        gDPSetColorDither((*gfxPos)++, G_CD_MAGICSQ);
        gDPSetAlphaDither((*gfxPos)++, G_AD_PATTERN);
    }

    if (combineSubType != 0 || renderMode == RENDER_MODE_ALPHATEST || renderMode == RENDER_MODE_ALPHATEST_ONESIDED) {
        u32 v1 = 0;

        if (textureHeader != NULL) {
            u32 colorCombineType = textureHeader->colorCombineType;
            if (colorCombineType >= 3) {
                v1 = colorCombineType + 10;
            } else {
                v1 = extraTileType * 3 + 1 + textureHeader->colorCombineSubType;
            }
        }

        if (renderMode != RENDER_MODE_ALPHATEST && renderMode != RENDER_MODE_ALPHATEST_ONESIDED) {
            *(*gfxPos) = D_8014B0B8[v1][combineSubType];
        } else {
            *(*gfxPos) = D_8014B400[v1][combineSubType];
        }
        (*gfxPos)++;
    }

    switch (texturingMode) {
        case 1:
            switch (renderMode) {
                case RENDER_MODE_SURFACE_OPA:
                    renderModeIdx = 0;
                    break;
                case RENDER_MODE_SURFACE_OPA_NO_AA:
                    renderModeIdx = 1;
                    break;
                case RENDER_MODE_DECAL_OPA:
                    renderModeIdx = 2;
                    break;
                case RENDER_MODE_DECAL_OPA_NO_AA:
                    renderModeIdx = 3;
                    break;
                case RENDER_MODE_INTERSECTING_OPA:
                    renderModeIdx = 4;
                    break;
                case RENDER_MODE_ALPHATEST:
                    renderModeIdx = 6;
                    break;
                case RENDER_MODE_ALPHATEST_ONESIDED:
                    renderModeIdx = 7;
                    break;
                case RENDER_MODE_SURFXLU_AA_ZB_ZUPD:
                    renderModeIdx = 9;
                    break;
                case RENDER_MODE_SURFACE_XLU_LAYER1:
                case RENDER_MODE_SURFACE_XLU_LAYER2:
                case RENDER_MODE_SURFACE_XLU_LAYER3:
                    renderModeIdx = 8;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_AA:
                    renderModeIdx = 10;
                    break;
                case RENDER_MODE_SURFXLU_ZB_ZUPD:
                    renderModeIdx = 11;
                    break;
                case RENDER_MODE_DECAL_XLU:
                    renderModeIdx = 12;
                    break;
                case RENDER_MODE_DECAL_XLU_NOAA:
                    renderModeIdx = 13;
                    break;
                case RENDER_MODE_INTERSECTING_XLU:
                    renderModeIdx = 14;
                    break;
                case RENDER_MODE_SURFACE_OPA_NO_ZB:
                    renderModeIdx = 0x2E;
                    break;
                case RENDER_MODE_ALPHATEST_NO_ZB:
                    renderModeIdx = 0x2F;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_ZB:
                    renderModeIdx = 0x30;
                    break;
                case RENDER_MODE_CLOUD:
                    renderModeIdx = 0x37;
                    break;
                case RENDER_MODE_CLOUD_NO_ZB:
                    renderModeIdx = 0x38;
                    break;
                default:
                    renderModeIdx = 0;
                    break;
            }
            gSPDisplayList((*gfxPos)++, D_8014AFC0[renderModeIdx]);
            break;
        case 2:
            switch (renderMode) {
                case RENDER_MODE_SURFACE_OPA_NO_AA:
                    renderModeIdx = 0x11;
                    break;
                case RENDER_MODE_DECAL_OPA:
                    renderModeIdx = 0x12;
                    break;
                case RENDER_MODE_DECAL_OPA_NO_AA:
                    renderModeIdx = 0x13;
                    break;
                case RENDER_MODE_INTERSECTING_OPA:
                    renderModeIdx = 0x14;
                    break;
                case RENDER_MODE_ALPHATEST:
                    renderModeIdx = 0x16;
                    break;
                case RENDER_MODE_ALPHATEST_ONESIDED:
                    renderModeIdx = 0x17;
                    break;
                case RENDER_MODE_SURFACE_XLU_LAYER1:
                case RENDER_MODE_SURFACE_XLU_LAYER2:
                case RENDER_MODE_SURFACE_XLU_LAYER3:
                    renderModeIdx = 0x18;
                    break;
                case RENDER_MODE_SURFXLU_AA_ZB_ZUPD:
                    renderModeIdx = 0x19;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_AA:
                    renderModeIdx = 0x1A;
                    break;
                case RENDER_MODE_DECAL_XLU:
                    renderModeIdx = 0x1B;
                    break;
                case RENDER_MODE_DECAL_XLU_NOAA:
                    renderModeIdx = 0x1C;
                    break;
                case RENDER_MODE_INTERSECTING_XLU:
                    renderModeIdx = 0x1D;
                    break;
                case RENDER_MODE_SURFACE_OPA_NO_ZB:
                    renderModeIdx = 0x31;
                    break;
                case RENDER_MODE_ALPHATEST_NO_ZB:
                    renderModeIdx = 0x32;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_ZB:
                    renderModeIdx = 0x33;
                    break;
                case RENDER_MODE_CLOUD:
                    renderModeIdx = 0x39;
                    break;
                case RENDER_MODE_CLOUD_NO_ZB:
                    renderModeIdx = 0x3A;
                    break;
                case RENDER_MODE_SURFACE_OPA:
                    renderModeIdx = 0x10;
                    break;
                default:
                    renderModeIdx = 0x10;
                    break;
            }
            gSPDisplayList((*gfxPos)++, D_8014AFC0[renderModeIdx]);
            break;
        case 3:
            temp = 0x25; // required to match
            switch (renderMode) {
                case RENDER_MODE_SURFACE_OPA_NO_AA:
                    renderModeIdx = 0x20;
                    break;
                case RENDER_MODE_DECAL_OPA:
                    renderModeIdx = 0x21;
                    break;
                case RENDER_MODE_DECAL_OPA_NO_AA:
                    renderModeIdx = 0x22;
                    break;
                case RENDER_MODE_INTERSECTING_OPA:
                    renderModeIdx = 0x23;
                    break;
                case RENDER_MODE_ALPHATEST:
                    renderModeIdx = temp;
                    break;
                case RENDER_MODE_ALPHATEST_ONESIDED:
                    renderModeIdx = 0x26;
                    break;
                case RENDER_MODE_SURFACE_XLU_LAYER1:
                case RENDER_MODE_SURFACE_XLU_LAYER2:
                case RENDER_MODE_SURFACE_XLU_LAYER3:
                    renderModeIdx = 0x27;
                    break;
                case RENDER_MODE_SURFXLU_AA_ZB_ZUPD:
                    renderModeIdx = 0x28;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_AA:
                    renderModeIdx = 0x29;
                    break;
                case RENDER_MODE_DECAL_XLU:
                    renderModeIdx = 0x2A;
                    break;
                case RENDER_MODE_DECAL_XLU_NOAA:
                    renderModeIdx = 0x2B;
                    break;
                case RENDER_MODE_INTERSECTING_XLU:
                    renderModeIdx = 0x2C;
                    break;
                case RENDER_MODE_SURFACE_OPA_NO_ZB:
                    renderModeIdx = 0x34;
                    break;
                case RENDER_MODE_ALPHATEST_NO_ZB:
                    renderModeIdx = 0x35;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_ZB:
                    renderModeIdx = 0x36;
                    break;
                case RENDER_MODE_CLOUD:
                    renderModeIdx = 0x3B;
                    break;
                case RENDER_MODE_CLOUD_NO_ZB:
                    renderModeIdx = 0x3C;
                    break;
                case RENDER_MODE_SURFACE_OPA:
                    renderModeIdx = 0x1F;
                    break;
                default:
                    renderModeIdx = 0x1F;
                    break;
            }
            gSPDisplayList((*gfxPos)++, D_8014AFC0[renderModeIdx]);
            gDPSetFogColor((*gfxPos)++, gCurrentFogSettings->r,
                                            gCurrentFogSettings->g,
                                            gCurrentFogSettings->b,
                                            gCurrentFogSettings->a);
            gSPFogPosition((*gfxPos)++, gCurrentFogSettings->startDistance, gCurrentFogSettings->endDistance);
            break;
        case 4:
        case 5:
            if (mdl_bgMultiplyColorA == 255) {
                return;
            }
            gSPDisplayList((*gfxPos)++, D_8014AFC0[0x10]);
            switch (renderMode) {
                case RENDER_MODE_SURFACE_OPA:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_OPA_SURF2);
                    break;
                case RENDER_MODE_SURFACE_OPA_NO_AA:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_ZB_OPA_SURF2);
                    break;
                case RENDER_MODE_DECAL_OPA:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_OPA_DECAL2);
                    break;
                case RENDER_MODE_DECAL_OPA_NO_AA:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_ZB_OPA_DECAL2);
                    break;
                case RENDER_MODE_INTERSECTING_OPA:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_OPA_INTER2);
                    break;
                case RENDER_MODE_ALPHATEST:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_TEX_EDGE2);
                    break;
                case RENDER_MODE_ALPHATEST_ONESIDED:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_TEX_EDGE2);
                    break;
                case RENDER_MODE_SURFACE_XLU_LAYER1:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_XLU_SURF2);
                    break;
                case RENDER_MODE_SURFACE_XLU_LAYER2:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_XLU_SURF2);
                    break;
                case RENDER_MODE_SURFACE_XLU_LAYER3:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_XLU_SURF2);
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_AA:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_ZB_XLU_SURF2);
                    break;
                case RENDER_MODE_DECAL_XLU:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_XLU_DECAL2);
                    break;
                case RENDER_MODE_DECAL_XLU_NOAA:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_XLU_DECAL2);
                    break;
                case RENDER_MODE_INTERSECTING_XLU:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_ZB_XLU_INTER2);
                    break;
                case RENDER_MODE_SURFACE_OPA_NO_ZB:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_OPA_SURF2);
                    break;
                case RENDER_MODE_ALPHATEST_NO_ZB:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_TEX_EDGE2);
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_ZB:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_AA_XLU_SURF2);
                    break;
                case RENDER_MODE_CLOUD:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_ZB_CLD_SURF2);
                    break;
                case RENDER_MODE_CLOUD_NO_ZB:
                    gDPSetRenderMode(gMainGfxPos++, GBL_c1(G_BL_CLR_BL, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA), G_RM_CLD_SURF2);
                    break;
            }
            gDPSetFogColor((*gfxPos)++, gCurrentFogSettings->r,
                                            gCurrentFogSettings->g,
                                            gCurrentFogSettings->b,
                                            mdl_bgMultiplyColorA);
            gDPSetBlendColor((*gfxPos)++, mdl_bgMultiplyColorR,
                                              mdl_bgMultiplyColorG,
                                              mdl_bgMultiplyColorB,
                                              255);
            gSPFogPosition((*gfxPos)++, 970, 1000);
            break;
        case 6:
            switch (renderMode) {
                case RENDER_MODE_SURFACE_OPA_NO_AA:
                    renderModeIdx = 0x20;
                    break;
                case RENDER_MODE_DECAL_OPA:
                    renderModeIdx = 0x21;
                    break;
                case RENDER_MODE_DECAL_OPA_NO_AA:
                    renderModeIdx = 0x22;
                    break;
                case RENDER_MODE_INTERSECTING_OPA:
                    renderModeIdx = 0x23;
                    break;
                case RENDER_MODE_ALPHATEST:
                    renderModeIdx = 0x25;
                    break;
                case RENDER_MODE_ALPHATEST_ONESIDED:
                    renderModeIdx = 0x26;
                    break;
                case RENDER_MODE_SURFACE_XLU_LAYER1:
                case RENDER_MODE_SURFACE_XLU_LAYER2:
                case RENDER_MODE_SURFACE_XLU_LAYER3:
                    renderModeIdx = 0x27;
                    break;
                case RENDER_MODE_SURFXLU_AA_ZB_ZUPD:
                    renderModeIdx = 0x28;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_AA:
                    renderModeIdx = 0x29;
                    break;
                case RENDER_MODE_DECAL_XLU:
                    renderModeIdx = 0x2A;
                    break;
                case RENDER_MODE_DECAL_XLU_NOAA:
                    renderModeIdx = 0x2B;
                    break;
                case RENDER_MODE_INTERSECTING_XLU:
                    renderModeIdx = 0x2C;
                    break;
                case RENDER_MODE_SURFACE_OPA_NO_ZB:
                    renderModeIdx = 0x34;
                    break;
                case RENDER_MODE_ALPHATEST_NO_ZB:
                    renderModeIdx = 0x35;
                    break;
                case RENDER_MODE_SURFACE_XLU_NO_ZB:
                    renderModeIdx = 0x36;
                    break;
                case RENDER_MODE_CLOUD:
                    renderModeIdx = 0x3B;
                    break;
                case RENDER_MODE_CLOUD_NO_ZB:
                    renderModeIdx = 0x3C;
                    break;
                case RENDER_MODE_SURFACE_OPA:
                    renderModeIdx = 0x1F;
                    break;
                default:
                    renderModeIdx = 0x1F;
                    break;
            }
            gSPDisplayList((*gfxPos)++, D_8014AFC0[renderModeIdx]);

            fogR = (gCurrentFogSettings->r * (255 - mdl_bgMultiplyColorA) + mdl_bgMultiplyColorR * mdl_bgMultiplyColorA) / 255;
            fogG = (gCurrentFogSettings->g * (255 - mdl_bgMultiplyColorA) + mdl_bgMultiplyColorG * mdl_bgMultiplyColorA) / 255;
            fogB = (gCurrentFogSettings->b * (255 - mdl_bgMultiplyColorA) + mdl_bgMultiplyColorB * mdl_bgMultiplyColorA) / 255;

            fogMin = (gCurrentFogSettings->startDistance * (255 - mdl_bgMultiplyColorA) + 900 * mdl_bgMultiplyColorA) / 255;
            fogMax = (gCurrentFogSettings->endDistance * (255 - mdl_bgMultiplyColorA) + 1000 * mdl_bgMultiplyColorA) / 255;

            gDPSetFogColor(gMainGfxPos++,  fogR, fogG, fogB, gCurrentFogSettings->a);
            gSPFogPosition((*gfxPos)++, fogMin, fogMax);
            break;
        case 10:
        case 11:
            switch (renderMode) {
                case RENDER_MODE_DECAL_OPA:
                    renderModeIdx = 0x21;
                    break;
                case RENDER_MODE_INTERSECTING_OPA:
                    renderModeIdx = 0x23;
                    break;
                case RENDER_MODE_ALPHATEST:
                    renderModeIdx = 0x25;
                    break;
                case RENDER_MODE_CLOUD:
                    renderModeIdx = 0x3B;
                    break;
                case RENDER_MODE_CLOUD_NO_ZB:
                    renderModeIdx = 0x3C;
                    break;
                case RENDER_MODE_SURFACE_OPA:
                default:
                    renderModeIdx = 0x1F;
                    break;
            }
            gSPDisplayList((*gfxPos)++, D_8014AFC0[renderModeIdx]);
            break;
    }

    if (!(flags & MODEL_FLAG_TRANSFORM_GROUP_MEMBER)) {
        if (!(flags & MODEL_FLAG_FLAG_2000)) {
            gSPMatrix((*gfxPos)++, model->currentSpecialMatrix, mtxLoadMode | mtxPushMode | G_MTX_MODELVIEW);
            if (mtxPushMode != G_MTX_NOPUSH) {
                mtxPushMode = G_MTX_NOPUSH;
            }
            if (mtxLoadMode != G_MTX_MUL) {
                mtxLoadMode = G_MTX_MUL;
            }
        }
    } else {
        mtxLoadMode = G_MTX_MUL;
        if (!(flags & MODEL_FLAG_FLAG_2000)) {
            gSPMatrix((*gfxPos)++, model->currentSpecialMatrix, mtxLoadMode | mtxPushMode | G_MTX_MODELVIEW);
            if (mtxPushMode != G_MTX_NOPUSH) {
                mtxPushMode = G_MTX_NOPUSH;
            }
        }
    }

    if (flags & MODEL_FLAG_USES_CUSTOM_GFX) {
        customGfxIndex = (model->customGfxIndex & 0xF) * 2;
        if ((*gCurrentCustomModelGfxPtr)[customGfxIndex] != NULL) {
            gSPDisplayList((*gfxPos)++, (*gCurrentCustomModelGfxPtr)[customGfxIndex]);
        }
    }

    if (textureHeader != NULL) {
        if (flags & MODEL_FLAG_HAS_TEX_PANNER) {
            s32 panMainU = texPannerMainU[model->texPannerID] >> 8;
            s32 panMainV = texPannerMainV[model->texPannerID] >> 8;
            s32 panAuxU = texPannerAuxU[model->texPannerID] >> 8;
            s32 panAuxV = texPannerAuxV[model->texPannerID] >> 8;

            switch (extraTileType) {
                case EXTRA_TILE_AUX_SAME_AS_MAIN:
                    gDPSetTileSize((*gfxPos)++, G_TX_RENDERTILE, panMainU, panMainV, (textureHeader->mainW - 1) * 4 + panMainU, (textureHeader->mainH / 2 - 1) * 4 + panMainV);
                    gDPSetTileSize((*gfxPos)++, G_TX_RENDERTILE + 1, panAuxU, panAuxV, (textureHeader->mainW - 1) * 4 + panAuxU, (textureHeader->mainH / 2 - 1) * 4 + panAuxV);
                    break;
                case EXTRA_TILE_AUX_INDEPENDENT:
                    gDPSetTileSize((*gfxPos)++, G_TX_RENDERTILE, panMainU, panMainV, (textureHeader->mainW - 1) * 4 + panMainU, (textureHeader->mainH - 1) * 4 + panMainV);
                    gDPSetTileSize((*gfxPos)++, G_TX_RENDERTILE + 1, panAuxU, panAuxV, (textureHeader->auxW - 1) * 4 + panAuxU, (textureHeader->auxH - 1) * 4 + panAuxV);
                    break;
                default:
                    gDPSetTileSize((*gfxPos)++, G_TX_RENDERTILE, panMainU, panMainV, (textureHeader->mainW - 1) * 4 + panMainU, (textureHeader->mainH - 1) * 4 + panMainV);
                    break;
            }
        }
    }
    if (flags & MODEL_FLAG_USE_CAMERA_UNK_MATRIX) {
        gSPMatrix((*gfxPos)++, gCameras[gCurrentCamID].unkMatrix, mtxLoadMode | mtxPushMode | G_MTX_MODELVIEW);
        if (mtxPushMode != G_MTX_NOPUSH) {
            mtxPushMode = G_MTX_NOPUSH;
        }
        if (mtxLoadMode != G_MTX_MUL) {
            mtxLoadMode = G_MTX_MUL;
        }
    }
    if (!(flags & MODEL_FLAG_HAS_LOCAL_VERTEX_COPY)) {
        gSPDisplayList((*gfxPos)++, modelNode->displayData->displayList);
    }

    if (flags & MODEL_FLAG_USES_CUSTOM_GFX) {
        customGfxIndex++;
        if ((*gCurrentCustomModelGfxPtr)[customGfxIndex] != NULL) {
            gSPDisplayList((*gfxPos)++, (*gCurrentCustomModelGfxPtr)[customGfxIndex]);
        }
    }

    if (mtxPushMode == G_MTX_NOPUSH) {
        gSPPopMatrix((*gfxPos)++, G_MTX_MODELVIEW);
    }

    gDPPipeSync((*gfxPos)++);
}

void func_80114B58(u32 romOffset, TextureHandle* handle, TextureHeader* header, s32 mainSize, s32 mainPalSize, s32 auxSize, s32 auxPalSize) {
    Gfx** temp;

    handle->raster = (IMG_PTR) mdl_nextTextureAddress;
    if (mainPalSize != 0) {
        handle->palette = (PAL_PTR) (mdl_nextTextureAddress + mainSize);
    } else {
        handle->palette = NULL;
    }
    dma_copy((u8*) romOffset, (u8*) (romOffset + mainSize + mainPalSize), mdl_nextTextureAddress);
    romOffset += mainSize + mainPalSize;
    mdl_nextTextureAddress += mainSize + mainPalSize;
    if (auxSize != 0) {
        handle->auxRaster = (IMG_PTR) mdl_nextTextureAddress;
        if (auxPalSize != 0) {
            handle->auxPalette = (PAL_PTR) (mdl_nextTextureAddress + auxSize);
        } else {
            handle->auxPalette = NULL;
        }
        dma_copy((u8*) romOffset, (u8*) (romOffset + auxSize + auxPalSize), mdl_nextTextureAddress);
        mdl_nextTextureAddress += auxSize + auxPalSize;
    } else {
        handle->auxPalette = NULL;
        handle->auxRaster = NULL;
    }

    handle->gfx = (Gfx*) mdl_nextTextureAddress;
    memcpy(&handle->header, header, sizeof(*header));
    func_801180E8(header, (Gfx**)&mdl_nextTextureAddress, handle->raster, handle->palette, handle->auxRaster, handle->auxPalette, 0, 0, 0, 0);

    temp = (Gfx**) &mdl_nextTextureAddress;
    gSPEndDisplayList((*temp)++);
}

void load_tile_header(ModelNodeProperty* propertyName, s32 romOffset, s32 size) {
    char* textureName = (char*)propertyName->data.p;
    u32 baseOffset = romOffset;
    s32 textureID = 0;
    u32 paletteSize;
    u32 rasterSize;
    u32 auxPaletteSize;
    u32 auxRasterSize;
    TextureHeader* header;
    TextureHandle* textureHandle;
    s32 mainSize;

    if (textureName == NULL) {
        (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].textureID = 0;
        return;
    }

    while (romOffset < baseOffset + size) {
        dma_copy((u8*)romOffset, (u8*)romOffset + sizeof(gCurrentTileDescriptor), &gCurrentTileDescriptor);
        header = &gCurrentTileDescriptor;

        rasterSize = header->mainW * header->mainH;

        if (header->mainBitDepth == G_IM_SIZ_4b) {
            if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                s32 d = 2;
                while (header->mainW / d >= 16 && header->mainH / d > 0) {
                    rasterSize += header->mainW / d * header->mainH / d;
                    d *= 2;
                }
            }
            rasterSize /= 2;
        } else if (header->mainBitDepth == G_IM_SIZ_8b) {
            if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                s32 d = 2;
                while (header->mainW / d >= 8 && header->mainH / d > 0) {
                    rasterSize += header->mainW / d * header->mainH / d;
                    d *= 2;
                }
            }
        } else {
            do {} while (0);
            if (header->mainBitDepth == G_IM_SIZ_16b) {
                if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                    s32 d = 2;
                    while (header->mainW / d >= 4 && header->mainH / d > 0) {
                        rasterSize += header->mainW / d * header->mainH / d;
                        d *= 2;
                    }
                }
                rasterSize *= 2;
            } else if (header->mainBitDepth == G_IM_SIZ_32b) {
                if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                    s32 d = 2;
                    while (header->mainW / d >= 2 && header->mainH / d > 0) {
                        rasterSize += header->mainW / d * header->mainH / d;
                        d *= 2;
                    }
                }
                rasterSize *= 4;
            }
        }

        if (header->mainFmt == G_IM_FMT_CI) {
            paletteSize = 0x20;
            if (header->mainBitDepth == G_IM_SIZ_8b) {
                paletteSize = 0x200;
            }
        } else {
            paletteSize = 0;
        }

        if (header->extraTiles == EXTRA_TILE_AUX_INDEPENDENT) {
            auxRasterSize = header->auxW * header->auxH;
            if (header->auxBitDepth == G_IM_SIZ_4b) {
                auxRasterSize /= 2;
            } else if (header->auxBitDepth == G_IM_SIZ_8b) {
            } else if (header->auxBitDepth == G_IM_SIZ_16b) {
                auxRasterSize *= 2;
            } else {
                if (header->auxBitDepth == G_IM_SIZ_32b) {
                    auxRasterSize *= 4;
                }
            }
            if (header->auxFmt == G_IM_FMT_CI) {
                auxPaletteSize = 0x20;
                if (header->auxBitDepth == G_IM_SIZ_8b) {
                    auxPaletteSize = 0x200;
                }
            } else {
                auxPaletteSize = 0;
            }
        } else {
            auxPaletteSize = 0;
            auxRasterSize = 0;
        }

        if (strcmp(textureName, header->name) == 0) {
            break;
        }

        textureID++;
        mainSize = rasterSize + paletteSize + sizeof(*header);
        romOffset += mainSize;
        romOffset += auxRasterSize + auxPaletteSize;
    }

    if (romOffset >= baseOffset + 0x40000) {
        (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].textureID = 0;
        return;
    }

    (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].textureID = textureID + 1;
    textureHandle = &mdl_textureHandles[(*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].textureID];
    romOffset += sizeof(*header);

    if (textureHandle->gfx == NULL) {
        func_80114B58(romOffset, textureHandle, header, rasterSize, paletteSize, auxRasterSize, auxPaletteSize);
        func_80115498(romOffset + rasterSize + paletteSize + auxRasterSize + auxPaletteSize, (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].textureID, baseOffset, size);
    }

}

void func_80115498(u32 romOffset, s32 textureID, s32 baseOffset, s32 size) {
    u32 offset;
    TextureHeader sp20;
    u32 rasterSize;
    s32 paletteSize;
    u32 auxRasterSize;
    u32 auxPaletteSize;
    s32 bitDepth;
    s32 mainSize;
    TextureHeader* header;
    s32 currentTextureID = textureID;


    for (offset = romOffset; offset < baseOffset + size;) {
        dma_copy((u8*)offset, (u8*)offset + sizeof(sp20), &sp20);
        header = &sp20;
        if (header->unk_28 == 0) {
            break;
        }

        rasterSize = header->mainW * header->mainH;

        if (header->mainBitDepth == G_IM_SIZ_4b) {
            if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                s32 d = 2;
                while (header->mainW / d >= 16 && header->mainH / d > 0) {
                    rasterSize += header->mainW / d * header->mainH / d;
                    d *= 2;
                }
            }
            rasterSize /= 2;
        } else if (header->mainBitDepth == G_IM_SIZ_8b) {
            if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                s32 d = 2;
                while (header->mainW / d >= 8 && header->mainH / d > 0) {
                    rasterSize += header->mainW / d * header->mainH / d;
                    d *= 2;
                }
            }
        } else {
            do {} while (0);
            if (header->mainBitDepth == G_IM_SIZ_16b) {
                if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                    s32 d = 2;
                    while (header->mainW / d >= 4 && header->mainH / d > 0) {
                        rasterSize += header->mainW / d * header->mainH / d;
                        d *= 2;
                    }
                }
                rasterSize *= 2;
            } else if (header->mainBitDepth == G_IM_SIZ_32b) {
                if (header->extraTiles == EXTRA_TILE_MIPMAPS) {
                    s32 d = 2;
                    while (header->mainW / d >= 2 && header->mainH / d > 0) {
                        rasterSize += header->mainW / d * header->mainH / d;
                        d *= 2;
                    }
                }
                rasterSize *= 4;
            }
        }

        if (header->mainFmt == G_IM_FMT_CI) {
            paletteSize = 0x20;
            if (header->mainBitDepth == G_IM_SIZ_8b) {
                paletteSize = 0x200;
            }
        } else {
            paletteSize = 0;
        }

        if (header->extraTiles == EXTRA_TILE_AUX_INDEPENDENT) {
            auxRasterSize = header->auxW * header->auxH;
            if (header->auxBitDepth == G_IM_SIZ_4b) {
                auxRasterSize /= 2;
            } else if (header->auxBitDepth == G_IM_SIZ_8b) {
            } else if (header->auxBitDepth == G_IM_SIZ_16b) {
                auxRasterSize *= 2;
            } else {
                if (header->auxBitDepth == G_IM_SIZ_32b) {
                    auxRasterSize *= 4;
                }
            }
            if (header->auxFmt == G_IM_FMT_CI) {
                auxPaletteSize = 0x20;
                if (header->auxBitDepth == G_IM_SIZ_8b) {
                    auxPaletteSize = 0x200;
                }
            } else {
                auxPaletteSize = 0;
            }
        } else {
            auxPaletteSize = 0;
            auxRasterSize = 0;
        }
        currentTextureID = ++textureID;
        func_80114B58(offset + sizeof(*header), &mdl_textureHandles[currentTextureID], header, rasterSize, paletteSize, auxRasterSize, auxPaletteSize);
        mainSize = rasterSize + paletteSize + sizeof(*header);
        offset += mainSize;
        offset += auxRasterSize + auxPaletteSize;
    }
}

// Goofy temps needed to match
ModelNodeProperty* get_model_property(ModelNode* node, ModelPropertyKeys key) {
    s32 numProperties = node->numProperties;
    ModelNodeProperty* propertyList = node->propertyList;
    s32 i;

    for (i = 0; i < numProperties; i++, propertyList++) {
        if (propertyList->key == key) {
            return propertyList;
        }
    }
    return NULL;
}

void _load_model_textures(ModelNode* model, s32 romOffset, s32 size) {
    if (model->type != SHAPE_TYPE_MODEL) {
        if (model->groupData != NULL) {
            s32 numChildren = model->groupData->numChildren;

            if (numChildren != 0) {
                s32 i;

                for (i = 0; i < numChildren; i++) {
                    _load_model_textures(model->groupData->childList[i], romOffset, size);
                }
            }
        }
    } else {
        ModelNodeProperty* propTextureName = get_model_property(model, MODEL_PROP_KEY_TEXTURE_NAME);
        if (propTextureName != NULL) {
            load_tile_header(propTextureName, romOffset, size);
        }
    }
    mdl_treeIterPos++;
}

void load_model_textures(ModelNode* model, s32 romOffset, s32 size) {
    s32 battleOffset = ((gGameStatusPtr->isBattle != 0) << 17); // TODO FIX

    mdl_nextTextureAddress = mdl_textureBaseAddress + battleOffset;

    if (model != NULL && romOffset != 0 && size != 0) {
        s32 i;

        for (i = 0; i < ARRAY_COUNT(mdl_textureHandles); i++) {
            mdl_textureHandles[i].gfx = NULL;
        }

        mdl_treeIterPos = 0;
        if (model != NULL) {
            _load_model_textures(model, romOffset, size);
        }
    }
}

s32 mdl_get_child_count(ModelNode* model) {
    s32 ret = 0;

    if (model->type != SHAPE_TYPE_MODEL && model->groupData != NULL) {
        s32 numChildren = model->groupData->numChildren;

        if (numChildren != 0) {
            s32 i;

            ret += numChildren;
            for (i = 0; i < numChildren; i++) {
                ret += mdl_get_child_count(model->groupData->childList[i]);
            }
        }
    }
    return ret;
}

void clear_model_data(void) {
    s32 i;

    if (!gGameStatusPtr->isBattle) {
        gCurrentModels = &wModelList;
        gCurrentTransformGroups = &wTransformGroups;
        gCurrentCustomModelGfxPtr = &wModelSpecialDls;
        gCurrentCustomModelGfxBuildersPtr = &wCustomModelGfxBuilders;
        gCurrentModelTreeRoot = &D_80152214;
        gCurrentModelLocalVtxBuffers = &wModelLocalVtxBuffers;
        mdl_currentModelTreeNodeInfo = &D_80152220;
        gBackgroundFogModePtr = &wBackgroundFogMode;
        mdl_bgMultiplyColorA = 0;
        mdl_bgMultiplyColorR = 0;
        mdl_bgMultiplyColorG = 0;
        mdl_bgMultiplyColorB = 0;
        gCurrentFogSettings = &wFogSettings;
    } else {
        gCurrentModels = &bModelList;
        gCurrentTransformGroups = &bTransformGroups;
        gCurrentCustomModelGfxPtr = &bModelSpecialDls;
        gCurrentCustomModelGfxBuildersPtr = &bCustomModelGfxBuilders;
        gCurrentModelTreeRoot = &D_80152218;
        gCurrentModelLocalVtxBuffers = &bModelLocalVtxBuffers;
        mdl_currentModelTreeNodeInfo = &D_80152A20;
        gBackgroundFogModePtr = &bBackgroundFogMode;
        gCurrentFogSettings = &bFogSettings;
    }

    for (i = 0; i < ARRAY_COUNT(*gCurrentModels); i++) {
        (*gCurrentModels)[i] = 0;
    }

    for (i = 0; i < ARRAY_COUNT(*gCurrentTransformGroups); i++) {
        (*gCurrentTransformGroups)[i] = 0;
    }

    for (i = 0; i < ARRAY_COUNT(*gCurrentCustomModelGfxPtr); i++) {
        (*gCurrentCustomModelGfxPtr)[i] = 0;
        (*gCurrentCustomModelGfxBuildersPtr)[i] = 0;
    }

    *gCurrentModelTreeRoot = NULL;

    for (i = 0; i < ARRAY_COUNT(*mdl_currentModelTreeNodeInfo); i++) {
        (*mdl_currentModelTreeNodeInfo)[i].modelIndex = -1;
        (*mdl_currentModelTreeNodeInfo)[i].treeDepth = 0;
        (*mdl_currentModelTreeNodeInfo)[i].textureID = 0;
    }

    *gBackgroundFogModePtr = FOG_MODE_0;
    gCurrentFogSettings->r = 10;
    gCurrentFogSettings->g = 10;
    gCurrentFogSettings->b = 10;
    gCurrentFogSettings->startDistance = 950;
    gCurrentFogSettings->enabled = FALSE;
    gCurrentFogSettings->a = 0;
    gCurrentFogSettings->endDistance = 1000;

    for (i = 0; i < ARRAY_COUNT(texPannerAuxV); i++) {
        texPannerAuxV[i] = 0;
        texPannerAuxU[i] = 0;
        texPannerMainV[i] = 0;
        texPannerMainU[i] = 0;
    }
}

void init_model_data(void) {
    if (!gGameStatusPtr->isBattle) {
        gCurrentModels = &wModelList;
        gCurrentTransformGroups = &wTransformGroups;
        gCurrentCustomModelGfxPtr = &wModelSpecialDls;
        gCurrentCustomModelGfxBuildersPtr = &wCustomModelGfxBuilders;
        gCurrentModelTreeRoot = &D_80152214;
        gCurrentModelLocalVtxBuffers = &wModelLocalVtxBuffers;
        mdl_currentModelTreeNodeInfo = &D_80152220;
        gBackgroundFogModePtr = &wBackgroundFogMode;
        gCurrentFogSettings = &wFogSettings;
    } else {
        gCurrentModels = &bModelList;
        gCurrentTransformGroups = &bTransformGroups;
        gCurrentCustomModelGfxPtr = &bModelSpecialDls;
        gCurrentCustomModelGfxBuildersPtr = &bCustomModelGfxBuilders;
        gCurrentModelTreeRoot = &D_80152218;
        gCurrentModelLocalVtxBuffers = &bModelLocalVtxBuffers;
        mdl_currentModelTreeNodeInfo = &D_80152A20;
        gBackgroundFogModePtr = &bBackgroundFogMode;
        gCurrentFogSettings = &bFogSettings;
    }
}

void calculate_model_sizes(void) {
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentModels); i++) {
        Model* model = (*gCurrentModels)[i];

        if (model != NULL) {
            ModelBoundingBox* bb = (ModelBoundingBox*)get_model_property(model->modelNode, MODEL_PROP_KEY_BOUNDING_BOX);

            bb->halfSizeX = (bb->maxX - bb->minX) * 0.5;
            bb->halfSizeY = (bb->maxY - bb->minY) * 0.5;
            bb->halfSizeZ = (bb->maxZ - bb->minZ) * 0.5;
            model->flags |= MODEL_FLAG_USES_TRANSFORM_MATRIX;
        }
    }
}

void mdl_create_model(ModelBlueprint* bp, s32 arg1) {
    EffectInstance* effect;
    ModelNode* node = bp->mdlNode;
    ModelNodeProperty* prop;
    ModelBoundingBox* bb;
    s32 modelIdx;
    Model* model;
    f32 x, y, z;

    prop = get_model_property(node, MODEL_PROP_KEY_SPECIAL);
    modelIdx = 0;
    if (prop != NULL) {
        s32 temp_s1 = (u8) prop->data.s / 16;

        if (temp_s1 != 0) {
            prop = get_model_property(node, MODEL_PROP_KEY_BOUNDING_BOX);
            if (prop != NULL) {
                ModelBoundingBox* bb = (ModelBoundingBox*) prop;

                fx_flame(
                    temp_s1 - 1, (bb->minX + bb->maxX) * 0.5f, bb->minY, (bb->minZ + bb->maxZ) * 0.5f, 1.0f, &effect
                );
                return;
            }
        }
    }

    for (modelIdx = 0; modelIdx < ARRAY_COUNT(*gCurrentModels); modelIdx++) {
        if ((*gCurrentModels)[modelIdx] == NULL) {
            break;
        }
    }

    (*gCurrentModels)[modelIdx] = model = heap_malloc(sizeof(*model));
    model->flags = bp->flags | MODEL_FLAG_FLAG_1;
    model->modelID = D_80153226;
    model->modelNode = bp->mdlNode;
    model->groupData = bp->groupData;
    model->matrixMode = 0;
    node = model->modelNode;
    prop = get_model_property(node, MODEL_PROP_KEY_SPECIAL);
    if (prop != NULL) {
        model->texPannerID = prop->data.s & 0xF;
    } else {
        model->texPannerID = 0;
    }
    model->customGfxIndex = 0;

    if (node->type != SHAPE_TYPE_GROUP) {
        prop = get_model_property(node, MODEL_PROP_KEY_RENDER_MODE);
    } else {
        prop = get_model_property(node, MODEL_PROP_KEY_GROUP_TYPE);

        if (prop != NULL) {
            prop = &prop[1];
        }
    }

    if (prop != NULL) {
        model->renderMode = prop->data.s;
    } else {
        model->renderMode = RENDER_MODE_SURFACE_OPA;
    }

    model->textureID = (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].textureID;
    model->textureVariation = 0;

    if (!is_identity_fixed_mtx(bp->mtx)) {
        model->currentMatrix = heap_malloc(sizeof(*model->currentMatrix));
        *model->currentMatrix = *bp->mtx;
        model->specialMatrix = *model->currentMatrix;
    } else {
        model->currentMatrix = NULL;
        guMtxIdent(&model->specialMatrix);
        model->flags |= MODEL_FLAG_FLAG_2000;
    }

    guMtxIdentF(model->transformMatrix);
    model->currentSpecialMatrix = NULL;
    prop = get_model_property(node, MODEL_PROP_KEY_BOUNDING_BOX);
    if (prop != NULL) {
        ModelBoundingBox* bb = (ModelBoundingBox*) prop;

        x = (bb->minX + bb->maxX) * 0.5f;
        y = (bb->minY + bb->maxY) * 0.5f;
        z = (bb->minZ + bb->maxZ) * 0.5f;
    } else {
        x = y = z = 0.0f;
    }

    if (model->currentMatrix != NULL) {
        guMtxXFML(model->currentMatrix, x, y, z, &x, &y, &z);
    }

    model->center.x = x;
    model->center.y = y;
    model->center.z = z;


    bb = (ModelBoundingBox*) prop;
    x = bb->maxX - bb->minX;
    y = bb->maxY - bb->minY;
    z = bb->maxZ - bb->minZ;
    bb->halfSizeX = x * 0.5;
    bb->halfSizeY = y * 0.5;
    bb->halfSizeZ = z * 0.5;

    if (model->currentMatrix == NULL && x < 100.0f && y < 100.0f && z < 100.0f) {
        model->flags |= MODEL_FLAG_FLAG_200;
    }
    (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].modelIndex = modelIdx;
}

// Mysterious no-op
void iterate_models(void) {
    Model* nonNull;
    Model* ret;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentModels); i++) {
        ret = (*gCurrentModels)[i];
        if (ret != NULL) {
            nonNull = ret;
        }
    }
    ret = nonNull;
}

void func_80116698(void) {
    Matrix4f sp20;
    Matrix4f sp60;
    f32 mX, mY, mZ;
    f32 mtgX, mtgY, mtgZ;
    Model* model;
    Mtx* mtx;
    ModelBoundingBox* bb;
    ModelTransformGroup* mtg;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentModels); i++) {
        model = (*gCurrentModels)[i];
        if (model != NULL && (model->flags != 0) && !(model->flags & MODEL_FLAG_FLAG_4)) {
            if (!(model->flags & MODEL_FLAG_USES_TRANSFORM_MATRIX)) {
                if (model->matrixMode != 0) {
                    model->matrixMode--;
                    if (model->matrixMode <= 0) {
                        model->specialMatrix = *model->currentSpecialMatrix;
                    }
                    mtx = model->currentSpecialMatrix;
                    model->currentSpecialMatrix = &gDisplayContext->matrixStack[gMatrixListPos++];
                    *model->currentSpecialMatrix = *mtx;
                } else {
                    model->currentSpecialMatrix = &model->specialMatrix;
                }
            } else {
                model->flags &= ~MODEL_FLAG_USES_TRANSFORM_MATRIX;
                model->matrixMode = 2;
                mtx = &gDisplayContext->matrixStack[gMatrixListPos++];
                if (model->currentMatrix == NULL || (model->flags & MODEL_FLAG_TRANSFORM_GROUP_MEMBER)) {
                    guMtxF2L(model->transformMatrix, mtx);
                } else {
                    guMtxL2F(sp20, model->currentMatrix);
                    guMtxCatF(model->transformMatrix, sp20, sp20);
                    guMtxF2L(sp20, mtx);
                }
                model->flags &= ~MODEL_FLAG_FLAG_2000;
                bb = (ModelBoundingBox*) get_model_property(model->modelNode, MODEL_PROP_KEY_BOUNDING_BOX);
                mX = (bb->minX + bb->maxX) * 0.5f;
                mY = (bb->minY + bb->maxY) * 0.5f;
                mZ = (bb->minZ + bb->maxZ) * 0.5f;
                guMtxXFML(mtx, mX, mY, mZ, &mX, &mY, &mZ);
                model->currentSpecialMatrix = mtx;
                model->center.x = mX;
                model->center.y = mY;
                model->center.z = mZ;
                model->flags &= ~MODEL_FLAG_FLAG_200;
            }
        }
    }

    for (i = 0; i < ARRAY_COUNT((*gCurrentTransformGroups)); i++) {
        mtg = (*gCurrentTransformGroups)[i];
        if (mtg != NULL && mtg->flags != 0 && !(mtg->flags & MODEL_TRANSFORM_GROUP_FLAG_4)) {
            if (!(mtg->flags & MODEL_TRANSFORM_GROUP_FLAG_1000)) {
                if (mtg->matrixMode != 0) {
                    mtg->matrixMode--;
                    if (!(mtg->matrixMode & 0xFF)) {
                        mtg->matrixA = *mtg->transformMtx;
                    }
                    mtx = mtg->transformMtx;
                    mtg->transformMtx = &gDisplayContext->matrixStack[gMatrixListPos++];
                    *mtg->transformMtx = *mtx;
                } else {
                    mtg->transformMtx = &mtg->matrixA;
                }
            } else {
                mtg->flags &= ~MODEL_TRANSFORM_GROUP_FLAG_1000;
                mtg->matrixMode = 2;
                mtx = &gDisplayContext->matrixStack[gMatrixListPos++];
                if (mtg->matrixRDP_N == NULL) {
                    guMtxF2L(mtg->matrixB, mtx);
                } else {
                    guMtxL2F(sp60, mtg->matrixRDP_N);
                    guMtxCatF(mtg->matrixB, sp60, sp60);
                    guMtxF2L(sp60, mtx);
                }
                mtg->flags &= ~MODEL_TRANSFORM_GROUP_FLAG_2000;
                bb = (ModelBoundingBox*) get_model_property(mtg->modelNode, MODEL_PROP_KEY_BOUNDING_BOX);
                mtgX = (bb->minX + bb->maxX) * 0.5f;
                mtgY = (bb->minY + bb->maxY) * 0.5f;
                mtgZ = (bb->minZ + bb->maxZ) * 0.5f;
                guMtxXFML(mtx, mtgX, mtgY, mtgZ, &mtgX, &mtgY, &mtgZ);
                mtg->transformMtx = mtx;
                mtg->center.x = mtgX;
                mtg->center.y = mtgY;
                mtg->center.z = mtgZ;
            }
        }
    }
    build_custom_gfx();
}

void render_models(void) {
    RenderTask rt;
    RenderTask* rtPtr = &rt;
    f32 outX, outY, outZ, outW;
    f32 m00, m01, m02, m03;
    f32 m10, m11, m12, m13;
    f32 m20, m21, m22, m23;
    f32 m30, m31, m32, m33;
    f32 x, y, z;
    f32 bbx, bby, bbz;

    Camera* camera = &gCameras[gCurrentCameraID];
    Model* model;
    ModelBoundingBox* boundingBox;
    ModelTransformGroup* transformGroup;
    f32 xComp, yComp, zComp;

    s32 distance;
    s32 cond;
    s32 i;

#define COMMON_RENDER_MODELS \
    outX = (m00 * xComp) + (m10 * yComp) + (m20 * zComp) + m30; \
    outY = (m01 * xComp) + (m11 * yComp) + (m21 * zComp) + m31; \
    outZ = (m02 * xComp) + (m12 * yComp) + (m22 * zComp) + m32; \
    outW = (m03 * xComp) + (m13 * yComp) + (m23 * zComp) + m33; \
    if (outW == 0.0f) { \
        break; \
    } \
    /* Perspective divide */ \
    outW = 1.0f / outW; \
    xComp = outX * outW; \
    yComp = outY * outW; \
    zComp = outZ * outW; \
    if (zComp > -1.0f && xComp >= -1.0f && xComp <= 1.0f && yComp >= -1.0f && yComp <= 1.0f) { \
        break; \
    }

    m00 = camera->perspectiveMatrix[0][0];
    m01 = camera->perspectiveMatrix[0][1];
    m02 = camera->perspectiveMatrix[0][2];
    m03 = camera->perspectiveMatrix[0][3];
    m10 = camera->perspectiveMatrix[1][0];
    m11 = camera->perspectiveMatrix[1][1];
    m12 = camera->perspectiveMatrix[1][2];
    m13 = camera->perspectiveMatrix[1][3];
    m20 = camera->perspectiveMatrix[2][0];
    m21 = camera->perspectiveMatrix[2][1];
    m22 = camera->perspectiveMatrix[2][2];
    m23 = camera->perspectiveMatrix[2][3];
    m30 = camera->perspectiveMatrix[3][0];
    m31 = camera->perspectiveMatrix[3][1];
    m32 = camera->perspectiveMatrix[3][2];
    m33 = camera->perspectiveMatrix[3][3];

    for (i = 0; i < ARRAY_COUNT(*gCurrentModels); i++) {
        model = (*gCurrentModels)[i];
        if (model == NULL) {
            continue;
        }
        if (model->flags == 0) {
            continue;
        }
        if (model->flags & MODEL_FLAG_FLAG_4) {
            continue;
        }
        if (model->flags & MODEL_FLAG_HIDDEN) {
            continue;
        }
        if (model->flags & MODEL_FLAG_FLAG_20) {
            continue;
        }
        if (model->flags & MODEL_FLAG_TRANSFORM_GROUP_MEMBER) {
            continue;
        }

        x = model->center.x;
        y = model->center.y;
        z = model->center.z;

        if (model->flags & MODEL_FLAG_FLAG_200) {
            cond = FALSE;
            boundingBox = (ModelBoundingBox*) model->modelNode->propertyList;
            bbx = boundingBox->halfSizeX;
            bby = boundingBox->halfSizeY;
            bbz = boundingBox->halfSizeZ;

            while (TRUE) {
                if (TRUE) {
                    xComp = x - bbx;
                    yComp = y - bby;
                    zComp = z - bbz;
                    COMMON_RENDER_MODELS;
                }

                if (bbx != 0.0f) {
                    xComp = x + bbx;
                    yComp = y - bby;
                    zComp = z - bbz;
                    COMMON_RENDER_MODELS;
                }

                if (bby != 0.0f) {
                    xComp = x - bbx;
                    yComp = y + bby;
                    zComp = z - bbz;
                    COMMON_RENDER_MODELS;
                }

                if (bbx != 0.0f && bby != 0.0f) {
                    xComp = x + bbx;
                    yComp = y + bby;
                    zComp = z - bbz;
                    COMMON_RENDER_MODELS;
                }

                if (bbz != 0.0f) {
                    xComp = x - bbx;
                    yComp = y - bby;
                    zComp = z + bbz;
                    COMMON_RENDER_MODELS;
                }

                if (bbx != 0.0f && bbz != 0.0f) {
                    xComp = x + bbx;
                    yComp = y - bby;
                    zComp = z + bbz;
                    COMMON_RENDER_MODELS;
                }

                if (bby != 0.0f && bbz != 0.0f) {
                    xComp = x - bbx;
                    yComp = y + bby;
                    zComp = z + bbz;
                    COMMON_RENDER_MODELS;
                }

                if (bbx != 0.0f && bby != 0.0f && bbz != 0.0f) {
                    xComp = x + bbx;
                    yComp = y + bby;
                    zComp = z + bbz;
                    COMMON_RENDER_MODELS;
                }
                cond = TRUE;
                break;
            }
            if (cond) {
                continue;
            }
        }

        transform_point(camera->perspectiveMatrix, x, y, z, 1.0f, &outX, &outY, &outZ, &outW);
        distance = outZ + 5000.0f;
        if (distance < 0) {
            distance = 0;
        } else if (distance > 10000) {
            distance = 10000;
        }
        rtPtr->appendGfxArg = model;
        if (model->modelNode->type == SHAPE_TYPE_GROUP) {
            rtPtr->appendGfx = appendGfx_model_group;
        } else {
            rtPtr->appendGfx = appendGfx_model;
        }
        rtPtr->distance = -distance;
        rtPtr->renderMode = model->renderMode;
        queue_render_task(rtPtr);
    }

    for (i = 0; i < ARRAY_COUNT(*gCurrentTransformGroups); i++) {
        transformGroup = (*gCurrentTransformGroups)[i];
        if (transformGroup == NULL) {
            continue;
        }

        if (transformGroup->flags == 0) {
            continue;
        }

        if (transformGroup->flags & 4) {
            continue;
        }

        xComp = transformGroup->center.x;
        yComp = transformGroup->center.y;
        zComp = transformGroup->center.z;

        transform_point(
            camera->perspectiveMatrix,
            xComp, yComp, zComp, 1.0f,
            &outX, &outY, &outZ, &outW
        );
        if (outW == 0.0f) {
            outW = 1.0f;
        }

        distance = ((outZ / outW) * 10000.0f);

        if (!(transformGroup->flags & 2)) {
            rtPtr->appendGfx = render_transform_group;
            rtPtr->appendGfxArg = transformGroup;
            rtPtr->distance = -distance;
            rtPtr->renderMode = transformGroup->renderMode;
            queue_render_task(rtPtr);
        }
    }
}

void appendGfx_model_group(void* data) {
    Model* model = data;
    s32 modelTreeDepth = (*mdl_currentModelTreeNodeInfo)[model->modelID].treeDepth;
    s32 i;

    for (i = model->modelID - 1; i >= 0; i--) {
        if (modelTreeDepth >= (*mdl_currentModelTreeNodeInfo)[i].treeDepth) {
            break;
        }
    }

    mdl_treeIterPos = i + 1;
    func_80117D00(model);
}

void func_80117D00(Model* model) {
    Model* mdl = model; // temps needed to match
    ModelNode* modelNode = mdl->modelNode;

    if (model->modelNode->type != SHAPE_TYPE_MODEL) {
        if (modelNode->groupData != NULL) {
            s32 numChildren = modelNode->groupData->numChildren;

            if (numChildren != 0) {
                s32 i;

                for (i = 0; i < numChildren; i++, mdl_treeIterPos++) {
                    Model newModel = *mdl;
                    ModelNodeProperty* prop;

                    newModel.flags = mdl->flags;
                    newModel.currentSpecialMatrix = mdl->currentSpecialMatrix;
                    newModel.modelNode = modelNode->groupData->childList[i];
                    newModel.texPannerID = mdl->texPannerID;
                    newModel.customGfxIndex = mdl->customGfxIndex;

                    if (newModel.modelNode->type == SHAPE_TYPE_MODEL) {
                        prop = get_model_property(newModel.modelNode, MODEL_PROP_KEY_RENDER_MODE);
                    } else {
                        prop = NULL;
                    }

                    if (prop != NULL) {
                        newModel.renderMode = prop->data.s;
                    } else {
                        newModel.renderMode = 0;
                    }

                    newModel.textureID = (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].textureID;
                    newModel.textureVariation = 0;
                    func_80117D00(&newModel);
                }
            }
        }
    } else {
        appendGfx_model(mdl);
    }
}

// this looks like a switch, but I can't figure it out
void render_transform_group_node(ModelNode* node) {
    Gfx** gfx = &gMainGfxPos;
    Model* model;

    if (node != NULL) {
        if (node->type == SHAPE_TYPE_GROUP) {
            ModelNodeProperty* groupTypeProp = get_model_property(node, MODEL_PROP_KEY_GROUP_TYPE);

            if (groupTypeProp != NULL && groupTypeProp->data.s != 0) {
                model = get_model_from_list_index(mdl_currentTransformGroupChildIndex);
                if (!(model->flags & MODEL_FLAG_HIDDEN)) {
                    appendGfx_model_group(model);
                }
                mdl_currentTransformGroupChildIndex++;
                return;
            }
        }
        if (node->type != SHAPE_TYPE_MODEL) {
            if (node->groupData != NULL) {
                s32 numChildren;
                s32 i;

                if (node->groupData->transformMatrix != NULL) {
                    gSPMatrix((*gfx)++, node->groupData->transformMatrix,
                              G_MTX_PUSH | G_MTX_MUL | G_MTX_MODELVIEW);
                }

                numChildren = node->groupData->numChildren;
                if (numChildren != 0) {
                    for (i = 0; i < numChildren; i++) {
                        render_transform_group_node(node->groupData->childList[i]);
                    }
                }

                if (node->groupData->transformMatrix != NULL) {
                    gSPPopMatrix((*gfx)++, G_MTX_MODELVIEW);
                }
            }
            return;
        }

        model = get_model_from_list_index(mdl_currentTransformGroupChildIndex);
        if (!(model->flags & MODEL_FLAG_HIDDEN)) {
            appendGfx_model(model);
        }
        mdl_currentTransformGroupChildIndex++;
    }
}


// gfx temps needed
void render_transform_group(void* data) {
    ModelTransformGroup* group = data;
    Gfx** gfx = &gMainGfxPos;

    if (!(group->flags & MODEL_TRANSFORM_GROUP_FLAG_4)) {
        mdl_currentTransformGroupChildIndex = group->minChildModelIndex;
        if (!(group->flags & MODEL_TRANSFORM_GROUP_FLAG_2000)) {
            gSPMatrix((*gfx)++, group->transformMtx, (G_MTX_PUSH | G_MTX_LOAD) | G_MTX_MODELVIEW);
        }

        render_transform_group_node(group->modelNode);
        if (!(group->flags & MODEL_TRANSFORM_GROUP_FLAG_2000)) {
            gSPPopMatrix((*gfx)++, G_MTX_MODELVIEW);
        }
        gDPPipeSync((*gfx)++);
    }
}

void func_801180E8(TextureHeader* header, Gfx** gfxPos, IMG_PTR raster, PAL_PTR palette, IMG_PTR auxRaster, PAL_PTR auxPalette, u8 arg6, u8 arg7, u16 arg8, u16 arg9) {
    s32 mainWidth, mainHeight;
    s32 auxWidth, auxHeight;
    s32 mainFmt;
    s32 auxFmt;
    s32 mainWrapW, mainWrapH;
    s32 auxWrapW, auxWrapH;
    s32 extraTileType;
    u32 renderType;
    s32 lod;
    s32 lodDivisor;
    IMG_PTR rasterPtr;
    s32 filteringMode;
    s32 auxPaletteIndex;
    s32 lutMode;
    s32 lodMode;
    s32 mainMasks, mainMaskt;
    s32 auxMasks, auxMaskt;
    s32 mainBitDepth;
    s32 auxBitDepth;
    s32 temp;

    mainWidth = header->mainW;
    mainHeight = header->mainH;

    lod = 0;
    auxPaletteIndex = 0;

    mainMasks = INTEGER_LOG2(mainWidth);
    mainMaskt = INTEGER_LOG2(mainHeight);

    mainWrapW = header->mainWrapW;
    mainWrapH = header->mainWrapH;

    mainFmt = header->mainFmt;
    mainBitDepth = header->mainBitDepth;

    extraTileType = header->extraTiles;
    filteringMode = header->filtering << G_MDSFT_TEXTFILT;

    auxWidth = header->auxW;
    auxHeight = header->auxH;

    auxMasks = INTEGER_LOG2(auxWidth);
    auxMaskt = INTEGER_LOG2(auxHeight);

    auxWrapW = header->auxWrapW;
    auxWrapH = header->auxWrapH;
    auxFmt = header->auxFmt;
    auxBitDepth = header->auxBitDepth;


    if (extraTileType == EXTRA_TILE_AUX_INDEPENDENT) {
        if (palette != NULL) {
            auxPaletteIndex = 1;
        } else {
            auxPaletteIndex = 0;
        }
    }

    if (palette != NULL || auxPalette != NULL) {
        lutMode = G_TT_RGBA16;
        if (palette != NULL) {
            if (mainBitDepth == G_IM_SIZ_4b) {
                gDPLoadTLUT_pal16((*gfxPos)++, 0, palette);
            } else if (mainBitDepth == G_IM_SIZ_8b) {
                gDPLoadTLUT_pal256((*gfxPos)++, palette);
            }
        }
        if (auxPalette != NULL) {
            if (auxBitDepth == G_IM_SIZ_4b) {
                gDPLoadTLUT_pal16((*gfxPos)++, auxPaletteIndex, auxPalette);
            } else if (auxBitDepth == G_IM_SIZ_8b) {
                gDPLoadTLUT_pal256((*gfxPos)++, auxPalette);
            }
        }
    } else {
        lutMode = G_TT_NONE;
    }

    renderType = header->colorCombineType;
    if (renderType >= 3) {
        renderType += 10;
    } else {
        renderType = header->extraTiles * 3 + 1 + header->colorCombineSubType;
    }

    **gfxPos = D_8014B0B8[renderType][0];
    (*gfxPos)++;

    switch (extraTileType) {
        case EXTRA_TILE_NONE:
            lodMode = G_TL_TILE;
            gSPTexture((*gfxPos)++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
            switch (mainBitDepth) {
                case G_IM_SIZ_4b:
                    gDPLoadTextureBlock_4b((*gfxPos)++, raster, mainFmt,
                                           mainWidth, mainHeight, 0,
                                           mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    break;
                case G_IM_SIZ_8b:
                    gDPLoadTextureBlock((*gfxPos)++, raster, mainFmt, G_IM_SIZ_8b,
                                        mainWidth, mainHeight, 0,
                                        mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    break;
                case G_IM_SIZ_16b:
                    gDPLoadTextureBlock((*gfxPos)++, raster, mainFmt, G_IM_SIZ_16b,
                                        mainWidth, mainHeight, 0,
                                        mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    break;
                case 3:
                    gDPLoadTextureBlock((*gfxPos)++, raster, mainFmt, G_IM_SIZ_32b,
                                        mainWidth, mainHeight, 0,
                                        mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    break;
            }
            break;
        case EXTRA_TILE_MIPMAPS:
            lodMode = G_TL_LOD;
            switch (mainBitDepth) {
                case G_IM_SIZ_4b:
                    for (rasterPtr = raster, lod = 0, lodDivisor = 1;
                         mainWidth / lodDivisor * 4 >= 64 && mainHeight / lodDivisor != 0;
                         rasterPtr += mainWidth / lodDivisor * mainHeight / lodDivisor / 2, lodDivisor *= 2, lod++)
                    {
                        gDPLoadMultiTile_4b((*gfxPos)++, rasterPtr, (u32)(rasterPtr - raster) >> 3, lod, mainFmt,
                                            mainWidth / lodDivisor, mainHeight / lodDivisor,
                                            0, 0, mainWidth / lodDivisor - 1, mainHeight / lodDivisor - 1, 0,
                                            mainWrapW, mainWrapH, mainMasks - lod, mainMaskt - lod, lod, lod);
                    }
                    break;
                case G_IM_SIZ_8b:
                    for (rasterPtr = raster, lod = 0, lodDivisor = 1;
                         mainWidth / lodDivisor * 8 >= 64 && mainHeight / lodDivisor != 0;
                         rasterPtr += mainWidth / lodDivisor * mainHeight / lodDivisor, lodDivisor *= 2, lod++)
                    {
                        gDPLoadMultiTile((*gfxPos)++, rasterPtr, ((u32)(rasterPtr - raster)) >> 3, lod, mainFmt, G_IM_SIZ_8b,
                                         mainWidth / lodDivisor, mainHeight / lodDivisor,
                                         0, 0, mainWidth / lodDivisor - 1, mainHeight / lodDivisor - 1, 0,
                                         mainWrapW, mainWrapH, mainMasks - lod, mainMaskt - lod, lod, lod);
                    }
                    break;
                case G_IM_SIZ_16b:
                    for (rasterPtr = raster, lod = 0, lodDivisor = 1;
                         mainWidth / lodDivisor * 16 >= 64 && mainHeight / lodDivisor != 0;
                         rasterPtr += mainWidth / lodDivisor * mainHeight / lodDivisor * 2, lodDivisor *= 2, lod++)
                    {
                        gDPLoadMultiTile((*gfxPos)++, rasterPtr, ((u32)(rasterPtr - raster)) >> 3, lod, mainFmt, G_IM_SIZ_16b,
                                         mainWidth / lodDivisor, mainHeight / lodDivisor,
                                         0, 0, mainWidth / lodDivisor - 1, mainHeight / lodDivisor - 1, 0,
                                         mainWrapW, mainWrapH, mainMasks - lod, mainMaskt - lod, lod, lod);
                    }
                    break;
                case G_IM_SIZ_32b:
                    for (rasterPtr = raster, lod = 0, lodDivisor = 1;
                         mainWidth / lodDivisor * 32 >= 64 && mainHeight / lodDivisor != 0;
                         rasterPtr += mainWidth / lodDivisor * mainHeight / lodDivisor * 4, lodDivisor *= 2, lod++)
                    {
                        gDPLoadMultiTile((*gfxPos)++, rasterPtr, ((u32)(rasterPtr - raster)) >> 4, lod, mainFmt, G_IM_SIZ_32b,
                                         mainWidth / lodDivisor, mainHeight / lodDivisor,
                                         0, 0, mainWidth / lodDivisor - 1, mainHeight / lodDivisor - 1, 0,
                                         mainWrapW, mainWrapH, mainMasks - lod, mainMaskt - lod, lod, lod);
                    }
                    break;
            }
            gSPTexture((*gfxPos)++, 0xFFFF, 0xFFFF, lod - 1, G_TX_RENDERTILE, G_ON);
            break;
        case EXTRA_TILE_AUX_SAME_AS_MAIN:
            gSPTexture((*gfxPos)++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
            gDPPipeSync((*gfxPos)++);
            lodMode = G_TL_TILE;
            switch (mainBitDepth) {
                case G_IM_SIZ_4b:
                    gDPScrollTextureBlockHalfHeight_4b((*gfxPos)++, raster, mainFmt, mainWidth, mainHeight, 0,
                                                       mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD,
                                                       arg8, arg9, arg6, arg7);
                    break;
                case G_IM_SIZ_8b:
                    gDPScrollTextureBlockHalfHeight((*gfxPos)++, raster, mainFmt, G_IM_SIZ_8b, mainWidth, mainHeight, 0,
                                                    mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD,
                                                    arg8, arg9, arg6, arg7);
                    break;
                case G_IM_SIZ_16b:
                    gDPScrollTextureBlockHalfHeight((*gfxPos)++, raster, mainFmt, G_IM_SIZ_16b, mainWidth, mainHeight, 0,
                                                    mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD,
                                                    arg8, arg9, arg6, arg7);
                    break;
                case G_IM_SIZ_32b:
                    gDPScrollTextureBlockHalfHeight((*gfxPos)++, raster, mainFmt, G_IM_SIZ_32b, mainWidth, mainHeight, 0,
                                                    mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD,
                                                    arg8, arg9, arg6, arg7);
                    break;
            }
            break;
        case EXTRA_TILE_AUX_INDEPENDENT:
            gSPTexture((*gfxPos)++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
            lodMode = G_TL_TILE;
            switch (mainBitDepth) {
                case G_IM_SIZ_4b:
                    gDPLoadTextureTile_4b((*gfxPos)++, raster, mainFmt, mainWidth, mainHeight,
                                          0, 0, mainWidth - 1, mainHeight - 1, 0,
                                          mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    lodDivisor = (((mainWidth * mainHeight) >> 1) + 7)>>3; // required to use lodDivisor here
                    break;
                case G_IM_SIZ_8b:
                    gDPLoadTextureTile((*gfxPos)++, raster, mainFmt, G_IM_SIZ_8b, mainWidth, mainHeight,
                                       0, 0, mainWidth - 1, mainHeight - 1, 0,
                                       mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    lodDivisor = ((mainWidth * mainHeight) + 7)>>3;
                    break;
                case G_IM_SIZ_16b:
                    gDPLoadTextureTile((*gfxPos)++, raster, mainFmt, G_IM_SIZ_16b, mainWidth, mainHeight,
                                       0, 0, mainWidth - 1, mainHeight - 1, 0,
                                       mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    lodDivisor = ((mainWidth * mainHeight) * 2 + 7)>>3;
                    break;
                case G_IM_SIZ_32b:
                    gDPLoadTextureTile((*gfxPos)++, raster, mainFmt, G_IM_SIZ_32b, mainWidth, mainHeight,
                                       0, 0, mainWidth - 1, mainHeight - 1, 0,
                                       mainWrapW, mainWrapH, mainMasks, mainMaskt, G_TX_NOLOD, G_TX_NOLOD);
                    lodDivisor = ((mainWidth * mainHeight / 2) * 2 + 7)>>3;
                    break;
            }

            switch (auxBitDepth) {
                case G_IM_SIZ_4b:
                    gDPScrollMultiTile_4b((*gfxPos)++, auxRaster, lodDivisor, 1, auxFmt, auxWidth, auxHeight,
                                          0, 0, auxWidth - 1, auxHeight - 1, auxPaletteIndex,
                                          auxWrapW, auxWrapH, auxMasks, auxMaskt,
                                          arg6, arg7, arg8, arg9);
                    break;
                case G_IM_SIZ_8b:
                    gDPScrollMultiTile((*gfxPos)++, auxRaster, lodDivisor, 1, auxFmt, G_IM_SIZ_8b, auxWidth, auxHeight,
                                       0, 0, auxWidth - 1, auxHeight - 1, auxPaletteIndex,
                                       auxWrapW, auxWrapH, auxMasks, auxMaskt,
                                       arg6, arg7, arg8, arg9);
                    break;
                case G_IM_SIZ_16b:
                    gDPScrollMultiTile((*gfxPos)++, auxRaster, lodDivisor, 1, auxFmt, G_IM_SIZ_16b, auxWidth, auxHeight,
                                       0, 0, auxWidth - 1, auxHeight - 1, auxPaletteIndex,
                                       auxWrapW, auxWrapH, auxMasks, auxMaskt,
                                       arg6, arg7, arg8, arg9);
                    break;
                case G_IM_SIZ_32b:
                    gDPScrollMultiTile((*gfxPos)++, auxRaster, lodDivisor, 1, auxFmt, G_IM_SIZ_32b, auxWidth, auxHeight,
                                       0, 0, auxWidth - 1, auxHeight - 1, auxPaletteIndex,
                                       auxWrapW, auxWrapH, auxMasks, auxMaskt,
                                       arg6, arg7, arg8, arg9);
                    break;
            }
    }
    gSPSetOtherMode((*gfxPos)++, G_SETOTHERMODE_H, 4, 16, filteringMode | G_TC_FILT | lutMode | lodMode | G_TP_PERSP );
}

Model* get_model_from_list_index(s32 listIndex) {
    return (*gCurrentModels)[listIndex];
}

void load_data_for_models(ModelNode* model, s32 romOffset, s32 size) {
    Matrix4f mtx;

    guMtxIdentF(mtx);

    if (romOffset != 0) {
        load_model_textures(model, romOffset, size);
    }

    *gCurrentModelTreeRoot = model;
    mdl_treeIterPos = 0;

    if (model != NULL) {
        load_model_transforms(model, NULL, mtx, 0);
    }
}

void load_model_transforms(ModelNode* model, ModelNode* parent, Matrix4f mdlTxMtx, s32 treeDepth) {
    Matrix4f sp10;
    Mtx sp50;
    ModelBlueprint modelBP;
    ModelBlueprint* modelBPptr = &modelBP;
    ModelNodeProperty* groupTypeProperty;
    ModelNode* modelTemp;
    s32 i;

    if (model->groupData != NULL && model->groupData->numChildren != 0) {
        s32 groupType;

        if (model->groupData->transformMatrix != NULL) {
            Matrix4f spA0;

            guMtxL2F(spA0, model->groupData->transformMatrix);
            guMtxCatF(spA0, mdlTxMtx, sp10);
        }
        groupTypeProperty = get_model_property(model, MODEL_PROP_KEY_GROUP_TYPE);

        if (groupTypeProperty == NULL) {
            groupType = 0;
        } else {
            groupType = groupTypeProperty->data.s;
        }

        if (model->type != SHAPE_TYPE_GROUP || groupType == 0) {
            for (i = 0; i < model->groupData->numChildren; i++) {
                load_model_transforms(model->groupData->childList[i], model,
                                      model->groupData->transformMatrix != NULL ? sp10 : mdlTxMtx, treeDepth + 1);
            }

            (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].modelIndex = -1;
            (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].treeDepth = treeDepth;
            mdl_treeIterPos += 1;
            return;
        }
    }

    guMtxF2L(mdlTxMtx, &sp50);
    modelBPptr->flags = 0;
    modelBPptr->mdlNode = model;
    modelBPptr->groupData = parent->groupData;
    modelBPptr->mtx = &sp50;

    if (model->type == SHAPE_TYPE_GROUP) {
        s32 childCount = mdl_get_child_count(model);

        for (i = mdl_treeIterPos; i < mdl_treeIterPos + childCount; i++) {
            (*mdl_currentModelTreeNodeInfo)[i].modelIndex = -1;
            (*mdl_currentModelTreeNodeInfo)[i].treeDepth = treeDepth + 1;
        }
        mdl_treeIterPos += childCount;
    }

    mdl_create_model(modelBPptr, 4);
    (*mdl_currentModelTreeNodeInfo)[mdl_treeIterPos].treeDepth = treeDepth;
    mdl_treeIterPos += 1;
}

s32 get_model_list_index_from_tree_index(s32 treeIndex) {
    s32 i;

    if (treeIndex < MAX_MODELS) {
        u8 modelIndex = (*mdl_currentModelTreeNodeInfo)[treeIndex].modelIndex;

        if (modelIndex != (u8)-1) {
            return modelIndex;
        }
    }

    for (i = 0; i < MAX_MODELS; i++) {
        Model* model = get_model_from_list_index(i);

        if (model != NULL && model->modelID == treeIndex) {
            return i;
        }
    }
    return 0;
}

s32 get_transform_group_index(s32 modelID) {
    ModelTransformGroup* group;
    s32 i;

    for (i = 0; i < MAX_MODEL_TRANSFORM_GROUPS; i++) {
        group = get_transform_group(i);

        if (group != NULL && group->groupModelID == modelID) {
            return i;
        }
    }

    return -1;
}

// TODO this seems to be returning center for the model and center for the BB (not the size)
void get_model_center_and_size(u16 modelID, f32* centerX, f32* centerY, f32* centerZ, f32* sizeX, f32* sizeY, f32* sizeZ) {
    Model* model = get_model_from_list_index(get_model_list_index_from_tree_index(modelID));
    ModelNode* node = model->modelNode;
    ModelBoundingBox* bb;

    *centerX = model->center.x;
    *centerY = model->center.y;
    *centerZ = model->center.z;

    bb = (ModelBoundingBox*)get_model_property(node, MODEL_PROP_KEY_BOUNDING_BOX);

    if (bb != NULL) {
        *sizeX = bb->halfSizeX;
        *sizeY = bb->halfSizeY;
        *sizeZ = bb->halfSizeZ;
    } else {
        *sizeX = *sizeY = *sizeZ = 0.0f;
    }
}

ModelTransformGroup* get_transform_group(s32 index) {
    return (*gCurrentTransformGroups)[index];
}

void func_8011B1D8(ModelNode* node) {
    ModelNode* childNode;
    ModelNodeProperty* prop;
    s32 numChildren;
    s32 i;
    u16 childCount;

    if (node->type == SHAPE_TYPE_MODEL) {
        D_80153376 = D_80153226;
        return;
    }

    if (node->type == SHAPE_TYPE_GROUP) {
        prop = get_model_property(node, MODEL_PROP_KEY_GROUP_TYPE);
        if (prop != NULL && prop->data.s != 0) {
            mdl_treeIterPos += mdl_get_child_count(node);
            D_80153376 = mdl_treeIterPos;
            return;
        }
    }

    if (node->groupData != NULL) {
        numChildren = node->groupData->numChildren;
        if (numChildren != 0) {
            for (i = 0; i < numChildren; i++) {
                childNode = node->groupData->childList[i];
                childCount = mdl_treeIterPos;
                if (childNode->type == SHAPE_TYPE_GROUP) {
                    prop = get_model_property(childNode, MODEL_PROP_KEY_GROUP_TYPE);
                    if (prop != NULL && prop->data.s != 0) {
                        childCount += mdl_get_child_count(childNode);
                    }
                }
                func_8011B1D8(childNode);

                if (D_80153370 != NULL) {
                    break;
                }

                if (D_8015336E == mdl_treeIterPos) {
                    D_80153370 = childNode;
                    D_80153374 = childCount;
                    break;
                }

                mdl_treeIterPos++;
            }
        }
    }
}

void make_transform_group(u16 modelID) {
    mdl_treeIterPos = 0;
    D_80153370 = NULL;
    D_8015336E = modelID;
    D_80153376 = 0;
    D_80153374 = 0;
    func_8011B1D8(*gCurrentModelTreeRoot);

    if (D_80153370 != 0) {
        ModelTransformGroup* newMtg;
        ModelNode* node;
        ModelNodeProperty* prop;
        ModelBoundingBox* bb;
        f32 x, y, z;
        s32 i;

        for (i = 0; i < ARRAY_COUNT(*gCurrentTransformGroups); i++) {
            if ((*gCurrentTransformGroups)[i] == NULL) {
                break;
            }
        }

        (*gCurrentTransformGroups)[i] = newMtg = heap_malloc(sizeof(*newMtg));
        newMtg->flags = MODEL_TRANSFORM_GROUP_FLAG_1;
        newMtg->groupModelID = modelID;
        newMtg->minChildModelIndex = get_model_list_index_from_tree_index(D_80153374);
        newMtg->maxChildModelIndex = get_model_list_index_from_tree_index(D_80153376);
        newMtg->matrixMode = 0;
        newMtg->matrixRDP_N = NULL;
        newMtg->modelNode = D_80153370;
        guMtxIdent(&newMtg->matrixA);
        newMtg->flags |= 0x2000;
        guMtxIdentF(newMtg->matrixB);

        node = newMtg->modelNode;

        if (node->type != SHAPE_TYPE_GROUP) {
            prop = get_model_property(node, MODEL_PROP_KEY_RENDER_MODE);
        } else {
            prop = get_model_property(node, MODEL_PROP_KEY_GROUP_TYPE);

            if (prop != NULL) {
                prop = &prop[1];
            }
        }

        if (prop != NULL) {
            newMtg->renderMode = prop->data.s;
        } else {
            newMtg->renderMode = 1;
        }

        bb = (ModelBoundingBox*)get_model_property(node, MODEL_PROP_KEY_BOUNDING_BOX);
        if (bb != NULL) {
            x = (bb->minX + bb->maxX) * 0.5f;
            y = (bb->minY + bb->maxY) * 0.5f;
            z = (bb->minZ + bb->maxZ) * 0.5f;
        } else {
            x = y = z = 0.0f;
        }

        if (newMtg->matrixRDP_N != NULL) {
            guMtxXFML(newMtg->matrixRDP_N, x, y, z, &x, &y, &z);
        }

        newMtg->center.x = x;
        newMtg->center.y = y;
        newMtg->center.z = z;
        enable_transform_group(modelID);
    }
}

void enable_transform_group(u16 modelID) {
    ModelTransformGroup* group = get_transform_group(get_transform_group_index(modelID));
    s32 i;

    group->flags &= ~0x4;

    for (i = group->minChildModelIndex; i <= group->maxChildModelIndex; i++) {
        Model* model = get_model_from_list_index(i);

        model->flags |= MODEL_FLAG_TRANSFORM_GROUP_MEMBER;

        if (model->currentMatrix != NULL) {
            model->flags |= MODEL_FLAG_USES_TRANSFORM_MATRIX;
        }
    }
}

void disable_transform_group(u16 modelID) {
    ModelTransformGroup* group = get_transform_group(get_transform_group_index(modelID));
    s32 i;

    group->flags |= MODEL_TRANSFORM_GROUP_FLAG_4;

    for (i = group->minChildModelIndex; i <= group->maxChildModelIndex; i++) {
        Model* model = get_model_from_list_index(i);

        model->flags &= ~MODEL_FLAG_TRANSFORM_GROUP_MEMBER;

        if (model->currentMatrix != NULL) {
            model->flags |= MODEL_FLAG_USES_TRANSFORM_MATRIX;
        }
    }
}

void clone_model(u16 srcModelID, u16 newModelID) {
    Model* srcModel = get_model_from_list_index(get_model_list_index_from_tree_index(srcModelID));
    Model* newModel;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentModels); i++) {
        if ((*gCurrentModels)[i] == NULL) {
            break;
        }
    }

    (*gCurrentModels)[i] = newModel = heap_malloc(sizeof(*newModel));
    *newModel = *srcModel;
    newModel->modelID = newModelID;
}

void set_model_group_visibility(u16 treeIndex, s32 flags, s32 mode) {
    s32 maxGroupIndex = -1;
    s32 minGroupIndex;
    s32 modelIndex = (*mdl_currentModelTreeNodeInfo)[treeIndex].modelIndex;
    s32 siblingIndex;
    s32 i;

    if (modelIndex < MAX_MODELS - 1) {
        minGroupIndex = maxGroupIndex = modelIndex;
    } else {
        s32 treeDepth = (*mdl_currentModelTreeNodeInfo)[treeIndex].treeDepth;
        for (i = treeIndex - 1; i >= 0; i--) {
            if ((*mdl_currentModelTreeNodeInfo)[i].treeDepth <= treeDepth) {
                break;
            }

            siblingIndex = (*mdl_currentModelTreeNodeInfo)[i].modelIndex;

            if (siblingIndex < MAX_MODELS - 1) {
                if (maxGroupIndex == -1) {
                    maxGroupIndex = siblingIndex;
                }
                minGroupIndex = siblingIndex;
            }
        }
    }

    if (mode < 2) {
        for (i = minGroupIndex; i <= maxGroupIndex; i++) {
            Model* model = (*gCurrentModels)[i];
            if (mode != MODEL_GROUP_HIDDEN) {
                model->flags &= ~flags;
            } else {
                model->flags |= flags;
            }
        }
    } else {
        for (i = 0; i < minGroupIndex; i++) {
            Model* model = (*gCurrentModels)[i];
            if (mode == MODEL_GROUP_OTHERS_VISIBLE) {
                model->flags &= ~flags;
            } else {
                model->flags |= flags;
            }
        }
        for (i = maxGroupIndex + 1; i < MAX_MODELS; i++) {
            Model* model = (*gCurrentModels)[i];
            if (model != NULL) {
                if (mode == MODEL_GROUP_OTHERS_VISIBLE) {
                    model->flags &= ~flags;
                } else {
                    model->flags |= flags;
                }
            }
        }
    }
}

void func_8011B950(u16 treeIndex, s32 customGfxIndex, s32 fogType, s32 arg3) {
    s32 maxGroupIndex = -1;
    s32 i;
    s32 minGroupIndex;
    s32 modelIndex = (*mdl_currentModelTreeNodeInfo)[treeIndex].modelIndex;
    s32 siblingIndex;
    s32 maskLow, maskHigh, newIndex;

    if (modelIndex < MAX_MODELS - 1) {
        minGroupIndex = maxGroupIndex = modelIndex;
    } else {
        s32 treeDepth = (*mdl_currentModelTreeNodeInfo)[treeIndex].treeDepth;
        for (i = treeIndex - 1; i >= 0; i--) {
            if ((*mdl_currentModelTreeNodeInfo)[i].treeDepth <= treeDepth) {
                break;
            }

            siblingIndex = (*mdl_currentModelTreeNodeInfo)[i].modelIndex;

            if (siblingIndex < MAX_MODELS - 1) {
                if (maxGroupIndex == -1) {
                    maxGroupIndex = siblingIndex;
                }
                minGroupIndex = siblingIndex;
            }
        }
    }

    maskLow = maskHigh = 0;

    if (customGfxIndex < 0) {
        maskLow = 0xF;
        customGfxIndex = 0;
    }

    if (fogType < 0) {
        maskHigh = 0xF0;
        fogType = 0;
    }

    newIndex = customGfxIndex + (fogType << 4);

    if (arg3 == 0) {
        for (i = minGroupIndex; i <= maxGroupIndex; i++) {
            Model* model = (*gCurrentModels)[i];
            model->customGfxIndex = (model->customGfxIndex & (maskLow + maskHigh)) + newIndex;
        }
    } else {
        for (i = 0; i < minGroupIndex; i++) {
            Model* model = (*gCurrentModels)[i];
            model->customGfxIndex = (model->customGfxIndex & (maskLow + maskHigh)) + newIndex;
        }
        for (i = maxGroupIndex + 1; i < MAX_MODELS; i++) {
            Model* model = (*gCurrentModels)[i];
            if (model != NULL) {
                model->customGfxIndex = (model->customGfxIndex & (maskLow + maskHigh)) + newIndex;
            }
        }
    }
}

void func_8011BAE8(void) {
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentModels); i++) {
        Model* model = (*gCurrentModels)[i];

        if (model != NULL) {
            model->flags &= ~MODEL_FLAG_HAS_TRANSFORM_APPLIED;
        }
    }

    for (i = 0; i < ARRAY_COUNT(*gCurrentTransformGroups); i++) {
        ModelTransformGroup* transformGroup = (*gCurrentTransformGroups)[i];

        if (transformGroup != NULL) {
            transformGroup->flags &= ~MODEL_TRANSFORM_GROUP_FLAG_400;
        }
    }
}

void enable_world_fog(void) {
    gCurrentFogSettings->enabled = TRUE;
}

void disable_world_fog(void) {
    gCurrentFogSettings->enabled = FALSE;
}

void set_world_fog_dist(s32 start, s32 end) {
    gCurrentFogSettings->startDistance = start;
    gCurrentFogSettings->endDistance = end;
}

void set_world_fog_color(s32 r, s32 g, s32 b, s32 a) {
    gCurrentFogSettings->r = r;
    gCurrentFogSettings->g = g;
    gCurrentFogSettings->b = b;
    gCurrentFogSettings->a = a;
}

s32 is_world_fog_enabled(void) {
    return gCurrentFogSettings->enabled;
}

void get_world_fog_distance(s32* start, s32* end) {
    *start = gCurrentFogSettings->startDistance;
    *end = gCurrentFogSettings->endDistance;
}

void get_world_fog_color(s32* r, s32* g, s32* b, s32* a) {
    *r = gCurrentFogSettings->r;
    *g = gCurrentFogSettings->g;
    *b = gCurrentFogSettings->b;
    *a = gCurrentFogSettings->a;
}

void set_tex_panner(Model* model, s32 texPannerID) {
    model->texPannerID = texPannerID;
}

void set_main_pan_u(s32 texPannerID, s32 value) {
    texPannerMainU[texPannerID] = value;
}

void set_main_pan_v(s32 texPannerID, s32 value) {
    texPannerMainV[texPannerID] = value;
}

void set_aux_pan_u(s32 texPannerID, s32 value) {
    texPannerAuxU[texPannerID] = value;
}

void set_aux_pan_v(s32 texPannerID, s32 value) {
    texPannerAuxV[texPannerID] = value;
}

void set_mdl_custom_gfx_set(Model* model, s32 customGfxIndex, u32 fogType) {
    if (customGfxIndex == -1) {
        customGfxIndex = model->customGfxIndex & 15;
    }

    if (fogType == -1) {
        fogType = model->customGfxIndex / 16;
    }

    model->customGfxIndex = (customGfxIndex & 15) + ((fogType & 15) * 16);
}

void set_custom_gfx(s32 customGfxIndex, Gfx* pre, Gfx* post) {
    (*gCurrentCustomModelGfxPtr)[customGfxIndex * 2] = pre;
    (*gCurrentCustomModelGfxPtr)[customGfxIndex * 2 + 1] = post;
}

void set_custom_gfx_builders(s32 customGfxIndex, ModelCustomGfxBuilderFunc pre, ModelCustomGfxBuilderFunc post) {
    (*gCurrentCustomModelGfxBuildersPtr)[customGfxIndex * 2] = pre;
    (*gCurrentCustomModelGfxBuildersPtr)[customGfxIndex * 2 + 1] = post;
}

void build_custom_gfx(void) {
    Gfx* gfx = gMainGfxPos;
    ModelCustomGfxBuilderFunc preFunc;
    ModelCustomGfxBuilderFunc postFunc;
    s32 i;

    gSPBranchList(gMainGfxPos++, 0x00000000);

    for (i = 0; i < ARRAY_COUNT(*gCurrentCustomModelGfxPtr) / 2; i++) {
        preFunc = (*gCurrentCustomModelGfxBuildersPtr)[i * 2];

        if (preFunc != NULL) {
            (*gCurrentCustomModelGfxPtr)[i * 2] = gMainGfxPos;
            preFunc(i);
            gSPEndDisplayList(gMainGfxPos++);
        }

        postFunc = (*gCurrentCustomModelGfxBuildersPtr)[i * 2 + 1];
        if (postFunc != NULL) {
            (*gCurrentCustomModelGfxPtr)[i * 2 + 1] = gMainGfxPos;
            postFunc(i);
            gSPEndDisplayList(gMainGfxPos++);
        }
    }

    gSPBranchList(gfx, gMainGfxPos);
}

// weird temps necessary to match
/// @returns TRUE if mtx is NULL or identity.
// TODO takes a Matrix4f, not a Matrix4s - types being weird
s32 is_identity_fixed_mtx(Mtx* mtx) {
    s32* mtxIt = (s32*)mtx;
    s32* identityIt;
    s32 i;

    if (mtx == NULL) {
        return TRUE;
    }

    identityIt = (s32*)&mdl_RDPIdentity;

    for (i = 0; i < 16; i++, mtxIt++, identityIt++) {
        if (*mtxIt != *identityIt) {
            return FALSE;
        }
    }

    return TRUE;
}

void set_background_color_blend(u8 r, u8 g, u8 b, u8 a) {
    mdl_bgMultiplyColorR = r;
    mdl_bgMultiplyColorG = g;
    mdl_bgMultiplyColorB = b;
    mdl_bgMultiplyColorA = a;
}

void get_background_color_blend(u8* r, u8* g, u8* b, u8* a) {
    *r = mdl_bgMultiplyColorR;
    *g = mdl_bgMultiplyColorG;
    *b = mdl_bgMultiplyColorB;
    *a = mdl_bgMultiplyColorA;
}

void set_model_fog_color_parameters(u8 primR, u8 primG, u8 primB, u8 primA, u8 fogR, u8 fogG, u8 fogB, s32 fogStart, s32 fogEnd) {
    mdl_renderModelFogPrimColorR = primR;
    mdl_renderModelFogPrimColorG = primG;
    mdl_renderModelFogPrimColorB = primB;
    mdl_renderModelFogPrimColorA = primA;
    mdl_renderModelFogColorR = fogR;
    mdl_renderModelFogColorG = fogG;
    mdl_renderModelFogColorB = fogB;
    mdl_renderModelFogStart = fogStart;
    mdl_renderModelFogEnd = fogEnd;
}

void get_model_fog_color_parameters(u8* primR, u8* primG, u8* primB, u8* primA, u8* fogR, u8* fogG, u8* fogB,
                                    s32* fogStart, s32* fogEnd) {
    *primR = mdl_renderModelFogPrimColorR;
    *primG = mdl_renderModelFogPrimColorG;
    *primB = mdl_renderModelFogPrimColorB;
    *primA = mdl_renderModelFogPrimColorA;
    *fogR = mdl_renderModelFogColorR;
    *fogG = mdl_renderModelFogColorG;
    *fogB = mdl_renderModelFogColorB;
    *fogStart = mdl_renderModelFogStart;
    *fogEnd = mdl_renderModelFogEnd;
}

void set_model_env_color_parameters(u8 primR, u8 primG, u8 primB, u8 envR, u8 envG, u8 envB) {
    gRenderModelPrimR = primR;
    gRenderModelPrimG = primG;
    gRenderModelPrimB = primB;
    gRenderModelEnvR = envR;
    gRenderModelEnvG = envG;
    gRenderModelEnvB = envB;
}

void get_model_env_color_parameters(u8* primR, u8* primG, u8* primB, u8* envR, u8* envG, u8* envB) {
    *primR = gRenderModelPrimR;
    *primG = gRenderModelPrimG;
    *primB = gRenderModelPrimB;
    *envR = gRenderModelEnvR;
    *envG = gRenderModelEnvG;
    *envB = gRenderModelEnvB;
}

void mdl_get_vertex_count(Gfx* gfx, s32* numVertices, Vtx** baseVtx, s32* gfxCount, Vtx* baseAddr) {
    s8 stuff[2];

    s32 vtxCount;
    u32 w0, w1;
    u32 cmd;
    u32 vtxEndAddr;
    s32 minVtx;
    s32 maxVtx;
    u32 vtxStartAddr;

    minVtx = 0;
    maxVtx = 0;

    if (gfx == NULL) {
        *numVertices = maxVtx;
        *baseVtx = (Vtx*)minVtx;
    } else {
        Gfx* baseGfx = gfx;

        do {
            w0 = gfx->words.w0;
            w1 = gfx->words.w1;
            cmd = _SHIFTR(w0,24,8);

            if (cmd == G_VTX) {
                vtxStartAddr = w1;
                if (baseAddr != NULL) {
                    vtxStartAddr = (vtxStartAddr & 0xFFFF) + (s32)baseAddr;
                }
                vtxCount = _SHIFTR(w0,12,8);
                if (minVtx == 0) {
                    minVtx = vtxStartAddr;
                    maxVtx = vtxStartAddr + (vtxCount * sizeof(Vtx));
                }
                vtxEndAddr = vtxStartAddr + (vtxCount * sizeof(Vtx));
                if (maxVtx < vtxEndAddr) {
                    maxVtx = vtxEndAddr;
                }
                if (minVtx > vtxEndAddr) {
                    minVtx = vtxEndAddr;
                }
            }
            gfx++;
        } while (cmd != G_ENDDL);

        *numVertices = (maxVtx - minVtx) >> 4;
        *baseVtx = (Vtx*)minVtx;
        *gfxCount = gfx - baseGfx;
        w1 = gfx->words.w1; // TODO required to match
    }
}

void mdl_local_gfx_update_vtx_pointers(Gfx *nodeDlist, Vtx *baseVtx, Gfx *arg2, Vtx *arg3) {
    u32 w0;
    Vtx* w1;
    do {
        w0 = (*((unsigned long long*)nodeDlist)) >> 0x20; // TODO required to match
        w1 = (Vtx*)nodeDlist->words.w1;
        if (w0 >> 0x18 == G_VTX) {
            w1 = arg3 + (w1 - baseVtx);
        }
        arg2->words.w0 = w0;
        arg2->words.w1 = (u32)w1;
        nodeDlist++;
        arg2++;
    } while (w0 >> 0x18 != G_ENDDL);
}

void mdl_local_gfx_copy_vertices(Vtx* src, s32 num, Vtx* dest) {
    u32 i;

    for (i = 0; i < num * sizeof(*src); i++) {
        ((u8*)dest)[i] = ((u8*)src)[i];
    }
}


void mdl_make_local_vertex_copy(s32 copyIndex, u16 modelID, s32 isMakingCopy) {
    s32 numVertices;
    Vtx* baseVtx;
    s32 gfxCount;
    Gfx* nodeDlist;
    Model* model;
    ModelLocalVertexCopy* copy;
    s32 i;

    model = get_model_from_list_index(get_model_list_index_from_tree_index(modelID));
    nodeDlist = model->modelNode->displayData->displayList;
    mdl_get_vertex_count(nodeDlist, &numVertices, &baseVtx, &gfxCount, NULL);

    copy = (*gCurrentModelLocalVtxBuffers)[copyIndex] = heap_malloc(sizeof(*copy));

    if (isMakingCopy) {
        for (i = 0; i < ARRAY_COUNT(copy->gfxCopy); i++) {
            copy->gfxCopy[i] = heap_malloc(gfxCount * sizeof(*copy->gfxCopy[i]));
            copy->vtxCopy[i] = heap_malloc(numVertices * sizeof(*copy->vtxCopy[i]));
            mdl_local_gfx_update_vtx_pointers(nodeDlist, baseVtx, copy->gfxCopy[i], copy->vtxCopy[i]);
            mdl_local_gfx_copy_vertices(baseVtx, numVertices, copy->vtxCopy[i]);
        }
        model->flags |= MODEL_FLAG_HAS_LOCAL_VERTEX_COPY;
    } else {
        for (i = 0; i < ARRAY_COUNT(copy->gfxCopy); i++) {
            copy->gfxCopy[i] = NULL;
            copy->vtxCopy[i] = NULL;
        }
        model->flags |= MODEL_FLAG_HIDDEN;
    }

    copy->selector = 0;
    copy->numVertices = numVertices;
    copy->minVertexAddr = baseVtx;
}

void mdl_get_copied_vertices(s32 copyIndex, Vtx** firstVertex, Vtx** copiedVertices, s32* numCopied) {
    ModelLocalVertexCopy* mlvc = (*gCurrentModelLocalVtxBuffers)[copyIndex];
    s32 selector = mlvc->selector;

    *firstVertex = mlvc->minVertexAddr;
    *copiedVertices = mlvc->vtxCopy[selector];
    *numCopied = mlvc->numVertices;
}

Gfx* mdl_get_copied_gfx(s32 copyIndex) {
    ModelLocalVertexCopy* mlvc = (*gCurrentModelLocalVtxBuffers)[copyIndex];
    s32 selector = mlvc->selector;
    Gfx* gfxCopy = mlvc->gfxCopy[selector];

    mlvc->selector++;
    if (mlvc->selector >= 2) {
        mlvc->selector = 0;
    }

    return gfxCopy;
}

#ifdef WIP
void mdl_project_tex_coords(s32 modelID, Gfx* destGfx, f32 (*destMtx)[4], void* destVertices) {
    s32 numVertices;
    Vtx* baseVtx;
    s32 gfxCount;
    f32 sp2C;
    f32 sp40;
    Vtx* temp_a0;
    f32 temp_f10;
    f32 temp_f12;
    f32 temp_f12_2;
    f32 temp_f14;
    f32 temp_f14_2;
    f32 temp_f2;
    f32 temp_f2_2;
    f32 temp_f4_2;
    f32 temp_f4_3;
    f32 temp_f8;
    f32 temp_f8_3;
    f32 var_f0;
    f32 var_f10;
    f32 var_f24;
    f32 var_f26;
    f32 var_f2;
    f32 var_f30;
    f32 var_f6;
    f32 var_f6_2;

    f32 v0ob0;
    f32 v0ob2;
    f32 v0tc0;
    f32 v0tc1;

    f32 v1ob0;
    f32 v1ob2;
    f32 v2ob0;
    f32 v1tc0;
    f32 v1tc1;
    f32 v2ob2;
    f32 v2tc0;
    f32 v2tc1;

    f32 ob0;
    f32 ob1;
    f32 ob2;

    f32 tc0;
    f32 tc1;

    s32 cn0;
    s32 cn1;
    s32 cn2;
    s32 cmd;
    s32 i;

    s32 listIndex;
    Model* model;
    Gfx* dlist;

    listIndex = get_model_list_index_from_tree_index(modelID & 0xFFFF);
    model = get_model_from_list_index(listIndex);
    dlist = model->modelNode->displayData->displayList;

    while (TRUE) {
        cmd = dlist->words.w0 >> 0x18;
        //temp_a0 = var_v0->words.w1;
        if (cmd == G_ENDDL) {
            break;
        }

        if (cmd == G_VTX) {
            baseVtx = dlist->words.w1;
            break;
        }
        dlist++;
    }

    v0ob0 = baseVtx[0].v.ob[0];
    v0ob2 = baseVtx[0].v.ob[2];
    v0tc0 = baseVtx[0].v.tc[0];
    v0tc1 = baseVtx[0].v.tc[1];

    v1ob0 = baseVtx[1].v.ob[0];
    v1ob2 = baseVtx[1].v.ob[2];
    v1tc0 = baseVtx[1].v.tc[0];
    v1tc1 = baseVtx[1].v.tc[1];

    v2ob0 = baseVtx[2].v.ob[0];
    v2ob2 = baseVtx[2].v.ob[2];
    v2tc0 = baseVtx[2].v.tc[0];
    v2tc1 = baseVtx[2].v.tc[1];

    cn0 = baseVtx[0].v.cn[0];
    cn1 = baseVtx[0].v.cn[1];
    cn2 = baseVtx[0].v.cn[2];

    if (v0ob0 != v1ob0) {
        temp_f14_2 = v0ob0 - v1ob0;
        temp_f8_3 = v0tc0 - v1tc0;
        temp_f2_2 = (v0ob0 - v2ob0) / temp_f14_2;
        temp_f12_2 = v0ob2 - v1ob2;
        temp_f10 = (temp_f2_2 * temp_f12_2) - (v0ob2 - v2ob2);
        sp40 = ((temp_f2_2 * temp_f8_3) - (v0tc0 - v2tc0)) / temp_f10;
        temp_f4_3 = v0tc1 - v1tc1;
        var_f30 = (temp_f8_3 - (temp_f12_2 * sp40)) / temp_f14_2;
        var_f26 = ((temp_f2_2 * temp_f4_3) - (v0tc1 - v2tc1)) / temp_f10;
        var_f2 = var_f26 * v0ob2;
        var_f24 = (temp_f4_3 - (temp_f12_2 * var_f26)) / temp_f14_2;
        var_f6 = (v0tc0 - (var_f30 * v0ob0)) - (sp40 * v0ob2);
        var_f0 = v0tc1 - (var_f24 * v0ob0);
    } else {
        temp_f14 = (v0ob2 - v1ob2);
        temp_f8 = v0tc0 - v1tc0;
        temp_f2 = (v0ob2 - v2ob2) / temp_f14;
        temp_f12 = v0ob0 - v1ob0;
        temp_f10 = (temp_f2 * temp_f12) - (v0ob0 - v2ob0);
        var_f30 = ((temp_f2 * temp_f8) - (v0tc0 - v2tc0)) / temp_f10;
        temp_f4_2 = v0tc1 - v1tc1;
        sp40 = (temp_f8 - (temp_f12 * var_f30)) / temp_f14;
        var_f24 = ((temp_f2 * temp_f4_2) - (v0tc1 - v2tc1)) / temp_f10;
        var_f26 = (temp_f4_2 - (temp_f12 * var_f24)) / temp_f14;
        var_f2 = var_f26 * v0ob2;
        var_f6 = (v0tc0 - (var_f30 * v0ob0)) - (sp40 * v0ob2);
        var_f0 = v0tc1 - (var_f24 * v0ob0);
    }
    sp2C = var_f6;

    mdl_get_vertex_count(destGfx, &numVertices, &baseVtx, &gfxCount, destVertices);

    for (i = 0; i < numVertices; i++) {
        ob0 = baseVtx->v.ob[0];
        ob1 = baseVtx->v.ob[1];
        ob2 = baseVtx->v.ob[2];

        if (destMtx != NULL) {
            var_f10 = (destMtx[0][0] * ob0) + (destMtx[1][0] * ob1) + (destMtx[2][0] * ob2) + destMtx[3][0];
            var_f6_2 = (destMtx[0][2] * ob0) + (destMtx[1][2] * ob1) + (destMtx[2][2] * ob2) + destMtx[3][2];
        } else {
            var_f10 = ob0;
            var_f6_2 = ob2;
        }

        tc0 = (var_f30 * var_f10) + (sp40 * var_f6_2) + sp2C;
        tc1 = (var_f24 * var_f10) + (var_f26 * var_f6_2) + (var_f0 - var_f2);

        if (tc0 < 0.0f) {
            tc0 -= 0.5;
        } else if (tc0 > 0.0f) {
            tc0 += 0.5;
        }

        if (tc1 < 0.0f) {
            tc1 -= 0.5;
        } else if (tc1 > 0.0f) {
            tc1 += 0.5;
        }

        baseVtx->v.tc[0] = tc0;
        baseVtx->v.tc[1] = tc1;
        baseVtx->v.cn[0] = cn0;
        baseVtx->v.cn[1] = cn1;
        baseVtx->v.cn[2] = cn2;
        baseVtx++;
    }
}
#else
INCLUDE_ASM(s32, "a5dd0_len_114e0", mdl_project_tex_coords);
#endif

// Checks if the center of a model is visible.
// If `depthQueryID` is nonnegative, the depth buffer is checked to see if the model's center is occluded by geometry.
//   Otherwise, the occlusion check is skipped.
// `depthQueryID` must be between 0 and the size of `depthCopyBuffer` minus 1.
// Every nonnegative value of `depthQueryID` must be unique within a frame, otherwise the result will corrupt the data
//   of the previous query that shared the same ID.
// Occlusion visibility checks are always one frame out of date, as they reference the previous frame's depth buffer.
s32 is_model_center_visible(u16 modelID, s32 depthQueryID, f32* screenX, f32* screenY) {
    Camera* camera = &gCameras[gCurrentCameraID];
    Model* model = get_model_from_list_index(get_model_list_index_from_tree_index(modelID));
    f32 outX;
    f32 outY;
    f32 outZ;
    f32 outW;

    s32 depthExponent;
    s32 depthMantissa;
    u32 shiftedMantissa, mantissaBias;
    u32 decodedDepth;
    s32 scaledDepth;

    // If an invalid depth query id was provided, return false.
    if (depthQueryID >= ARRAY_COUNT(depthCopyBuffer)) {
        return FALSE;
    }
    // Transform the model's center into clip space.
    transform_point(camera->perspectiveMatrix, model->center.x, model->center.y, model->center.z, 1.0f, &outX, &outY, &outZ, &outW);
    if (outW == 0.0f) {
        *screenX = 0.0f;
        *screenY = 0.0f;
        return TRUE;
    }
    // Perform the perspective divide (divide xyz by w) to convert to normalized device coords.
    // Normalized device coords have a range of (-1, 1) on each axis.
    outW = 1.0f / outW;
    outX *= outW;
    outY *= -outW;
    outZ *= outW;
    // Perform the viewport transform for x and y (convert normalized device coords to viewport coords).
    // Viewport coords have a range of (0, Width) for x and (0, Height) for y.
    outX = (outX * camera->viewportW + camera->viewportW) * 0.5;
    outX += camera->viewportStartX;
    outY = (outY * camera->viewportH + camera->viewportH) * 0.5;
    outY += camera->viewportStartY;
    // Convert depth from (-1, 1) to (0, 1).
    outZ = (outZ + 1.0f) * 0.5;
    // Write out the calculated x and y values.
    *screenX = (s32)outX;
    *screenY = (s32)outY;
    // If a depth query wasn't requested, simply check if the point is within the view frustum.
    if (depthQueryID < 0) {
        if (outZ > 0.0f) {
            return FALSE;
        } else {
            return TRUE;
        }
    }
    if (outX >= 0.0f && outY >= 0.0f && outX < 320.0f && outY < 240.0f) {
        gDPPipeSync(gMainGfxPos++);
        // Load a 4x1 pixel tile of the depth buffer
        gDPLoadTextureTile(gMainGfxPos++, osVirtualToPhysical(&nuGfxZBuffer[(s32) outY * 320]), G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, 1,
            (s32) outX, 0, (s32) outX + 3, 0,
            0,
            G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP,
            9, G_TX_NOMASK,
            G_TX_NOLOD, G_TX_NOLOD);
        gDPPipeSync(gMainGfxPos++);
        // Set the current color image to the buffer where copied depth values are stored.
        gDPSetColorImage(gMainGfxPos++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, depthCopyBuffer);
        gDPPipeSync(gMainGfxPos++);
        // Set up 1 cycle mode and all other relevant othermode params.
        // One cycle mode must be used here because only one pixel is copied, and copy mode only supports multiples of 4 pixels.
        gDPSetCycleType(gMainGfxPos++, G_CYC_1CYCLE);
        gDPSetRenderMode(gMainGfxPos++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
        gDPSetCombineMode(gMainGfxPos++, G_CC_DECALRGBA, G_CC_DECALRGBA);
        gDPSetTextureFilter(gMainGfxPos++, G_TF_POINT);
        gDPSetTexturePersp(gMainGfxPos++, G_TP_NONE);
        gSPTexture(gMainGfxPos++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
        gDPSetTextureLUT(gMainGfxPos++, G_TT_NONE);
        gDPSetTextureDetail(gMainGfxPos++, G_TD_CLAMP);
        gDPSetTextureLOD(gMainGfxPos++, G_TL_TILE);
        // Adjust the scissor to only draw to the specified pixel.
        gDPSetScissor(gMainGfxPos++, G_SC_NON_INTERLACE, depthQueryID, 0, depthQueryID + 1, 1);
        // Draw a texrect to copy one pixel of the loaded depth tile to the output buffer.
        gSPTextureRectangle(gMainGfxPos++, depthQueryID << 2, 0 << 2, 4 << 2, 1 << 2, G_TX_RENDERTILE, (s32) outX << 5, 0, 1 << 10, 1 << 10);
        // Sync and swap the color image back to the current framebuffer.
        gDPPipeSync(gMainGfxPos++);
        gDPSetColorImage(gMainGfxPos++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, osVirtualToPhysical(nuGfxCfb_ptr));
        gDPPipeSync(gMainGfxPos++);
        // Reconfigure the frame's normal scissor.
        gDPSetScissor(gMainGfxPos++, G_SC_NON_INTERLACE, camera->viewportStartX, camera->viewportStartY, camera->viewportStartX + camera->viewportW, camera->viewportStartY + camera->viewportH);

        // The following code will use last frame's depth value, since the copy that was just written won't be executed until the current frame is drawn.

        // Extract the exponent and mantissa from the depth buffer value.
        depthExponent = depthCopyBuffer[depthQueryID] >> DEPTH_EXPONENT_SHIFT;
        depthMantissa = (depthCopyBuffer[depthQueryID] & (DEPTH_MANTISSA_MASK | DEPTH_DZ_MASK)) >> DEPTH_MANTISSA_SHIFT;
        // Convert the exponent and mantissa into a fixed-point value.
        shiftedMantissa = depthMantissa << depthFloatLookupTable[depthExponent].shift;
        mantissaBias = depthFloatLookupTable[depthExponent].bias;
        // Remove the 3 fractional bits of precision.
        decodedDepth = (shiftedMantissa + mantissaBias) >> 3;
        // Convert the calculated screen depth into viewport depth.
        scaledDepth = outZ * MAX_VIEWPORT_DEPTH;
        if (decodedDepth < scaledDepth) {
            return FALSE;
        }
    }
    return outZ > 0.0f;
}

// Checks if a point is visible on screen.
// If `depthQueryID` is nonnegative, the depth buffer is checked to see if the point is occluded by geometry.
//   Otherwise, the occlusion check is skipped.
// `depthQueryID` must be between 0 and the size of `depthCopyBuffer` minus 1.
// Every nonnegative value of `depthQueryID` must be unique within a frame, otherwise the result will corrupt the data
//   of the previous query that shared the same ID.
// Occlusion visibility checks are always one frame out of date, as they reference the previous frame's depth buffer.
s32 is_point_visible(f32 x, f32 y, f32 z, s32 depthQueryID, f32* screenX, f32* screenY) {
    Camera* camera = &gCameras[gCurrentCameraID];
    f32 outX;
    f32 outY;
    f32 outZ;
    f32 outW;

    s32 depthExponent;
    s32 depthMantissa;
    u32 shiftedMantissa, mantissaBias;
    u32 decodedDepth;
    s32 scaledDepth;

    // If an invalid depth query id was provided, return false.
    if (depthQueryID >= ARRAY_COUNT(depthCopyBuffer)) {
        return FALSE;
    }
    // Transform the point into clip space.
    transform_point(camera->perspectiveMatrix, x, y, z, 1.0f, &outX, &outY, &outZ, &outW);
    if (outW == 0.0f) {
        *screenX = 0.0f;
        *screenY = 0.0f;
        return TRUE;
    }
    // Perform the perspective divide (divide xyz by w) to convert to normalized device coords.
    // Normalized device coords have a range of (-1, 1) on each axis.
    outW = 1.0f / outW;
    outX *= outW;
    outY *= -outW;
    outZ *= outW;
    // Perform the viewport transform for x and y (convert normalized device coords to viewport coords).
    // Viewport coords have a range of (0, Width) for x and (0, Height) for y.
    outX = (outX * camera->viewportW + camera->viewportW) * 0.5;
    outX += camera->viewportStartX;
    outY = (outY * camera->viewportH + camera->viewportH) * 0.5;
    outY += camera->viewportStartY;
    // Convert depth from (-1, 1) to (0, 1).
    outZ = (outZ + 1.0f) * 0.5;
    // Write out the calculated x and y values.
    *screenX = outX;
    *screenY = outY;
    // If a depth query wasn't requested, simply check if the point is within the view frustum.
    if (depthQueryID < 0) {
        return outZ > 0.0f;
    }
    if (outX >= 0.0f && outY >= 0.0f && outX < 320.0f && outY < 240.0f) {
        gDPPipeSync(gMainGfxPos++);
        // Load a 4x1 pixel tile of the depth buffer
        gDPLoadTextureTile(gMainGfxPos++, osVirtualToPhysical(&nuGfxZBuffer[(s32) outY * 320]), G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, 1,
            (s32) outX, 0, (s32) outX + 3, 0,
            0,
            G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP,
            9, G_TX_NOMASK,
            G_TX_NOLOD, G_TX_NOLOD);
        gDPPipeSync(gMainGfxPos++);
        // Set the current color image to the buffer where copied depth values are stored.
        gDPSetColorImage(gMainGfxPos++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, depthCopyBuffer);
        gDPPipeSync(gMainGfxPos++);
        // Set up 1 cycle mode and all other relevant othermode params.
        // One cycle mode must be used here because only one pixel is copied, and copy mode only supports multiples of 4 pixels.
        gDPSetCycleType(gMainGfxPos++, G_CYC_1CYCLE);
        gDPSetRenderMode(gMainGfxPos++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
        gDPSetCombineMode(gMainGfxPos++, G_CC_DECALRGBA, G_CC_DECALRGBA);
        gDPSetTextureFilter(gMainGfxPos++, G_TF_POINT);
        gDPSetTexturePersp(gMainGfxPos++, G_TP_NONE);
        gSPTexture(gMainGfxPos++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
        gDPSetTextureLUT(gMainGfxPos++, G_TT_NONE);
        gDPSetTextureDetail(gMainGfxPos++, G_TD_CLAMP);
        gDPSetTextureLOD(gMainGfxPos++, G_TL_TILE);
        // Adjust the scissor to only draw to the specified pixel.
        gDPSetScissor(gMainGfxPos++, G_SC_NON_INTERLACE, depthQueryID, 0, depthQueryID + 1, 1);
        // Draw a texrect to copy one pixel of the loaded depth tile to the output buffer.
        gSPTextureRectangle(gMainGfxPos++, depthQueryID << 2, 0 << 2, (depthQueryID + 1) << 2, 1 << 2, G_TX_RENDERTILE, (s32) outX << 5, 0, 1 << 10, 1 << 10);
        // Sync and swap the color image back to the current framebuffer.
        gDPPipeSync(gMainGfxPos++);
        gDPSetColorImage(gMainGfxPos++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, osVirtualToPhysical(nuGfxCfb_ptr));
        gDPPipeSync(gMainGfxPos++);
        // Reconfigure the frame's normal scissor.
        gDPSetScissor(gMainGfxPos++, G_SC_NON_INTERLACE, camera->viewportStartX, camera->viewportStartY, camera->viewportStartX + camera->viewportW, camera->viewportStartY + camera->viewportH);

        // The following code will use last frame's depth value, since the copy that was just written won't be executed until the current frame is drawn.

        // Extract the exponent and mantissa from the depth buffer value.
        depthExponent = depthCopyBuffer[depthQueryID] >> DEPTH_EXPONENT_SHIFT;
        depthMantissa = (depthCopyBuffer[depthQueryID] & (DEPTH_MANTISSA_MASK | DEPTH_DZ_MASK)) >> DEPTH_MANTISSA_SHIFT;
        // Convert the exponent and mantissa into a fixed-point value.
        shiftedMantissa = depthMantissa << depthFloatLookupTable[depthExponent].shift;
        mantissaBias = depthFloatLookupTable[depthExponent].bias;
        // Remove the 3 fractional bits of precision.
        decodedDepth = (shiftedMantissa + mantissaBias) >> 3;
        // Convert the calculated screen depth into viewport depth.
        scaledDepth = outZ * MAX_VIEWPORT_DEPTH;
        if (decodedDepth < scaledDepth) {
            return FALSE;
        }
    }
    return outZ > 0.0f;
}

void mdl_draw_hidden_panel_surface(Gfx** arg0, u16 treeIndex) {
    Model* model = get_model_from_list_index(get_model_list_index_from_tree_index(treeIndex));
    Model copied = *model;
    Gfx* oldGfxPos;
    s32 flag;

    if (*arg0 == gMainGfxPos) {
        flag = 1;
    }

    oldGfxPos = gMainGfxPos;
    gMainGfxPos = *arg0;

    copied.flags = MODEL_FLAG_HAS_LOCAL_VERTEX_COPY | MODEL_FLAG_FLAG_1;
    appendGfx_model(&copied);

    *arg0 = gMainGfxPos;

    if (flag == 0) {
        gMainGfxPos = oldGfxPos;
    }
}

void* mdl_get_next_texture_address(s32 size) {
    u32 offset = mdl_nextTextureAddress - mdl_textureBaseAddress + 0x3F;

    offset = (offset >> 6) << 6;

    if (size + offset > 0x28000) {
        return NULL;
    } else {
        return mdl_textureBaseAddress + offset;
    }

}

void mdl_set_all_fog_mode(s32 fogMode) {
    ModelList* modelList = gCurrentModels;
    Model* model;
    s32 fogType = fogMode; // weirdness here and the next line needed to match
    s32 i = fogMode;

    for (i = 0; i < ARRAY_COUNT(*modelList); i++) {
        model = (*modelList)[i];

        if (model != NULL) {
            set_mdl_custom_gfx_set(model, -1, fogType);
        }
    }
}

void clear_render_tasks(void) {
    s32 i;

    for (i = 0; i < ARRAY_COUNT(mdl_clearRenderTasks); i++) {
        mdl_renderTaskLists[i] = mdl_clearRenderTasks[i];
    }

    mdl_renderTaskQueueIdx = 0;
    mdl_renderTaskCount = 0;
}

void clear_render_tasks_alt(void) {
    s32 i;

    for (i = 0; i < ARRAY_COUNT(mdl_clearRenderTasks); i++) {
        mdl_renderTaskLists[i] = mdl_clearRenderTasks[i];
    }

    mdl_renderTaskQueueIdx = 0;
    mdl_renderTaskCount = 0;
}

RenderTask* queue_render_task(RenderTask* task) {
    RenderTask* ret = mdl_renderTaskLists[mdl_renderTaskQueueIdx];

    ASSERT(mdl_renderTaskCount < ARRAY_COUNT(*mdl_clearRenderTasks));

    ret = &ret[mdl_renderTaskCount++];

    ret->renderMode = RENDER_TASK_FLAG_ENABLED;
    if (task->renderMode == RENDER_MODE_2D) {
        ret->renderMode |= RENDER_TASK_FLAG_20;
    }

    ret->appendGfxArg = task->appendGfxArg;
    ret->appendGfx = task->appendGfx;
    ret->distance = mdl_renderTaskBasePriorities[task->renderMode] - task->distance;

    return ret;
}

void execute_render_tasks(void) {
    s32 i, j, taskCount;
    s32 sorted[ARRAY_COUNT(*mdl_clearRenderTasks)];
    RenderTask* taskList;
    RenderTask* task;
    RenderTask* task2;
    Matrix4f mtxFlipY;
    void (*appendGfx)(void*);

    if (mdl_renderTaskCount == 0) {
        return;
    }

    for (i = taskCount = 0; i < mdl_renderTaskCount; i++) {
        sorted[taskCount++] = i;
    }

    // sort in ascending order
    taskList = mdl_renderTaskLists[mdl_renderTaskQueueIdx];
    for (i = 0; i < taskCount - 1; i++) {
        for (j = i + 1; j < taskCount; j++) {
            s32 t1 = sorted[i];
            s32 t2 = sorted[j];
            task = &taskList[t1];
            task2 = &taskList[t2];
            if (task->distance > task2->distance) {
                sorted[i] = t2;
                sorted[j] = t1;
            }
        }
    }

    // tasks with dist >= 3M sort in descending order
    taskList = mdl_renderTaskLists[mdl_renderTaskQueueIdx];
    for (i = 0; i < taskCount - 1; i++) {
        task = &taskList[sorted[i]];
        if (task->distance >= 3000000) {
            for (j = i + 1; j < taskCount; j++) {
                s32 t1 = sorted[i];
                s32 t2 = sorted[j];
                task = &taskList[t1];
                task2 = &taskList[t2];
                if (task->distance < task2->distance) {
                    sorted[i] = t2;
                    sorted[j] = t1;
                }
            }
        }
    }

    // tasks with dist <= 800k sort in descending order
    taskList = mdl_renderTaskLists[mdl_renderTaskQueueIdx];
    for (i = 0; i < taskCount - 1; i++) {
        task = &taskList[sorted[i]];
        if (task->distance > 800000) {
            break;
        }
        for (j = i + 1; j < taskCount; j++) {
            s32 t1 = sorted[i];
            s32 t2 = sorted[j];
            task = &taskList[t1];
            task2 = &taskList[t2];
            if (task2->distance > 800000) {
                break;
            }
            if (task->distance < task2->distance) {
                sorted[i] = t2;
                sorted[j] = t1;
            }
        }
    }

    gLastRenderTaskCount = taskCount;
    taskList = mdl_renderTaskLists[mdl_renderTaskQueueIdx];
    if (gOverrideFlags & GLOBAL_OVERRIDES_ENABLE_FLOOR_REFLECTION) {
        Mtx* dispMtx;
        Gfx* savedGfxPos = NULL;

        guScaleF(mtxFlipY, 1.0f, -1.0f, 1.0f);
        guMtxF2L(mtxFlipY, &gDisplayContext->matrixStack[gMatrixListPos]);
        dispMtx = &gDisplayContext->matrixStack[gMatrixListPos++];
        for (i = 0; i < taskCount; i++) {
            task = &taskList[sorted[i]];
            appendGfx = task->appendGfx;

            if (task->renderMode & RENDER_TASK_FLAG_REFLECT_FLOOR) {
                savedGfxPos = gMainGfxPos++;
            }

            appendGfx(task->appendGfxArg);

            if (task->renderMode & RENDER_TASK_FLAG_REFLECT_FLOOR) {
                gSPEndDisplayList(gMainGfxPos++);
                gSPBranchList(savedGfxPos, gMainGfxPos);
                gSPDisplayList(gMainGfxPos++, savedGfxPos + 1);
                gSPMatrix(gMainGfxPos++, dispMtx, G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION);
                gSPDisplayList(gMainGfxPos++, savedGfxPos + 1);
                gSPMatrix(gMainGfxPos++, &gDisplayContext->camPerspMatrix[gCurrentCamID], G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION);
            }
        }
    } else {
        for (i = 0; i < taskCount; i++) {
            task = &taskList[sorted[i]];
            appendGfx = task->appendGfx;
            appendGfx(task->appendGfxArg);
        }
    }

    mdl_renderTaskQueueIdx++;
    if (mdl_renderTaskQueueIdx > 2) {
        mdl_renderTaskQueueIdx = 0;
    }
    mdl_renderTaskCount = 0;
}
