Files
libsm64/src/decomp/game/rendering_graph_node.c
T
NepuShiro 9ff6947911
Build libsm64 / Build libsm64 for linux (push) Failing after 28s
Build libsm64 / Build libsm64 for web (push) Failing after 29s
Build libsm64 / Build libsm64 for windows (push) Failing after 23s
Reformat to something more readable for me :3
2026-05-24 11:31:35 -05:00

1318 lines
46 KiB
C

#define NO_SEGMENTED_MEMORY
#include "../include/PR/gbi.h"
#include "../include/PR/ultratypes.h"
#include "area.h"
#include "../engine/math_util.h"
//#include "game_init.h"
//#include "gfx_dimensions.h"
//#include "main.h"
#include "../memory.h"
//#include "print.h"
#include "rendering_graph_node.h"
//#include "shadow.h"
#include "../include/sm64.h"
#include "../shim.h"
#include "../../gfx_adapter.h"
#include "../mario/model.inc.h"
// PATCH
static Vec3s gVec3sZero = {0, 0, 0};
static Vec3f gVec3fZero = {0, 0, 0};
static Gfx *gDisplayListHead;
#define USE_SYSTEM_MALLOC
/**
* This file contains the code that processes the scene graph for rendering.
* The scene graph is responsible for drawing everything except the HUD / text boxes.
* First the root of the scene graph is processed when geo_process_root
* is called from level_script.c. The rest of the tree is traversed recursively
* using the function geo_process_node_and_siblings, which switches over all
* geo node types and calls a specialized function accordingly.
* The types are defined in engine/graph_node.h
*
* The scene graph typically looks like:
* - Root (viewport)
* - Master list
* - Ortho projection
* - Background (skybox)
* - Master list
* - Perspective
* - Camera
* - <area-specific display lists>
* - Object parent
* - <group with 240 object nodes>
* - Master list
* - Script node (Cannon overlay)
*
*/
s16 gMatStackIndex;
Mat4 gMatStack[32];
Mtx *gMatStackFixed[32];
/**
* Animation nodes have state in global variables, so this struct captures
* the animation state so a 'context switch' can be made when rendering the
* held object.
*/
struct GeoAnimState
{
/*0x00*/
u8 type;
/*0x01*/
u8 enabled;
/*0x02*/
s16 frame;
/*0x04*/
f32 translationMultiplier;
/*0x08*/
u16 *attribute;
/*0x0C*/
s16 *data;
};
// For some reason, this is a GeoAnimState struct, but the current state consists
// of separate global variables. It won't match EU otherwise.
struct GeoAnimState gGeoTempState;
u8 gCurAnimType;
u8 gCurAnimEnabled;
s16 gCurrAnimFrame;
f32 gCurAnimTranslationMultiplier;
u16 *gCurrAnimAttribute;
s16 *gCurAnimData;
struct AllocOnlyPool *gDisplayListHeap;
struct RenderModeContainer
{
u32 modes[8];
};
/* Rendermode settings for cycle 1 for all 8 layers. */
struct RenderModeContainer renderModeTable_1Cycle[2] = {
{
{
G_RM_OPA_SURF,
G_RM_AA_OPA_SURF,
G_RM_AA_OPA_SURF,
G_RM_AA_OPA_SURF,
G_RM_AA_TEX_EDGE,
G_RM_AA_XLU_SURF,
G_RM_AA_XLU_SURF,
G_RM_AA_XLU_SURF,
}
},
{
{
/* z-buffered */
G_RM_ZB_OPA_SURF,
G_RM_AA_ZB_OPA_SURF,
G_RM_AA_ZB_OPA_DECAL,
G_RM_AA_ZB_OPA_INTER,
G_RM_AA_ZB_TEX_EDGE,
G_RM_AA_ZB_XLU_SURF,
G_RM_AA_ZB_XLU_DECAL,
G_RM_AA_ZB_XLU_INTER,
}
}
};
/* Rendermode settings for cycle 2 for all 8 layers. */
struct RenderModeContainer renderModeTable_2Cycle[2] = {
{
{
G_RM_OPA_SURF2,
G_RM_AA_OPA_SURF2,
G_RM_AA_OPA_SURF2,
G_RM_AA_OPA_SURF2,
G_RM_AA_TEX_EDGE2,
G_RM_AA_XLU_SURF2,
G_RM_AA_XLU_SURF2,
G_RM_AA_XLU_SURF2,
}
},
{
{
/* z-buffered */
G_RM_ZB_OPA_SURF2,
G_RM_AA_ZB_OPA_SURF2,
G_RM_AA_ZB_OPA_DECAL2,
G_RM_AA_ZB_OPA_INTER2,
G_RM_AA_ZB_TEX_EDGE2,
G_RM_AA_ZB_XLU_SURF2,
G_RM_AA_ZB_XLU_DECAL2,
G_RM_AA_ZB_XLU_INTER2,
}
}
};
struct GraphNodeRoot *gCurGraphNodeRoot = nullptr;
struct GraphNodeMasterList *gCurGraphNodeMasterList = nullptr;
struct GraphNodePerspective *gCurGraphNodeCamFrustum = nullptr;
struct GraphNodeCamera *gCurGraphNodeCamera = nullptr;
struct GraphNodeObject *gCurGraphNodeObject = nullptr;
struct GraphNodeHeldObject *gCurGraphNodeHeldObject = nullptr;
#ifdef F3DEX_GBI_2
LookAt lookAt;
#endif
/**
* Process a master list node.
*/
static void geo_process_master_list_sub(struct GraphNodeMasterList *node)
{
struct DisplayListNode *currList;
s32 i;
s32 enableZBuffer = (node->node.flags & GRAPH_RENDER_Z_BUFFER) != 0;
// struct RenderModeContainer *modeList = &renderModeTable_1Cycle[enableZBuffer];
// struct RenderModeContainer *mode2List = &renderModeTable_2Cycle[enableZBuffer];
// @bug This is where the LookAt values should be calculated but aren't.
// As a result, environment mapping is broken on Fast3DEX2 without the
// changes below.
#ifdef F3DEX_GBI_2
Mtx lMtx;
guLookAtReflect(&lMtx, &lookAt, 0, 0, 0, /* eye */ 0, 0, 1, /* at */ 1, 0, 0 /* up */);
#endif
if (enableZBuffer != 0)
{
gDPPipeSync(gDisplayListHead++);
gSPSetGeometryMode(gDisplayListHead++, G_ZBUFFER);
}
// libsm64 HACK
// Mario ends up in the second master list for some reason.
// The first item in the list is the projection matrix that is uninitialized, so just skip it.
int xx = FALSE;
for (i = 1; i < GFX_NUM_MASTER_LISTS; i++)
{
if ((currList = node->listHeads[i]) != nullptr)
{
// gDPSetRenderMode(gDisplayListHead++, modeList->modes[i], mode2List->modes[i]);
while (currList != nullptr)
{
if (!xx)
{
xx = TRUE;
currList = currList->next;
continue;
}
gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transform),
G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
gSPDisplayList(gDisplayListHead++, currList->displayList);
currList = currList->next;
}
}
}
if (enableZBuffer != 0)
{
gDPPipeSync(gDisplayListHead++);
gSPClearGeometryMode(gDisplayListHead++, G_ZBUFFER);
}
}
/**
* Appends the display list to one of the master lists based on the layer
* parameter. Look at the RenderModeContainer struct to see the corresponding
* render modes of layers.
*/
static void geo_append_display_list(void *displayList, s16 layer)
{
#ifdef F3DEX_GBI_2
gSPLookAt(gDisplayListHead++, &lookAt);
#endif
if (gCurGraphNodeMasterList != 0)
{
struct DisplayListNode *listNode =
alloc_only_pool_alloc(gDisplayListHeap, sizeof(struct DisplayListNode));
listNode->transform = gMatStackFixed[gMatStackIndex];
listNode->displayList = displayList;
listNode->next = 0;
if (gCurGraphNodeMasterList->listHeads[layer] == 0)
{
gCurGraphNodeMasterList->listHeads[layer] = listNode;
}
else
{
gCurGraphNodeMasterList->listTails[layer]->next = listNode;
}
gCurGraphNodeMasterList->listTails[layer] = listNode;
}
}
/**
* Process the master list node.
*/
static void geo_process_master_list(struct GraphNodeMasterList *node)
{
s32 i;
UNUSED s32 sp1C;
if (gCurGraphNodeMasterList == nullptr && node->node.children != nullptr)
{
gCurGraphNodeMasterList = node;
for (i = 0; i < GFX_NUM_MASTER_LISTS; i++)
{
node->listHeads[i] = nullptr;
}
geo_process_node_and_siblings(node->node.children);
geo_process_master_list_sub(node);
gCurGraphNodeMasterList = nullptr;
}
}
/**
* Process an orthographic projection node.
*/
static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node)
{
if (node->node.children != nullptr)
{
Mtx *mtx = alloc_display_list(sizeof(*mtx));
// f32 left = (gCurGraphNodeRoot->x - gCurGraphNodeRoot->width) / 2.0f * node->scale;
// f32 right = (gCurGraphNodeRoot->x + gCurGraphNodeRoot->width) / 2.0f * node->scale;
// f32 top = (gCurGraphNodeRoot->y - gCurGraphNodeRoot->height) / 2.0f * node->scale;
// f32 bottom = (gCurGraphNodeRoot->y + gCurGraphNodeRoot->height) / 2.0f * node->scale;
// guOrtho(mtx, left, right, bottom, top, -2.0f, 2.0f, 1.0f);
// gSPPerspNormalize(gDisplayListHead++, 0xFFFF);
gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH);
geo_process_node_and_siblings(node->node.children);
}
}
/**
* Process a perspective projection node.
*/
static void geo_process_perspective(struct GraphNodePerspective *node)
{
if (node->fnNode.func != nullptr)
{
node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]);
}
if (node->fnNode.node.children != nullptr)
{
// u16 perspNorm;
Mtx *mtx = alloc_display_list(sizeof(*mtx));
// #ifdef VERSION_EU
// f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f;
// #else
// f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height;
// #endif
// guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f);
// gSPPerspNormalize(gDisplayListHead++, perspNorm);
gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH);
gCurGraphNodeCamFrustum = node;
geo_process_node_and_siblings(node->fnNode.node.children);
gCurGraphNodeCamFrustum = nullptr;
}
}
/**
* Process a level of detail node. From the current transformation matrix,
* the perpendicular distance to the camera is extracted and the children
* of this node are only processed if that distance is within the render
* range of this node.
*/
static void geo_process_level_of_detail(struct GraphNodeLevelOfDetail *node)
{
#ifdef GBI_FLOATS
Mtx *mtx = gMatStackFixed[gMatStackIndex];
s16 distanceFromCam = (s32) - mtx->m[3][2]; // z-component of the translation column
#else
// The fixed point Mtx type is defined as 16 longs, but it's actually 16
// shorts for the integer parts followed by 16 shorts for the fraction parts
Mtx *mtx = gMatStackFixed[gMatStackIndex];
s16 distanceFromCam = -GET_HIGH_S16_OF_32(mtx->m[1][3]); // z-component of the translation column
#endif
#ifndef TARGET_N64
// We assume modern hardware is powerful enough to draw the most detailed variant
distanceFromCam = 0;
#endif
if (node->minDistance <= distanceFromCam && distanceFromCam < node->maxDistance)
{
if (node->node.children != 0)
{
geo_process_node_and_siblings(node->node.children);
}
}
}
/**
* Process a switch case node. The node's selection function is called
* if it is 0, and among the node's children, only the selected child is
* processed next.
*/
static void geo_process_switch(struct GraphNodeSwitchCase *node)
{
struct GraphNode *selectedChild = node->fnNode.node.children;
s32 i;
if (node->fnNode.func != nullptr)
{
node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]);
}
for (i = 0; selectedChild != nullptr && node->selectedCase > i; i++)
{
selectedChild = selectedChild->next;
}
if (selectedChild != nullptr)
{
geo_process_node_and_siblings(selectedChild);
}
}
/**
* Process a camera node.
*/
static void geo_process_camera(struct GraphNodeCamera *node)
{
Mat4 cameraTransform;
Mtx *rollMtx = alloc_display_list(sizeof(*rollMtx));
Mtx *mtx = alloc_display_list(sizeof(*mtx));
if (node->fnNode.func != nullptr)
{
node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]);
}
mtxf_rotate_xy(rollMtx, node->rollScreen);
gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(rollMtx), G_MTX_PROJECTION | G_MTX_MUL | G_MTX_NOPUSH);
mtxf_lookat(cameraTransform, node->pos, node->focus, node->roll);
mtxf_mul(gMatStack[gMatStackIndex + 1], cameraTransform, gMatStack[gMatStackIndex]);
gMatStackIndex++;
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
if (node->fnNode.node.children != 0)
{
gCurGraphNodeCamera = node;
node->matrixPtr = &gMatStack[gMatStackIndex];
geo_process_node_and_siblings(node->fnNode.node.children);
gCurGraphNodeCamera = nullptr;
}
gMatStackIndex--;
}
/**
* Process a translation / rotation node. A transformation matrix based
* on the node's translation and rotation is created and pushed on both
* the float and fixed point matrix stacks.
* For the rest it acts as a normal display list node.
*/
static void geo_process_translation_rotation(struct GraphNodeTranslationRotation *node)
{
Mat4 mtxf;
Vec3f translation;
Mtx *mtx = alloc_display_list(sizeof(*mtx));
vec3s_to_vec3f(translation, node->translation);
mtxf_rotate_zxy_and_translate(mtxf, translation, node->rotation);
mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]);
gMatStackIndex++;
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
if (node->displayList != nullptr)
{
geo_append_display_list(node->displayList, node->node.flags >> 8);
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
gMatStackIndex--;
}
/**
* Process a translation node. A transformation matrix based on the node's
* translation is created and pushed on both the float and fixed point matrix stacks.
* For the rest it acts as a normal display list node.
*/
static void geo_process_translation(struct GraphNodeTranslation *node)
{
Mat4 mtxf;
Vec3f translation;
Mtx *mtx = alloc_display_list(sizeof(*mtx));
vec3s_to_vec3f(translation, node->translation);
mtxf_rotate_zxy_and_translate(mtxf, translation, gVec3sZero);
mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]);
gMatStackIndex++;
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
if (node->displayList != nullptr)
{
geo_append_display_list(node->displayList, node->node.flags >> 8);
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
gMatStackIndex--;
}
/**
* Process a rotation node. A transformation matrix based on the node's
* rotation is created and pushed on both the float and fixed point matrix stacks.
* For the rest it acts as a normal display list node.
*/
static void geo_process_rotation(struct GraphNodeRotation *node)
{
Mat4 mtxf;
Mtx *mtx = alloc_display_list(sizeof(*mtx));
mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, node->rotation);
mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]);
gMatStackIndex++;
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
if (node->displayList != nullptr)
{
geo_append_display_list(node->displayList, node->node.flags >> 8);
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
gMatStackIndex--;
}
/**
* Process a scaling node. A transformation matrix based on the node's
* scale is created and pushed on both the float and fixed point matrix stacks.
* For the rest it acts as a normal display list node.
*/
static void geo_process_scale(struct GraphNodeScale *node)
{
UNUSED Mat4 transform;
Vec3f scaleVec;
Mtx *mtx = alloc_display_list(sizeof(*mtx));
vec3f_set(scaleVec, node->scale, node->scale, node->scale);
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec);
gMatStackIndex++;
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
if (node->displayList != nullptr)
{
geo_append_display_list(node->displayList, node->node.flags >> 8);
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
gMatStackIndex--;
}
/**
* Process a billboard node. A transformation matrix is created that makes its
* children face the camera, and it is pushed on the floating point and fixed
* point matrix stacks.
* For the rest it acts as a normal display list node.
*/
static void geo_process_billboard(struct GraphNodeBillboard *node)
{
Vec3f translation;
Mtx *mtx = alloc_display_list(sizeof(*mtx));
gMatStackIndex++;
vec3s_to_vec3f(translation, node->translation);
mtxf_billboard(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex - 1], translation,
gCurGraphNodeCamera->roll);
if (gCurGraphNodeHeldObject != nullptr)
{
mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex],
gCurGraphNodeHeldObject->objNode->header.gfx.scale);
}
else if (gCurGraphNodeObject != nullptr)
{
mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex],
gCurGraphNodeObject->scale);
}
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
if (node->displayList != nullptr)
{
geo_append_display_list(node->displayList, node->node.flags >> 8);
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
gMatStackIndex--;
}
/**
* Process a display list node. It draws a display list without first pushing
* a transformation on the stack, so all transformations are inherited from the
* parent node. It processes its children if it has them.
*/
static void geo_process_display_list(struct GraphNodeDisplayList *node)
{
if (node->displayList != nullptr)
{
geo_append_display_list(node->displayList, node->node.flags >> 8);
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
}
/**
* Process a generated list. Instead of storing a pointer to a display list,
* the list is generated on the fly by a function.
*/
static void geo_process_generated_list(struct GraphNodeGenerated *node)
{
if (node->fnNode.func != nullptr)
{
Gfx *list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node,
(struct AllocOnlyPool *)gMatStack[gMatStackIndex]);
if (list != nullptr)
{
geo_append_display_list((void *)VIRTUAL_TO_PHYSICAL(list), node->fnNode.node.flags >> 8);
}
}
if (node->fnNode.node.children != nullptr)
{
geo_process_node_and_siblings(node->fnNode.node.children);
}
}
/**
* Process a background node. Tries to retrieve a background display list from
* the function of the node. If that function is null or returns null, a black
* rectangle is drawn instead.
*/
static void geo_process_background(struct GraphNodeBackground *node)
{
Gfx *list = nullptr;
if (node->fnNode.func != nullptr)
{
list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node,
(struct AllocOnlyPool *)gMatStack[gMatStackIndex]);
}
if (list != nullptr)
{
geo_append_display_list((void *)VIRTUAL_TO_PHYSICAL(list), node->fnNode.node.flags >> 8);
}
else if (gCurGraphNodeMasterList != nullptr)
{
#ifndef F3DEX_GBI_2E
Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 7);
#else
Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 8);
#endif
Gfx *gfx = gfxStart;
gDPPipeSync(gfx++);
gDPSetCycleType(gfx++, G_CYC_FILL);
gDPSetFillColor(gfx++, node->background);
gDPFillRectangle(gfx++, GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(0), BORDER_HEIGHT,
GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(0) - 1, SCREEN_HEIGHT - BORDER_HEIGHT - 1);
gDPPipeSync(gfx++);
gDPSetCycleType(gfx++, G_CYC_1CYCLE);
gSPEndDisplayList(gfx++);
geo_append_display_list((void *)VIRTUAL_TO_PHYSICAL(gfxStart), 0);
}
if (node->fnNode.node.children != nullptr)
{
geo_process_node_and_siblings(node->fnNode.node.children);
}
}
/**
* Render an animated part. The current animation state is not part of the node
* but set in global variables. If an animated part is skipped, everything afterwards desyncs.
*/
static void geo_process_animated_part(struct GraphNodeAnimatedPart *node)
{
Mat4 matrix;
Vec3s rotation;
Vec3f translation;
Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr));
vec3s_copy(rotation, gVec3sZero);
vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]);
if (gCurAnimType == ANIM_TYPE_TRANSLATION)
{
translation[0] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
* gCurAnimTranslationMultiplier;
translation[1] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
* gCurAnimTranslationMultiplier;
translation[2] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
* gCurAnimTranslationMultiplier;
gCurAnimType = ANIM_TYPE_ROTATION;
}
else
{
if (gCurAnimType == ANIM_TYPE_LATERAL_TRANSLATION)
{
translation[0] +=
gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
* gCurAnimTranslationMultiplier;
gCurrAnimAttribute += 2;
translation[2] +=
gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
* gCurAnimTranslationMultiplier;
gCurAnimType = ANIM_TYPE_ROTATION;
}
else
{
if (gCurAnimType == ANIM_TYPE_VERTICAL_TRANSLATION)
{
gCurrAnimAttribute += 2;
translation[1] +=
gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
* gCurAnimTranslationMultiplier;
gCurrAnimAttribute += 2;
gCurAnimType = ANIM_TYPE_ROTATION;
}
else if (gCurAnimType == ANIM_TYPE_NO_TRANSLATION)
{
gCurrAnimAttribute += 6;
gCurAnimType = ANIM_TYPE_ROTATION;
}
}
}
if (gCurAnimType == ANIM_TYPE_ROTATION)
{
rotation[0] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)];
rotation[1] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)];
rotation[2] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)];
}
mtxf_rotate_xyz_and_translate(matrix, translation, rotation);
mtxf_mul(gMatStack[gMatStackIndex + 1], matrix, gMatStack[gMatStackIndex]);
gMatStackIndex++;
mtxf_to_mtx(matrixPtr, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = matrixPtr;
if (node->displayList != nullptr)
{
geo_append_display_list(node->displayList, node->node.flags >> 8);
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
gMatStackIndex--;
}
/**
* Initialize the animation-related global variables for the currently drawn
* object's animation.
*/
void geo_set_animation_globals(struct AnimInfo *node, s32 hasAnimation)
{
struct Animation *anim = node->curAnim;
if (hasAnimation)
{
node->animFrame = geo_update_animation_frame(node, &node->animFrameAccelAssist);
}
node->animTimer = gAreaUpdateCounter;
if (anim->flags & ANIM_FLAG_HOR_TRANS)
{
gCurAnimType = ANIM_TYPE_VERTICAL_TRANSLATION;
}
else if (anim->flags & ANIM_FLAG_VERT_TRANS)
{
gCurAnimType = ANIM_TYPE_LATERAL_TRANSLATION;
}
else if (anim->flags & ANIM_FLAG_6)
{
gCurAnimType = ANIM_TYPE_NO_TRANSLATION;
}
else
{
gCurAnimType = ANIM_TYPE_TRANSLATION;
}
gCurrAnimFrame = node->animFrame;
gCurAnimEnabled = (anim->flags & ANIM_FLAG_5) == 0;
gCurrAnimAttribute = segmented_to_virtual((void *)anim->index);
gCurAnimData = segmented_to_virtual((void *)anim->values);
if (anim->animYTransDivisor == 0)
{
gCurAnimTranslationMultiplier = 1.0f;
}
else
{
gCurAnimTranslationMultiplier = (f32)node->animYTrans / (f32)anim->animYTransDivisor;
}
}
/**
* Process a shadow node. Renders a shadow under an object offset by the
* translation of the first animated component and rotated according to
* the floor below it.
*/
static void geo_process_shadow(struct GraphNodeShadow *node)
{
// Gfx *shadowList;
// Mat4 mtxf;
// Vec3f shadowPos;
// Vec3f animOffset;
// f32 objScale;
// f32 shadowScale;
// f32 sinAng;
// f32 cosAng;
// struct GraphNode *geo;
// Mtx *mtx;
// if (gCurGraphNodeCamera != nullptr && gCurGraphNodeObject != nullptr) {
// if (gCurGraphNodeHeldObject != nullptr) {
// get_pos_from_transform_mtx(shadowPos, gMatStack[gMatStackIndex],
// *gCurGraphNodeCamera->matrixPtr);
// shadowScale = node->shadowScale;
// } else {
// vec3f_copy(shadowPos, gCurGraphNodeObject->pos);
// shadowScale = node->shadowScale * gCurGraphNodeObject->scale[0];
// }
// objScale = 1.0f;
// if (gCurAnimEnabled) {
// if (gCurAnimType == ANIM_TYPE_TRANSLATION
// || gCurAnimType == ANIM_TYPE_LATERAL_TRANSLATION) {
// geo = node->node.children;
// if (geo != nullptr && geo->type == GRAPH_NODE_TYPE_SCALE) {
// objScale = ((struct GraphNodeScale *) geo)->scale;
// }
// animOffset[0] =
// gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
// * gCurAnimTranslationMultiplier * objScale;
// animOffset[1] = 0.0f;
// gCurrAnimAttribute += 2;
// animOffset[2] =
// gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]
// * gCurAnimTranslationMultiplier * objScale;
// gCurrAnimAttribute -= 6;
// // simple matrix rotation so the shadow offset rotates along with the object
// sinAng = sins(gCurGraphNodeObject->angle[1]);
// cosAng = coss(gCurGraphNodeObject->angle[1]);
// shadowPos[0] += animOffset[0] * cosAng + animOffset[2] * sinAng;
// shadowPos[2] += -animOffset[0] * sinAng + animOffset[2] * cosAng;
// }
// }
// shadowList = create_shadow_below_xyz(shadowPos[0], shadowPos[1], shadowPos[2], shadowScale,
// node->shadowSolidity, node->shadowType);
// if (shadowList != nullptr) {
// mtx = alloc_display_list(sizeof(*mtx));
// gMatStackIndex++;
// mtxf_translate(mtxf, shadowPos);
// mtxf_mul(gMatStack[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtr);
// mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
// gMatStackFixed[gMatStackIndex] = mtx;
// if (gShadowAboveWaterOrLava == TRUE) {
// geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 4);
// } else if (gMarioOnIceOrCarpet == 1) {
// geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 5);
// } else {
// geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 6);
// }
// gMatStackIndex--;
// }
// }
// if (node->node.children != nullptr) {
// geo_process_node_and_siblings(node->node.children);
// }
}
/**
* Check whether an object is in view to determine whether it should be drawn.
* This is known as frustum culling.
* It checks whether the object is far away, very close / behind the camera,
* or horizontally out of view. It does not check whether it is vertically
* out of view. It assumes a sphere of 300 units around the object's position
* unless the object has a culling radius node that specifies otherwise.
*
* The matrix parameter should be the top of the matrix stack, which is the
* object's transformation matrix times the camera 'look-at' matrix. The math
* is counter-intuitive, but it checks column 3 (translation vector) of this
* matrix to determine where the origin (0,0,0) in object space will be once
* transformed to camera space (x+ = right, y+ = up, z = 'coming out the screen').
* In 3D graphics, you typically model the world as being moved in front of a
* static camera instead of a moving camera through a static world, which in
* this case simplifies calculations. Note that the perspective matrix is not
* on the matrix stack, so there are still calculations with the fov to compute
* the slope of the lines of the frustum.
*
* z-
*
* \ | /
* \ | /
* \ | /
* \ | /
* \ | /
* \|/
* C x+
*
* Since (0,0,0) is unaffected by rotation, columns 0, 1 and 2 are ignored.
*/
static s32 obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix)
{
s16 cullingRadius;
s16 halfFov; // half of the fov in in-game angle units instead of degrees
struct GraphNode *geo;
f32 hScreenEdge;
if (node->node.flags & GRAPH_RENDER_INVISIBLE)
{
return FALSE;
}
geo = node->sharedChild;
// ! @bug The aspect ratio is not accounted for. When the fov value is 45,
// the horizontal effective fov is actually 60 degrees, so you can see objects
// visibly pop in or out at the edge of the screen.
halfFov = (gCurGraphNodeCamFrustum->fov / 2.0f + 1.0f) * 32768.0f / 180.0f + 0.5f;
hScreenEdge = -matrix[3][2] * sins(halfFov) / coss(halfFov);
// -matrix[3][2] is the depth, which gets multiplied by tan(halfFov) to get
// the amount of units between the center of the screen and the horizontal edge
// given the distance from the object to the camera.
#ifdef WIDESCREEN
// This multiplication should really be performed on 4:3 as well,
// but the issue will be more apparent on widescreen.
hScreenEdge *= GFX_DIMENSIONS_ASPECT_RATIO;
#endif
if (geo != nullptr && geo->type == GRAPH_NODE_TYPE_CULLING_RADIUS)
{
cullingRadius =
(f32)((struct GraphNodeCullingRadius *)geo)->cullingRadius; //! Why is there a f32 cast?
}
else
{
cullingRadius = 300;
}
// Don't render if the object is close to or behind the camera
if (matrix[3][2] > -100.0f + cullingRadius)
{
return FALSE;
}
//! This makes the HOLP not update when the camera is far away, and it
// makes PU travel safe when the camera is locked on the main map.
// If Mario were rendered with a depth over 65536 it would cause overflow
// when converting the transformation matrix to a fixed point matrix.
if (matrix[3][2] < -20000.0f - cullingRadius)
{
return FALSE;
}
// Check whether the object is horizontally in view
if (matrix[3][0] > hScreenEdge + cullingRadius)
{
return FALSE;
}
if (matrix[3][0] < -hScreenEdge - cullingRadius)
{
return FALSE;
}
return TRUE;
}
/**
* Process an object node.
*/
static void geo_process_object(struct Object *node)
{
Mat4 mtxf;
s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0;
if (node->header.gfx.areaIndex == gCurGraphNodeRoot->areaIndex)
{
if (node->header.gfx.throwMatrix != nullptr)
{
mtxf_mul(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix,
gMatStack[gMatStackIndex]);
}
else if (node->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD)
{
mtxf_billboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex],
node->header.gfx.pos, gCurGraphNodeCamera->roll);
}
else
{
mtxf_rotate_zxy_and_translate(mtxf, node->header.gfx.pos, node->header.gfx.angle);
mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]);
}
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1],
node->header.gfx.scale);
node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex];
node->header.gfx.cameraToObject[0] = gMatStack[gMatStackIndex][3][0];
node->header.gfx.cameraToObject[1] = gMatStack[gMatStackIndex][3][1];
node->header.gfx.cameraToObject[2] = gMatStack[gMatStackIndex][3][2];
// FIXME: correct types
if (node->header.gfx.animInfo.curAnim != nullptr)
{
geo_set_animation_globals(&node->header.gfx.animInfo, hasAnimation);
}
if (obj_is_in_view(&node->header.gfx, gMatStack[gMatStackIndex]))
{
Mtx *mtx = alloc_display_list(sizeof(*mtx));
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
if (node->header.gfx.sharedChild != nullptr)
{
gCurGraphNodeObject = (struct GraphNodeObject *)node;
node->header.gfx.sharedChild->parent = &node->header.gfx.node;
geo_process_node_and_siblings(node->header.gfx.sharedChild);
node->header.gfx.sharedChild->parent = nullptr;
gCurGraphNodeObject = nullptr;
}
if (node->header.gfx.node.children != nullptr)
{
geo_process_node_and_siblings(node->header.gfx.node.children);
}
}
gMatStackIndex--;
gCurAnimType = ANIM_TYPE_NONE;
node->header.gfx.throwMatrix = nullptr;
}
}
/**
* Process an object parent node. Temporarily assigns itself as the parent of
* the subtree rooted at 'sharedChild' and processes the subtree, after which the
* actual children are be processed. (in practice they are null though)
*/
static void geo_process_object_parent(struct GraphNodeObjectParent *node)
{
if (node->sharedChild != nullptr)
{
node->sharedChild->parent = (struct GraphNode *)node;
geo_process_node_and_siblings(node->sharedChild);
node->sharedChild->parent = nullptr;
}
if (node->node.children != nullptr)
{
geo_process_node_and_siblings(node->node.children);
}
}
/**
* Process a held object node.
*/
void geo_process_held_object(struct GraphNodeHeldObject *node)
{
Mat4 mat;
Vec3f translation;
Mtx *mtx = alloc_display_list(sizeof(*mtx));
#ifdef F3DEX_GBI_2
gSPLookAt(gDisplayListHead++, &lookAt);
#endif
if (node->fnNode.func != nullptr)
{
node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]);
}
if (node->objNode != nullptr && node->objNode->header.gfx.sharedChild != nullptr)
{
s32 hasAnimation = (node->objNode->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0;
translation[0] = node->translation[0] / 4.0f;
translation[1] = node->translation[1] / 4.0f;
translation[2] = node->translation[2] / 4.0f;
mtxf_translate(mat, translation);
mtxf_copy(gMatStack[gMatStackIndex + 1], *gCurGraphNodeObject->throwMatrix);
gMatStack[gMatStackIndex + 1][3][0] = gMatStack[gMatStackIndex][3][0];
gMatStack[gMatStackIndex + 1][3][1] = gMatStack[gMatStackIndex][3][1];
gMatStack[gMatStackIndex + 1][3][2] = gMatStack[gMatStackIndex][3][2];
mtxf_mul(gMatStack[gMatStackIndex + 1], mat, gMatStack[gMatStackIndex + 1]);
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1],
node->objNode->header.gfx.scale);
if (node->fnNode.func != nullptr)
{
node->fnNode.func(GEO_CONTEXT_HELD_OBJ, &node->fnNode.node,
(struct AllocOnlyPool *)gMatStack[gMatStackIndex + 1]);
}
gMatStackIndex++;
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = mtx;
gGeoTempState.type = gCurAnimType;
gGeoTempState.enabled = gCurAnimEnabled;
gGeoTempState.frame = gCurrAnimFrame;
gGeoTempState.translationMultiplier = gCurAnimTranslationMultiplier;
gGeoTempState.attribute = gCurrAnimAttribute;
gGeoTempState.data = gCurAnimData;
gCurAnimType = 0;
gCurGraphNodeHeldObject = (void *)node;
if (node->objNode->header.gfx.animInfo.curAnim != nullptr)
{
geo_set_animation_globals(&node->objNode->header.gfx.animInfo, hasAnimation);
}
geo_process_node_and_siblings(node->objNode->header.gfx.sharedChild);
gCurGraphNodeHeldObject = nullptr;
gCurAnimType = gGeoTempState.type;
gCurAnimEnabled = gGeoTempState.enabled;
gCurrAnimFrame = gGeoTempState.frame;
gCurAnimTranslationMultiplier = gGeoTempState.translationMultiplier;
gCurrAnimAttribute = gGeoTempState.attribute;
gCurAnimData = gGeoTempState.data;
gMatStackIndex--;
}
if (node->fnNode.node.children != nullptr)
{
geo_process_node_and_siblings(node->fnNode.node.children);
}
}
/**
* Processes the children of the given GraphNode if it has any
*/
void geo_try_process_children(struct GraphNode *node)
{
if (node->children != nullptr)
{
geo_process_node_and_siblings(node->children);
}
}
/**
* Process a generic geo node and its siblings.
* The first argument is the start node, and all its siblings will
* be iterated over.
*/
void geo_process_node_and_siblings(struct GraphNode *firstNode)
{
s16 iterateChildren = TRUE;
struct GraphNode *curGraphNode = firstNode;
struct GraphNode *parent = curGraphNode->parent;
// In the case of a switch node, exactly one of the children of the node is
// processed instead of all children like usual
if (parent != nullptr)
{
iterateChildren = (parent->type != GRAPH_NODE_TYPE_SWITCH_CASE);
}
do
{
if (curGraphNode->flags & GRAPH_RENDER_ACTIVE)
{
if (curGraphNode->flags & GRAPH_RENDER_CHILDREN_FIRST)
{
geo_try_process_children(curGraphNode);
}
else
{
switch (curGraphNode->type)
{
case GRAPH_NODE_TYPE_ORTHO_PROJECTION:
geo_process_ortho_projection((struct GraphNodeOrthoProjection *)curGraphNode);
break;
case GRAPH_NODE_TYPE_PERSPECTIVE:
geo_process_perspective((struct GraphNodePerspective *)curGraphNode);
break;
case GRAPH_NODE_TYPE_MASTER_LIST:
geo_process_master_list((struct GraphNodeMasterList *)curGraphNode);
break;
case GRAPH_NODE_TYPE_LEVEL_OF_DETAIL:
geo_process_level_of_detail((struct GraphNodeLevelOfDetail *)curGraphNode);
break;
case GRAPH_NODE_TYPE_SWITCH_CASE:
geo_process_switch((struct GraphNodeSwitchCase *)curGraphNode);
break;
case GRAPH_NODE_TYPE_CAMERA:
geo_process_camera((struct GraphNodeCamera *)curGraphNode);
break;
case GRAPH_NODE_TYPE_TRANSLATION_ROTATION:
geo_process_translation_rotation(
(struct GraphNodeTranslationRotation *)curGraphNode);
break;
case GRAPH_NODE_TYPE_TRANSLATION:
geo_process_translation((struct GraphNodeTranslation *)curGraphNode);
break;
case GRAPH_NODE_TYPE_ROTATION:
geo_process_rotation((struct GraphNodeRotation *)curGraphNode);
break;
case GRAPH_NODE_TYPE_OBJECT:
geo_process_object((struct Object *)curGraphNode);
break;
case GRAPH_NODE_TYPE_ANIMATED_PART:
geo_process_animated_part((struct GraphNodeAnimatedPart *)curGraphNode);
break;
case GRAPH_NODE_TYPE_BILLBOARD:
geo_process_billboard((struct GraphNodeBillboard *)curGraphNode);
break;
case GRAPH_NODE_TYPE_DISPLAY_LIST:
geo_process_display_list((struct GraphNodeDisplayList *)curGraphNode);
break;
case GRAPH_NODE_TYPE_SCALE:
geo_process_scale((struct GraphNodeScale *)curGraphNode);
break;
case GRAPH_NODE_TYPE_SHADOW:
geo_process_shadow((struct GraphNodeShadow *)curGraphNode);
break;
case GRAPH_NODE_TYPE_OBJECT_PARENT:
geo_process_object_parent((struct GraphNodeObjectParent *)curGraphNode);
break;
case GRAPH_NODE_TYPE_GENERATED_LIST:
geo_process_generated_list((struct GraphNodeGenerated *)curGraphNode);
break;
case GRAPH_NODE_TYPE_BACKGROUND:
geo_process_background((struct GraphNodeBackground *)curGraphNode);
break;
case GRAPH_NODE_TYPE_HELD_OBJ:
geo_process_held_object((struct GraphNodeHeldObject *)curGraphNode);
break;
default:
geo_try_process_children((struct GraphNode *)curGraphNode);
break;
}
}
}
else
{
if (curGraphNode->type == GRAPH_NODE_TYPE_OBJECT)
{
((struct GraphNodeObject *)curGraphNode)->throwMatrix = nullptr;
}
}
}
while (iterateChildren && (curGraphNode = curGraphNode->next) != firstNode);
}
/**
* Process a root node. This is the entry point for processing the scene graph.
* The root node itself sets up the viewport, then all its children are processed
* to set up the projection and draw display lists.
*/
// void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) {
// UNUSED s32 unused;
//
// if (node->node.flags & GRAPH_RENDER_ACTIVE) {
// Mtx *initialMatrix;
// Vp *viewport = alloc_display_list(sizeof(*viewport));
//
// #ifdef USE_SYSTEM_MALLOC
// gDisplayListHeap = alloc_only_pool_init();
// #else
// gDisplayListHeap = alloc_only_pool_init(main_pool_available() - sizeof(struct AllocOnlyPool),
// MEMORY_POOL_LEFT);
// #endif
// initialMatrix = alloc_display_list(sizeof(*initialMatrix));
// gMatStackIndex = 0;
// gCurAnimType = 0;
// vec3s_set(viewport->vp.vtrans, node->x * 4, node->y * 4, 511);
// vec3s_set(viewport->vp.vscale, node->width * 4, node->height * 4, 511);
//
// // if (b != nullptr) {
// // clear_frame_buffer(clearColor);
// // make_viewport_clip_rect(b);
// // *viewport = *b;
// // }
// // else if (c != nullptr) {
// // clear_frame_buffer(clearColor);
// // make_viewport_clip_rect(c);
// // }
//
// mtxf_identity(gMatStack[gMatStackIndex]);
// mtxf_to_mtx(initialMatrix, gMatStack[gMatStackIndex]);
// gMatStackFixed[gMatStackIndex] = initialMatrix;
// gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewport));
// gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(gMatStackFixed[gMatStackIndex]),
// G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
// gCurGraphNodeRoot = node;
// if (node->node.children != nullptr) {
// geo_process_node_and_siblings(node->node.children);
// }
// gCurGraphNodeRoot = nullptr;
// // if (gShowDebugText) {
// //#ifndef USE_SYSTEM_MALLOC
// // print_text_fmt_int(180, 36, "MEM %d",
// // gDisplayListHeap->totalSpace - gDisplayListHeap->usedSpace);
// //#endif
// // }
// main_pool_free(gDisplayListHeap);
// }
// }
void geo_process_root_hack_single_node(struct GraphNode *node)
{
gDisplayListHead = nullptr; // Currently unused, but referenced
display_list_pool_reset();
Mtx *initialMatrix;
gDisplayListHeap = alloc_only_pool_init();
initialMatrix = alloc_display_list(sizeof(*initialMatrix));
gMatStackIndex = 0;
gCurAnimType = 0;
mtxf_identity(gMatStack[gMatStackIndex]);
mtxf_to_mtx(initialMatrix, gMatStack[gMatStackIndex]);
gMatStackFixed[gMatStackIndex] = initialMatrix;
gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(gMatStackFixed[gMatStackIndex]),
G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
// Hacked in from geo_proces_object since we only have Mario
//geo_process_object( node );
if (gMarioObject->header.gfx.throwMatrix != nullptr)
{
mtxf_mul(gMatStack[gMatStackIndex + 1], *gMarioObject->header.gfx.throwMatrix, gMatStack[gMatStackIndex]);
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], gMarioObject->header.gfx.scale);
gMarioObject->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex];
}
else
{
Mat4 identity, scale, rotTran;
mtxf_identity(identity);
mtxf_scale_vec3f(scale, identity, gMarioObject->header.gfx.scale);
mtxf_rotate_zxy_and_translate(rotTran, gMarioObject->header.gfx.pos, gMarioObject->header.gfx.angle);
mtxf_mul(gMatStack[++gMatStackIndex], scale, rotTran);
}
geo_set_animation_globals(&gMarioObject->header.gfx.animInfo, 1);
gCurGraphNodeRoot = (struct GraphNodeRoot *)node;
if (node->children != nullptr)
{
geo_process_node_and_siblings(node->children);
}
gCurGraphNodeRoot = nullptr;
gMarioObject->header.gfx.throwMatrix = nullptr;
alloc_only_pool_free(gDisplayListHeap);
}