#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 * - * - Object parent * - * - 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); }