#include "patches.h" #include "fault.h" #include "transform_ids.h" u16 next_actor_transform = 0; extern FaultClient sActorFaultClient; Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play); void ZeldaArena_Free(void* ptr); void Actor_CleanupContext(ActorContext* actorCtx, PlayState* play) { s32 i; Fault_RemoveClient(&sActorFaultClient); for (i = 0; i < ARRAY_COUNT(actorCtx->actorLists); i++) { if (i != ACTORCAT_PLAYER) { Actor* actor = actorCtx->actorLists[i].first; while (actor != NULL) { Actor_Delete(actorCtx, actor, play); actor = actorCtx->actorLists[i].first; } } } while (actorCtx->actorLists[ACTORCAT_PLAYER].first != NULL) { Actor_Delete(actorCtx, actorCtx->actorLists[ACTORCAT_PLAYER].first, play); } if (actorCtx->absoluteSpace != NULL) { ZeldaArena_Free(actorCtx->absoluteSpace); actorCtx->absoluteSpace = NULL; } // @recomp Reset the actor transform IDs as all actors have been deleted. next_actor_transform = 0; Play_SaveCycleSceneFlags(&play->state); ActorOverlayTable_Cleanup(); } u32 create_actor_transform_id() { u32 ret = next_actor_transform; next_actor_transform++; return ret; } void Actor_Init(Actor* actor, PlayState* play) { Actor_SetWorldToHome(actor); Actor_SetShapeRotToWorld(actor); Actor_SetFocus(actor, 0.0f); Math_Vec3f_Copy(&actor->prevPos, &actor->world.pos); Actor_SetScale(actor, 0.01f); actor->targetMode = TARGET_MODE_3; actor->terminalVelocity = -20.0f; actor->xyzDistToPlayerSq = FLT_MAX; actor->uncullZoneForward = 1000.0f; actor->uncullZoneScale = 350.0f; actor->uncullZoneDownward = 700.0f; actor->hintId = TATL_HINT_ID_NONE; CollisionCheck_InitInfo(&actor->colChkInfo); actor->floorBgId = BGCHECK_SCENE; ActorShape_Init(&actor->shape, 0.0f, NULL, 0.0f); if (Object_IsLoaded(&play->objectCtx, actor->objectSlot)) { Actor_SetObjectDependency(play, actor); actor->init(actor, play); actor->init = NULL; } // @recomp Pick a transform ID for this actor and encode it into struct padding u32 cur_transform_id = create_actor_transform_id(); actorIdByte0(actor) = (cur_transform_id >> 0) & 0xFF; actorIdByte1(actor) = (cur_transform_id >> 8) & 0xFF;; } // Extract the transform ID for this actor, add the limb index and write that as the matrix group to POLY_OPA_DISP. Gfx* push_limb_matrix_group(Gfx* dlist, Actor* actor, u32 limb_index) { if (actor != NULL) { u32 cur_transform_id = actor_transform_id(actor); if (actor_get_interpolation_skipped(actor)) { gEXMatrixGroupDecomposedSkipAll(dlist++, cur_transform_id + limb_index, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE); } else { gEXMatrixGroupDecomposedNormal(dlist++, cur_transform_id + limb_index, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_ALLOW); } } return dlist; } // Extract the transform ID for this actor, add the limb index and post-limb offset and write that as the matrix group to POLY_OPA_DISP. Gfx* push_post_limb_matrix_group(Gfx* dlist, Actor* actor, u32 limb_index) { if (actor != NULL) { u32 cur_transform_id = actor_transform_id(actor); if (actor_get_interpolation_skipped(actor)) { gEXMatrixGroupDecomposedSkipAll(dlist++, cur_transform_id + limb_index, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE); } else { gEXMatrixGroupDecomposedNormal(dlist++, cur_transform_id + limb_index + ACTOR_TRANSFORM_LIMB_COUNT, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_ALLOW); } } return dlist; } // Extract the transform ID for this actor, add the limb index and write that as the matrix group to POLY_OPA_DISP. Gfx* push_skin_limb_matrix_group(Gfx* dlist, Actor* actor, u32 limb_index) { if (actor != NULL) { u32 cur_transform_id = actor_transform_id(actor); if (actor_get_interpolation_skipped(actor)) { gEXMatrixGroupDecomposedSkipAll(dlist++, cur_transform_id + limb_index, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE); } else { gEXMatrixGroupDecomposedVerts(dlist++, cur_transform_id + limb_index, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_ALLOW); } } return dlist; } Gfx* pop_limb_matrix_group(Gfx* dlist, Actor* actor) { if (actor != NULL) { gEXPopMatrixGroup(dlist++, G_MTX_MODELVIEW); } return dlist; } Gfx* pop_post_limb_matrix_group(Gfx* dlist, Actor* actor) { if (actor != NULL) { gEXPopMatrixGroup(dlist++, G_MTX_MODELVIEW); } return dlist; } /* * Draws the limb at `limbIndex` with a level of detail display lists index by `dListIndex` */ void SkelAnime_DrawLimbLod(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, Actor* actor, s32 lod) { LodLimb* limb; Gfx* dList; Vec3f pos; Vec3s rot; OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); dList = limb->dLists[lod]; if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &dList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (dList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_NewMtx(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], dList); POLY_OPA_DISP = &polyTemp[2]; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &dList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (limb->child != LIMB_DONE) { SkelAnime_DrawLimbLod(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, lod); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { SkelAnime_DrawLimbLod(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, lod); } CLOSE_DISPS(play->state.gfxCtx); } /** * Draw all limbs of type `LodLimb` in a given skeleton * Near or far display list is specified via `lod` */ void SkelAnime_DrawLod(PlayState* play, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, Actor* actor, s32 lod) { LodLimb* rootLimb; s32 pad; Gfx* dList; Vec3f pos; Vec3s rot; if (skeleton == NULL) { return; } OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable[0].x; pos.y = jointTable[0].y; pos.z = jointTable[0].z; rot = jointTable[1]; dList = rootLimb->dLists[lod]; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, 0); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &dList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (dList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_NewMtx(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], dList); POLY_OPA_DISP = &polyTemp[2]; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, 0); if (postLimbDraw != NULL) { postLimbDraw(play, 1, &dList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (rootLimb->child != LIMB_DONE) { SkelAnime_DrawLimbLod(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, lod); } Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); } /** * Draw a limb of type `LodLimb` contained within a flexible skeleton * Near or far display list is specified via `lod` */ void SkelAnime_DrawFlexLimbLod(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDrawFlex overrideLimbDraw, PostLimbDrawFlex postLimbDraw, Actor* actor, s32 lod, Mtx** mtx) { LodLimb* limb; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; newDList = limbDList = limb->dLists[lod]; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &newDList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { Matrix_ToMtx(*mtx); gSPMatrix(POLY_OPA_DISP++, *mtx, G_MTX_LOAD); gSPDisplayList(POLY_OPA_DISP++, newDList); (*mtx)++; } else if (limbDList != NULL) { Matrix_ToMtx(*mtx); (*mtx)++; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &newDList, &limbDList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (limb->child != LIMB_DONE) { SkelAnime_DrawFlexLimbLod(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, lod, mtx); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { SkelAnime_DrawFlexLimbLod(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, lod, mtx); } CLOSE_DISPS(play->state.gfxCtx); } /** * Draws all limbs of type `LodLimb` in a given flexible skeleton * Limbs in a flexible skeleton have meshes that can stretch to line up with other limbs. * An array of matrices is dynamically allocated so each limb can access any transform to ensure its meshes line up. */ void SkelAnime_DrawFlexLod(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, OverrideLimbDrawFlex overrideLimbDraw, PostLimbDrawFlex postLimbDraw, Actor* actor, s32 lod) { LodLimb* rootLimb; s32 pad; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; Mtx* mtx = GRAPH_ALLOC(play->state.gfxCtx, dListCount * sizeof(Mtx)); if (skeleton == NULL) { return; } OPEN_DISPS(play->state.gfxCtx); gSPSegment(POLY_OPA_DISP++, 0x0D, mtx); Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable[0].x; pos.y = jointTable[0].y; pos.z = jointTable[0].z; rot = jointTable[1]; newDList = limbDList = rootLimb->dLists[lod]; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, 0); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &newDList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_ToMtx(mtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], newDList); POLY_OPA_DISP = &polyTemp[2]; mtx++; } else if (limbDList != NULL) { Matrix_ToMtx(mtx); mtx++; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, 0); if (postLimbDraw != NULL) { postLimbDraw(play, 1, &newDList, &limbDList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (rootLimb->child != LIMB_DONE) { SkelAnime_DrawFlexLimbLod(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, lod, &mtx); } Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); } /* * Draws the limb of the Skeleton `skeleton` at `limbIndex` */ void SkelAnime_DrawLimbOpa(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, Actor* actor) { StandardLimb* limb; Gfx* dList; Vec3f pos; Vec3s rot; OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; dList = limb->dList; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &dList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (dList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_NewMtx(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], dList); POLY_OPA_DISP = &polyTemp[2]; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &dList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (limb->child != LIMB_DONE) { SkelAnime_DrawLimbOpa(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { SkelAnime_DrawLimbOpa(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor); } CLOSE_DISPS(play->state.gfxCtx); } /** * Draw all limbs of type `StandardLimb` in a given skeleton to the polyOpa buffer */ void SkelAnime_DrawOpa(PlayState* play, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, Actor* actor) { StandardLimb* rootLimb; s32 pad; Gfx* dList; Vec3f pos; Vec3s rot; if (skeleton == NULL) { return; } OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable[0].x; pos.y = jointTable[0].y; pos.z = jointTable[0].z; rot = jointTable[1]; dList = rootLimb->dList; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, 0); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &dList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (dList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_NewMtx(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], dList); POLY_OPA_DISP = &polyTemp[2]; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, 0); if (postLimbDraw != NULL) { postLimbDraw(play, 1, &dList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (rootLimb->child != LIMB_DONE) { SkelAnime_DrawLimbOpa(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor); } Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); } void SkelAnime_DrawFlexLimbOpa(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, Actor* actor, Mtx** limbMatricies) { StandardLimb* limb; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; newDList = limbDList = limb->dList; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &newDList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { Matrix_ToMtx(*limbMatricies); gSPMatrix(POLY_OPA_DISP++, *limbMatricies, G_MTX_LOAD); gSPDisplayList(POLY_OPA_DISP++, newDList); (*limbMatricies)++; } else if (limbDList != NULL) { Matrix_ToMtx(*limbMatricies); (*limbMatricies)++; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &limbDList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (limb->child != LIMB_DONE) { SkelAnime_DrawFlexLimbOpa(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, limbMatricies); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { SkelAnime_DrawFlexLimbOpa(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, limbMatricies); } CLOSE_DISPS(play->state.gfxCtx); } /** * Draw all limbs of type `StandardLimb` in a given flexible skeleton to the polyOpa buffer * Limbs in a flexible skeleton have meshes that can stretch to line up with other limbs. * An array of matrices is dynamically allocated so each limb can access any transform to ensure its meshes line up. */ void SkelAnime_DrawFlexOpa(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, Actor* actor) { StandardLimb* rootLimb; s32 pad; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; Mtx* mtx = GRAPH_ALLOC(play->state.gfxCtx, dListCount * sizeof(Mtx)); if (skeleton == NULL) { return; } OPEN_DISPS(play->state.gfxCtx); gSPSegment(POLY_OPA_DISP++, 0x0D, mtx); Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable[0].x; pos.y = jointTable[0].y; pos.z = jointTable[0].z; rot = jointTable[1]; newDList = limbDList = rootLimb->dList; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, 0); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &newDList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_ToMtx(mtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], newDList); POLY_OPA_DISP = &polyTemp[2]; mtx++; } else { if (limbDList != NULL) { Matrix_ToMtx(mtx); mtx++; } } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, 0); if (postLimbDraw != NULL) { postLimbDraw(play, 1, &limbDList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (rootLimb->child != LIMB_DONE) { SkelAnime_DrawFlexLimbOpa(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, &mtx); } Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); } void SkelAnime_DrawTransformFlexLimbOpa(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, TransformLimbDrawOpa transformLimbDraw, Actor* actor, Mtx** mtx) { StandardLimb* limb; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; newDList = limbDList = limb->dList; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &newDList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); Matrix_Push(); transformLimbDraw(play, limbIndex, actor); if (newDList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_ToMtx(*mtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], newDList); POLY_OPA_DISP = &polyTemp[2]; (*mtx)++; } else { if (limbDList != NULL) { Matrix_ToMtx(*mtx); (*mtx)++; } } Matrix_Pop(); } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, limbIndex); if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &limbDList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (limb->child != LIMB_DONE) { SkelAnime_DrawTransformFlexLimbOpa(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, transformLimbDraw, actor, mtx); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { SkelAnime_DrawTransformFlexLimbOpa(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, transformLimbDraw, actor, mtx); } CLOSE_DISPS(play->state.gfxCtx); } /** * Draw all limbs of type `StandardLimb` in a given flexible skeleton to the polyOpa buffer * Limbs in a flexible skeleton have meshes that can stretch to line up with other limbs. * An array of matrices is dynamically allocated so each limb can access any transform to ensure its meshes line up. * * Also makes use of a `TransformLimbDraw`, which transforms limbs based on world coordinates, as opposed to local limb * coordinates. * Note that the `TransformLimbDraw` does not have a NULL check, so must be provided even if empty. */ void SkelAnime_DrawTransformFlexOpa(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, TransformLimbDrawOpa transformLimbDraw, Actor* actor) { StandardLimb* rootLimb; s32 pad; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; Mtx* mtx; if (skeleton == NULL) { return; } OPEN_DISPS(play->state.gfxCtx); mtx = GRAPH_ALLOC(play->state.gfxCtx, dListCount * sizeof(Mtx)); gSPSegment(POLY_OPA_DISP++, 0x0D, mtx); Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable[0].x; pos.y = jointTable[0].y; pos.z = jointTable[0].z; rot = jointTable[1]; newDList = limbDList = rootLimb->dList; // @recomp Push the limb's matrix group. POLY_OPA_DISP = push_limb_matrix_group(POLY_OPA_DISP, actor, 0); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &newDList, &pos, &rot, actor)) { Matrix_TranslateRotateZYX(&pos, &rot); Matrix_Push(); transformLimbDraw(play, 1, actor); if (newDList != NULL) { Gfx* polyTemp = POLY_OPA_DISP; gSPMatrix(&polyTemp[0], Matrix_ToMtx(mtx), G_MTX_LOAD); gSPDisplayList(&polyTemp[1], newDList); POLY_OPA_DISP = &polyTemp[2]; mtx++; } else { if (limbDList != NULL) { Matrix_ToMtx(mtx++); } } Matrix_Pop(); } // @recomp Pop the limb's matrix group and push the post-limb matrix group. POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); POLY_OPA_DISP = push_post_limb_matrix_group(POLY_OPA_DISP, actor, 0); if (postLimbDraw != NULL) { postLimbDraw(play, 1, &limbDList, &rot, actor); } // @recomp Pop the post-limb matrix group. POLY_OPA_DISP = pop_post_limb_matrix_group(POLY_OPA_DISP, actor); if (rootLimb->child != LIMB_DONE) { SkelAnime_DrawTransformFlexLimbOpa(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, transformLimbDraw, actor, &mtx); } Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); } /* * Draws the Skeleton `skeleton`'s limb at index `limbIndex`. Appends all generated graphics commands to * `gfx`. Returns a pointer to the next gfx to be appended to. */ Gfx* SkelAnime_DrawLimb(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDraw overrideLimbDraw, PostLimbDraw postLimbDraw, Actor* actor, Gfx* gfx) { StandardLimb* limb; Gfx* dList; Vec3f pos; Vec3s rot; Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; dList = limb->dList; // @recomp Push the limb's matrix group. gfx = push_limb_matrix_group(gfx, actor, limbIndex); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &dList, &pos, &rot, actor, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); if (dList != NULL) { gSPMatrix(&gfx[0], Matrix_NewMtx(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(&gfx[1], dList); gfx = &gfx[2]; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. gfx = pop_limb_matrix_group(gfx, actor); gfx = push_post_limb_matrix_group(gfx, actor, limbIndex); if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &dList, &rot, actor, &gfx); } // @recomp Pop the post-limb matrix group. gfx = pop_post_limb_matrix_group(gfx, actor); if (limb->child != LIMB_DONE) { gfx = SkelAnime_DrawLimb(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, gfx); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { gfx = SkelAnime_DrawLimb(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, gfx); } return gfx; } /* * Draws the Skeleton `skeleton` Appends all generated graphics to `gfx`, and returns a pointer to the * next gfx to be appended to. */ Gfx* SkelAnime_Draw(PlayState* play, void** skeleton, Vec3s* jointTable, OverrideLimbDraw overrideLimbDraw, PostLimbDraw postLimbDraw, Actor* actor, Gfx* gfx) { StandardLimb* rootLimb; s32 pad; Gfx* dList; Vec3f pos; Vec3s rot; if (skeleton == NULL) { return NULL; } Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable[0].x; pos.y = jointTable[0].y; pos.z = jointTable[0].z; rot = jointTable[1]; dList = rootLimb->dList; // @recomp Push the limb's matrix group. gfx = push_limb_matrix_group(gfx, actor, 0); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &dList, &pos, &rot, actor, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); if (dList != NULL) { gSPMatrix(&gfx[0], Matrix_NewMtx(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(&gfx[1], dList); gfx = &gfx[2]; } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. gfx = pop_limb_matrix_group(gfx, actor); gfx = push_post_limb_matrix_group(gfx, actor, 0); if (postLimbDraw != NULL) { postLimbDraw(play, 1, &dList, &rot, actor, &gfx); } // @recomp Pop the post-limb matrix group. gfx = pop_post_limb_matrix_group(gfx, actor); if (rootLimb->child != LIMB_DONE) { gfx = SkelAnime_DrawLimb(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, gfx); } Matrix_Pop(); return gfx; } /** * Draw a limb of type `StandardLimb` contained within a flexible skeleton to the specified display buffer */ Gfx* SkelAnime_DrawFlexLimb(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* jointTable, OverrideLimbDraw overrideLimbDraw, PostLimbDraw postLimbDraw, Actor* actor, Mtx** mtx, Gfx* gfx) { StandardLimb* limb; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; Matrix_Push(); limb = Lib_SegmentedToVirtual(skeleton[limbIndex]); limbIndex++; rot = jointTable[limbIndex]; pos.x = limb->jointPos.x; pos.y = limb->jointPos.y; pos.z = limb->jointPos.z; newDList = limbDList = limb->dList; // @recomp Push the limb's matrix group. gfx = push_limb_matrix_group(gfx, actor, limbIndex); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &newDList, &pos, &rot, actor, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { gSPMatrix(&gfx[0], Matrix_ToMtx(*mtx), G_MTX_LOAD); gSPDisplayList(&gfx[1], newDList); gfx = &gfx[2]; (*mtx)++; } else { if (limbDList != NULL) { Matrix_ToMtx(*mtx); (*mtx)++; } } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. gfx = pop_limb_matrix_group(gfx, actor); gfx = push_post_limb_matrix_group(gfx, actor, limbIndex); if (postLimbDraw != NULL) { postLimbDraw(play, limbIndex, &limbDList, &rot, actor, &gfx); } // @recomp Pop the post-limb matrix group. gfx = pop_post_limb_matrix_group(gfx, actor); if (limb->child != LIMB_DONE) { gfx = SkelAnime_DrawFlexLimb(play, limb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, mtx, gfx); } Matrix_Pop(); if (limb->sibling != LIMB_DONE) { gfx = SkelAnime_DrawFlexLimb(play, limb->sibling, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, mtx, gfx); } return gfx; } /** * Draw all limbs of type `StandardLimb` in a given flexible skeleton to the specified display buffer * Limbs in a flexible skeleton have meshes that can stretch to line up with other limbs. * An array of matrices is dynamically allocated so each limb can access any transform to ensure its meshes line up. */ Gfx* SkelAnime_DrawFlex(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, OverrideLimbDraw overrideLimbDraw, PostLimbDraw postLimbDraw, Actor* actor, Gfx* gfx) { StandardLimb* rootLimb; s32 pad; Gfx* newDList; Gfx* limbDList; Vec3f pos; Vec3s rot; Mtx* mtx; if (skeleton == NULL) { return NULL; } mtx = GRAPH_ALLOC(play->state.gfxCtx, dListCount * sizeof(Mtx)); gSPSegment(gfx++, 0x0D, mtx); Matrix_Push(); rootLimb = Lib_SegmentedToVirtual(skeleton[0]); pos.x = jointTable[0].x; pos.y = jointTable[0].y; pos.z = jointTable[0].z; rot = jointTable[1]; newDList = limbDList = rootLimb->dList; // @recomp Push the limb's matrix group. gfx = push_limb_matrix_group(gfx, actor, 0); if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &newDList, &pos, &rot, actor, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { gSPMatrix(&gfx[0], Matrix_ToMtx(mtx), G_MTX_LOAD); gSPDisplayList(&gfx[1], newDList); gfx = &gfx[2]; mtx++; } else { if (limbDList != NULL) { Matrix_ToMtx(mtx); mtx++; } } } // @recomp Pop the limb's matrix group and push the post-limb matrix group. gfx = pop_limb_matrix_group(gfx, actor); gfx = push_post_limb_matrix_group(gfx, actor, 0); if (postLimbDraw != NULL) { postLimbDraw(play, 1, &limbDList, &rot, actor, &gfx); } // @recomp Pop the post-limb matrix group. gfx = pop_post_limb_matrix_group(gfx, actor); if (rootLimb->child != LIMB_DONE) { gfx = SkelAnime_DrawFlexLimb(play, rootLimb->child, skeleton, jointTable, overrideLimbDraw, postLimbDraw, actor, &mtx, gfx); } Matrix_Pop(); return gfx; } extern MtxF gSkinLimbMatrices[]; void Skin_DrawImpl(Actor* actor, PlayState* play, Skin* skin, SkinPostDraw postDraw, SkinOverrideLimbDraw overrideLimbDraw, s32 setTranslation, s32 arg6, s32 drawFlags) { s32 i; SkinLimb** skeleton; GraphicsContext* gfxCtx = play->state.gfxCtx; OPEN_DISPS(gfxCtx); if (!(drawFlags & SKIN_DRAW_FLAG_CUSTOM_TRANSFORMS)) { Skin_ApplyAnimTransformations(skin, gSkinLimbMatrices, actor, setTranslation); } skeleton = Lib_SegmentedToVirtual(skin->skeletonHeader->segment); if (!(drawFlags & SKIN_DRAW_FLAG_CUSTOM_MATRIX)) { Mtx* mtx; gSPMatrix(POLY_OPA_DISP++, &gIdentityMtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); mtx = SkinMatrix_MtxFToNewMtx(gfxCtx, &skin->mtx); if (mtx == NULL) { goto close_disps; } gSPMatrix(POLY_OPA_DISP++, mtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); } for (i = 0; i < skin->skeletonHeader->limbCount; i++) { s32 shouldDraw = true; s32 segmentType; if (overrideLimbDraw != NULL) { shouldDraw = overrideLimbDraw(actor, play, i, skin); } segmentType = ((SkinLimb*)Lib_SegmentedToVirtual(skeleton[i]))->segmentType; // @recomp Push a new matrix by multiplying in the identity matrix. This doesn't change the actual matrix contents, // but allows a unique matrix group ID to be applied to this limb. gSPMatrix(POLY_OPA_DISP++, &gIdentityMtx, G_MTX_PUSH | G_MTX_MUL | G_MTX_MODELVIEW); // Tag the matrix. POLY_OPA_DISP = push_skin_limb_matrix_group(POLY_OPA_DISP, actor, i); if (segmentType == SKIN_LIMB_TYPE_ANIMATED && shouldDraw) { Skin_DrawAnimatedLimb(gfxCtx, skin, i, arg6, drawFlags); } else if (segmentType == SKIN_LIMB_TYPE_NORMAL && shouldDraw) { Skin_DrawLimb(gfxCtx, skin, i, NULL, drawFlags); } // @recomp Pop the matrix and matrix group that were made earlier. gSPPopMatrix(POLY_OPA_DISP++, G_MTX_MODELVIEW); POLY_OPA_DISP = pop_limb_matrix_group(POLY_OPA_DISP, actor); } if (postDraw != NULL) { postDraw(actor, play, skin); } close_disps:; CLOSE_DISPS(gfxCtx); } __attribute__((noinline)) s32 scan_for_matrices(Gfx* start, Gfx* end) { s32 matrix_count = 0; Gfx* cur = start; // Count any G_MTX commands between the start and end commands. while (cur != end) { if ((cur->words.w0 >> 24) == G_MTX) { matrix_count++; } cur++; } return matrix_count; } void tag_actor_displaylists(Actor* actor, PlayState* play, Gfx* opa_start, Gfx* xlu_start) { OPEN_DISPS(play->state.gfxCtx); // Scan the commands written by the actor to see how many matrices were added. s32 opa_matrices = scan_for_matrices(opa_start, POLY_OPA_DISP); s32 xlu_matrices = scan_for_matrices(xlu_start, POLY_XLU_DISP); bool skipped = actor_get_interpolation_skipped(actor); // If the actor wrote at least one matrix in total and at most one matrix to each list, tag that matrix with the actor's id. if ((opa_matrices == 1 || xlu_matrices == 1) && opa_matrices <= 1 && xlu_matrices <= 1) { u32 cur_transform_id = actor_transform_id(actor); if (opa_matrices == 1) { // Fill in the slot that was reserved for a transform id. if (skipped) { gEXMatrixGroupDecomposedSkipPosRot(opa_start, cur_transform_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_ALLOW); } else { gEXMatrixGroupDecomposedNormal(opa_start, cur_transform_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_ALLOW); } // Pop the matrix group. gEXPopMatrixGroup(POLY_OPA_DISP++, G_MTX_MODELVIEW); } if (xlu_matrices == 1) { // Fill in the slot that was reserved for a transform id. if (skipped) { gEXMatrixGroupDecomposedSkipPosRot(xlu_start, cur_transform_id + 1, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_ALLOW); } else { gEXMatrixGroupDecomposedNormal(xlu_start, cur_transform_id + 1, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_ALLOW); } // Pop the matrix groups. gEXPopMatrixGroup(POLY_XLU_DISP++, G_MTX_MODELVIEW); } } CLOSE_DISPS(); } // @recomp Patched to automatically add transform tagging to actor matrices based on what DL commands they write in their draw function void Actor_Draw(PlayState* play, Actor* actor) { Lights* light; OPEN_DISPS(play->state.gfxCtx); light = LightContext_NewLights(&play->lightCtx, play->state.gfxCtx); if ((actor->flags & ACTOR_FLAG_10000000) && (play->roomCtx.curRoom.enablePosLights || (MREG(93) != 0))) { light->enablePosLights = true; } Lights_BindAll(light, play->lightCtx.listHead, (actor->flags & (ACTOR_FLAG_10000000 | ACTOR_FLAG_400000)) ? NULL : &actor->world.pos, play); Lights_Draw(light, play->state.gfxCtx); if (actor->flags & ACTOR_FLAG_IGNORE_QUAKE) { Matrix_SetTranslateRotateYXZ(actor->world.pos.x + play->mainCamera.quakeOffset.x, actor->world.pos.y + ((actor->shape.yOffset * actor->scale.y) + play->mainCamera.quakeOffset.y), actor->world.pos.z + play->mainCamera.quakeOffset.z, &actor->shape.rot); } else { Matrix_SetTranslateRotateYXZ(actor->world.pos.x, actor->world.pos.y + (actor->shape.yOffset * actor->scale.y), actor->world.pos.z, &actor->shape.rot); } Matrix_Scale(actor->scale.x, actor->scale.y, actor->scale.z, MTXMODE_APPLY); Actor_SetObjectDependency(play, actor); gSPSegment(POLY_OPA_DISP++, 0x06, play->objectCtx.slots[actor->objectSlot].segment); gSPSegment(POLY_XLU_DISP++, 0x06, play->objectCtx.slots[actor->objectSlot].segment); if (actor->colorFilterTimer != 0) { s32 colorFlag = COLORFILTER_GET_COLORFLAG(actor->colorFilterParams); Color_RGBA8 actorDefaultHitColor = { 0, 0, 0, 255 }; if (colorFlag == COLORFILTER_COLORFLAG_GRAY) { actorDefaultHitColor.r = actorDefaultHitColor.g = actorDefaultHitColor.b = COLORFILTER_GET_COLORINTENSITY(actor->colorFilterParams) | 7; } else if (colorFlag == COLORFILTER_COLORFLAG_RED) { actorDefaultHitColor.r = COLORFILTER_GET_COLORINTENSITY(actor->colorFilterParams) | 7; } else if (colorFlag == COLORFILTER_COLORFLAG_NONE) { actorDefaultHitColor.b = actorDefaultHitColor.g = actorDefaultHitColor.r = 0; } else { actorDefaultHitColor.b = COLORFILTER_GET_COLORINTENSITY(actor->colorFilterParams) | 7; } if (actor->colorFilterParams & COLORFILTER_BUFFLAG_XLU) { func_800AE778(play, &actorDefaultHitColor, actor->colorFilterTimer, COLORFILTER_GET_DURATION(actor->colorFilterParams)); } else { func_800AE434(play, &actorDefaultHitColor, actor->colorFilterTimer, COLORFILTER_GET_DURATION(actor->colorFilterParams)); } } // @recomp Add two noops into the opa and xlu displaylists to reserve space for a transform tag before the actor is drawn. Gfx* opa_tag_slot = POLY_OPA_DISP; Gfx* xlu_tag_slot = POLY_XLU_DISP; gDPNoOp(POLY_OPA_DISP++); gDPNoOp(POLY_OPA_DISP++); gDPNoOp(POLY_XLU_DISP++); gDPNoOp(POLY_XLU_DISP++); actor->draw(actor, play); tag_actor_displaylists(actor, play, opa_tag_slot, xlu_tag_slot); if (actor->colorFilterTimer != 0) { if (actor->colorFilterParams & COLORFILTER_BUFFLAG_XLU) { func_800AE8EC(play); } else { func_800AE5A0(play); } } if (actor->shape.shadowDraw != NULL) { actor->shape.shadowDraw(actor, light, play); } actor->isDrawn = true; // @recomp Clear the actor's interpolation skipped flag. actor_clear_interpolation_skipped(actor); CLOSE_DISPS(play->state.gfxCtx); }