mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
F10 pad slider persistence (D489), glass collision hull (D490), A/B input harnesses (1964 virtual pad, GE_INPUTSCRIPT, live one-pad launcher), adjacent global struct reads (D491), vibration percent, D441 complete pointer-width census with high-arena tier-1 build, fix batches 1-5 and an ABI warning ratchet, playtest verification of D493-D502. Refs: D489 D490 D491 D492-D502 D441
6885 lines
202 KiB
C
6885 lines
202 KiB
C
#include <ultra64.h>
|
|
#ifdef PORT /* TEMP D51 */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#endif
|
|
#include <memp.h>
|
|
#include "model.h"
|
|
#include "../rmon.h" /*<PR/rmon.h>*/
|
|
#include "bondview.h"
|
|
#include "chr.h"
|
|
#include "chrobjdata.h"
|
|
#include "gbi_extension.h"
|
|
#include "initunk_005520.h"
|
|
#include "math_asinfacosf.h"
|
|
#include "math_floor.h"
|
|
#include "math_ceil.h"
|
|
#include "math_unk_05A9E0.h"
|
|
#include "objecthandler.h"
|
|
#include "quaternion.h"
|
|
#include "random.h"
|
|
#ifdef PORT
|
|
#include "envflag.h" /* cached getenv for hot-path probes */
|
|
#endif
|
|
|
|
|
|
typedef struct ModelGroupMtxBuildArg {
|
|
u16 flags;
|
|
u16 pad;
|
|
ModelRoData_GroupRecord *group;
|
|
ModelNode *parentnode;
|
|
} ModelGroupMtxBuildArg;
|
|
|
|
// forward declarations
|
|
void modelSetAnimFrame2WithChrStuff(struct Model *model, f32 framea, f32 frameb, f32 frame2a, f32 frame2b);
|
|
|
|
|
|
|
|
//newfile per EU
|
|
bool modelmgrCanSlotFitRwdata(Model *modelslot, ModelFileHeader *modeldef)
|
|
{
|
|
return modeldef->numRecords <= 0
|
|
|| (modelslot->datas != NULL && modelslot->rwdatalen >= modeldef->numRecords);
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06C094
|
|
*
|
|
* Allocates 0x20 bytes for a new model without animations.
|
|
* Models that need animations use modelmgrInstantiateModelWithAnim.
|
|
*/
|
|
Model *modelmgrInstantiateModel(ModelFileHeader *header)
|
|
{
|
|
Model *model;
|
|
u32 *rwdata;
|
|
s16 rwdatalen;
|
|
|
|
model = NULL;
|
|
rwdata = NULL;
|
|
rwdatalen = -1;
|
|
|
|
if (g_ModelIsLvResetting)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < (g_MaxModelSlots - 30); i++)
|
|
{
|
|
if (g_ModelSlots[i].unk08 == 0)
|
|
{
|
|
model = (Model *)&g_ModelSlots[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (model == NULL)
|
|
{
|
|
#ifdef PORT
|
|
/* PC port (D53.2): this buffer is used as a struct Model via the
|
|
* same type-pun as g_ModelSlots; on PC that struct is 0xE8 bytes,
|
|
* not the N64 0x20 slot size. */
|
|
model = mempAllocBytesInBank(sizeof(struct Model), MEMPOOL_STAGE);
|
|
#else
|
|
model = mempAllocBytesInBank(0x20, MEMPOOL_STAGE);
|
|
#endif
|
|
}
|
|
|
|
if (header->numRecords > 0)
|
|
{
|
|
rwdata = mempAllocBytesInBank((((header->numRecords * 4) + 0xf) | 0xf) ^ 0xf, MEMPOOL_STAGE);
|
|
rwdatalen = header->numRecords;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < g_MaxModelSlots; i++)
|
|
{
|
|
if (g_ModelSlots[i].unk08 == 0 && modelmgrCanSlotFitRwdata((Model *)&g_ModelSlots[i], header))
|
|
{
|
|
rwdata = g_ModelSlots[i].unk10;
|
|
rwdatalen = g_ModelSlots[i].unk02;
|
|
model = (Model *)&g_ModelSlots[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#ifdef PORT
|
|
/* PC port (D57, docs/dev/findings.md): pointer-grown rwdata records can
|
|
* push a model's word count past the (grown) spare-pool capacity; fall
|
|
* back to a dynamic slot+pool exactly like the LvResetting path above.
|
|
* The slot is not tracked in g_MaxModelSlots, so it is never reused —
|
|
* acceptable: with the D57 capacities this should not trigger. */
|
|
if (model == NULL && header->numRecords > 0)
|
|
{
|
|
model = mempAllocBytesInBank(sizeof(struct ModelSlot), MEMPOOL_STAGE);
|
|
if (model != NULL)
|
|
{
|
|
rwdata = mempAllocBytesInBank((((header->numRecords * 4) + 0xf) | 0xf) ^ 0xf, MEMPOOL_STAGE);
|
|
rwdatalen = header->numRecords;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (model != NULL)
|
|
{
|
|
modelInit(model, header, rwdata);
|
|
((struct ModelSlot *)model)->unk02 = rwdatalen;
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
|
|
void clear_model_obj(Model* model)
|
|
{
|
|
model->obj = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Allocates 0xc0 bytes for a new model to allow enough memory for animations.
|
|
*/
|
|
Model *modelmgrInstantiateModelWithAnim(ModelFileHeader *modelFileHeader)
|
|
{
|
|
Model *newModel;
|
|
void *rwdatas;
|
|
s16 rwdatalen;
|
|
s32 i;
|
|
s16 requiredRwdatalen;
|
|
s32 i2;
|
|
|
|
newModel = NULL;
|
|
rwdatas = NULL;
|
|
rwdatalen = -1;
|
|
|
|
if (g_ModelIsLvResetting)
|
|
{
|
|
for (i = 0; i < (g_MaxAnimModelSlots - 10); i++)
|
|
{
|
|
if (g_AnimModelSlots[i].unk08 == 0)
|
|
{
|
|
newModel = (Model *)&g_AnimModelSlots[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newModel == NULL)
|
|
{
|
|
#ifdef PORT
|
|
/* PC port (D53.2): same pun as g_AnimModelSlots — must hold a full
|
|
* struct Model (0xE8 on PC), not the N64 0xC0 anim-slot size. */
|
|
newModel = mempAllocBytesInBank(sizeof(struct AnimModelSlot), MEMPOOL_STAGE);
|
|
#else
|
|
newModel = mempAllocBytesInBank(0xc0, MEMPOOL_STAGE);
|
|
#endif
|
|
}
|
|
|
|
requiredRwdatalen = modelFileHeader->numRecords;
|
|
|
|
#ifdef DEBUG
|
|
if (modelFileHeader->numRecords > 140) osSyncPrintf("WARNING: increase OISAVESIZE to %d!\n", *(modelFileHeader->numRecords));
|
|
#endif
|
|
|
|
if (requiredRwdatalen > 0)
|
|
{
|
|
i = requiredRwdatalen;
|
|
rwdatas = mempAllocBytesInBank((((i * 4) + 0xf) | 0xf) ^ 0xf, MEMPOOL_STAGE);
|
|
rwdatalen = modelFileHeader->numRecords;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
requiredRwdatalen = modelFileHeader->numRecords;
|
|
|
|
for (i2 = 0; i2 < g_MaxAnimModelSlots; i2++)
|
|
{
|
|
if ((g_AnimModelSlots[i2].unk08 == 0) && ((requiredRwdatalen <= 0) || ((g_AnimModelSlots[i2].unk10 != NULL) &&(g_AnimModelSlots[i2].unk02 >= requiredRwdatalen))))
|
|
{
|
|
newModel = (Model *)&g_AnimModelSlots[i2];
|
|
rwdatas = g_AnimModelSlots[i2].unk10;
|
|
rwdatalen = g_AnimModelSlots[i2].unk02;
|
|
break;
|
|
}
|
|
}
|
|
#ifdef PORT
|
|
/* PC port (D57, docs/dev/findings.md): same fallback as
|
|
* modelmgrInstantiateModel() — a dynamic slot+pool when no spare
|
|
* fits the (pointer-grown) rwdata word count. */
|
|
if (newModel == NULL && requiredRwdatalen > 0)
|
|
{
|
|
newModel = mempAllocBytesInBank(sizeof(struct AnimModelSlot), MEMPOOL_STAGE);
|
|
if (newModel != NULL)
|
|
{
|
|
rwdatas = mempAllocBytesInBank((((requiredRwdatalen * 4) + 0xf) | 0xf) ^ 0xf, MEMPOOL_STAGE);
|
|
rwdatalen = requiredRwdatalen;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (newModel != NULL)
|
|
{
|
|
animInit(newModel, modelFileHeader, rwdatas);
|
|
newModel->rwdatalen = rwdatalen;
|
|
}
|
|
|
|
return newModel;
|
|
}
|
|
|
|
|
|
void modelAttachHead(Model *model, ModelNode *node, ModelFileHeader *head)
|
|
{
|
|
modelAttachPart(model,model->obj,node,head);
|
|
#ifdef DEBUG
|
|
if (model->numRecords > 140 && g_ModelDistanceScale == 0) osSyncPrintf("WARNING: increase OASAVESIZE to %d!\n", *(model + 0x14));
|
|
#endif
|
|
|
|
modelInitRwData(model,head->RootNode);
|
|
}
|
|
|
|
|
|
void clear_aircraft_model_obj(Model *objinstance)
|
|
{
|
|
objinstance->obj = NULL;
|
|
return;
|
|
}
|
|
|
|
|
|
void modelSetDistanceDisabled(s32 param_1) {
|
|
g_ModelDistanceDisabled = param_1;
|
|
}
|
|
|
|
|
|
// PD: modelSetDistanceScale
|
|
void modelSetDistanceScale(f32 param_1) {
|
|
g_ModelDistanceScale = param_1;
|
|
}
|
|
|
|
|
|
void sub_GAME_7F06C418(Vew4s32 *src, Vew4s32 *dst) {
|
|
s32 i, j;
|
|
for (i = 0; i < 4; i++) {
|
|
for (j = 0; j < 4; j++) {
|
|
dst[i].v[j] = src[i].v[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PORT
|
|
/* D59: keep the full 64-bit code address (see model.h). */
|
|
void set_vtxallocator(void *param_1) {
|
|
vtxallocator = (struct Vertex *(*)(s32 numvertices))param_1;
|
|
}
|
|
#else
|
|
void set_vtxallocator(s32 param_1) {
|
|
vtxallocator = param_1;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(LEFTOVERDEBUG)
|
|
// called after a debug print during failed model operation possible "exit()" function in debug
|
|
void return_null(void)
|
|
{
|
|
// dump something 8 bytes long?
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* Address: 7F06C474
|
|
*/
|
|
void modelCalculateScaledRootToOriginDir(Model* model, coord3d* coord)
|
|
{
|
|
Mtxf* mtx;
|
|
f32 dist;
|
|
f32 neg_x;
|
|
f32 neg_y;
|
|
f32 neg_z;
|
|
f32 inv_dist;
|
|
|
|
mtx = getsubmatrix(model);
|
|
neg_x = -mtx->m[3][0];
|
|
neg_y = -mtx->m[3][1];
|
|
neg_z = -mtx->m[3][2];
|
|
|
|
dist = sqrtf((neg_x * neg_x) + (neg_y * neg_y) + (neg_z * neg_z));
|
|
if (dist > 0.0f)
|
|
{
|
|
inv_dist = 1.0f / (model->scale * dist);
|
|
coord->f[0] = neg_x * inv_dist;
|
|
coord->f[1] = neg_y * inv_dist;
|
|
coord->f[2] = neg_z * inv_dist;
|
|
return;
|
|
}
|
|
|
|
coord->f[0] = 0.0f;
|
|
coord->f[1] = 0.0f;
|
|
coord->f[2] = 1.0f / model->scale;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06C550
|
|
*/
|
|
void modelGetScaledRootToOriginDir(Model* model, coord3d* coord)
|
|
{
|
|
modelCalculateScaledRootToOriginDir(model, coord);
|
|
}
|
|
|
|
|
|
// PD: model0001a524
|
|
s32 modelFindNodeMtxIndex(ModelNode *node, s32 arg1)
|
|
{
|
|
s32 index;
|
|
union ModelRoData *rodata1;
|
|
union ModelRoData *rodata2;
|
|
union ModelRoData *rodata3;
|
|
union ModelRoData *rodata4;
|
|
|
|
while (node)
|
|
{
|
|
switch (node->Opcode & 0xff)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
rodata1 = node->Data;
|
|
return rodata1->Header.MatrixIndex;
|
|
|
|
case MODELNODE_OPCODE_GROUP:
|
|
rodata2 = node->Data;
|
|
return rodata2->Group.MatrixIDs[arg1 == 0x200 ? 2 : (arg1 == 0x100 ? 1 : 0)];
|
|
|
|
case MODELNODE_OPCODE_OP03:
|
|
rodata3 = node->Data;
|
|
return rodata3->Group.MatrixIDs[arg1 == 0x200 ? 2 : (arg1 == 0x100 ? 1 : 0)];
|
|
|
|
case MODELNODE_OPCODE_GROUPSIMPLE:
|
|
rodata4 = node->Data;
|
|
return rodata4->GroupSimple.Group1;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
// PD: model0001a5cc
|
|
Mtxf *modelFindNodeMtx(struct Model *model, struct ModelNode *node, s32 arg2) {
|
|
s32 index = modelFindNodeMtxIndex(node, arg2);
|
|
|
|
if (index >= 0) {
|
|
return &model->render_pos[index].pos;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//rejoined per EU
|
|
// PD: model0001a60c
|
|
Mtxf *getsubmatrix(Model *objinst)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (!objinst)
|
|
{
|
|
osSyncPrintf("getsubmatrix: no objinst!\n");
|
|
return_null();
|
|
}
|
|
if (!objinst->obj)
|
|
{
|
|
osSyncPrintf("getsubmatrix: objinst has no object!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
return modelFindNodeMtx(objinst, objinst->obj->RootNode, 0);
|
|
}
|
|
|
|
|
|
// unreferenced
|
|
void sub_GAME_7F06C710(Model* model, coord3d* pos)
|
|
{
|
|
Mtxf* mtx;
|
|
|
|
mtx = getsubmatrix(model);
|
|
if (mtx != NULL)
|
|
{
|
|
pos->f[0] = (f32) mtx->m[3][0];
|
|
pos->f[1] = (f32) mtx->m[3][1];
|
|
pos->f[2] = (f32) mtx->m[3][2];
|
|
return;
|
|
}
|
|
|
|
pos->f[0] = 0.0f;
|
|
pos->f[1] = 0.0f;
|
|
pos->f[2] = 0.0f;
|
|
}
|
|
|
|
|
|
f32 sub_GAME_7F06C768(Model *objinst)
|
|
{
|
|
Mtxf *mtx = getsubmatrix(objinst);
|
|
if (mtx != 0)
|
|
{
|
|
return -mtx->m[3][2];
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 0x7F06C79C.
|
|
*/
|
|
union ModelRwData* modelGetNodeRwData(Model *Objinst, ModelNode *root)
|
|
{
|
|
s32 index = 0;
|
|
#ifdef PORT
|
|
/* D52: word-indexed pool access — see bondtypes.h struct Model.datas. */
|
|
u32 *data = Objinst->datas;
|
|
#else
|
|
union ModelRwData **data = Objinst->datas;
|
|
#endif
|
|
|
|
switch (root->Opcode & 0xff)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
{
|
|
index = root->Data->Header.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
{
|
|
index = root->Data->DisplayListCollisions.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_OP07:
|
|
{
|
|
index = root->Data->Op07.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_LOD:
|
|
{
|
|
index = root->Data->LOD.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
{
|
|
index = root->Data->Switch.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_BSP:
|
|
{
|
|
index = root->Data->BSP.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_OP11:
|
|
{
|
|
index = root->Data->Op11.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_GUNFIRE:
|
|
{
|
|
index = root->Data->Gunfire.RwDataIndex;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_HEAD:
|
|
{
|
|
index = root->Data->HeadPlaceholder.RwDataIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (root->Parent)
|
|
{
|
|
root = root->Parent;
|
|
if ((root->Opcode & 0xFF) == MODELNODE_OPCODE_HEAD)
|
|
{
|
|
ModelRwData_HeadPlaceholderRecord *tmp = modelGetNodeRwData(Objinst, root);
|
|
#ifdef PORT
|
|
data = (u32 *)tmp->RwDatas;
|
|
#else
|
|
data = tmp->RwDatas;
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef PORT
|
|
return (union ModelRwData *)&data[index];
|
|
#else
|
|
return &data[index];
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
void getpartoffset(Model *objinst, ModelNode *part, coord3d *offset) //#MATCH - however OPCODE 3 needs defining
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (!objinst)
|
|
{
|
|
osSyncPrintf("getpartoffset: no objinst!");
|
|
return_null();
|
|
}
|
|
if (!part)
|
|
{
|
|
osSyncPrintf("getpartoffset: no partdesc!");
|
|
return_null();
|
|
}
|
|
#endif
|
|
switch (part->Opcode & 0xFF)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
{
|
|
struct modeldata_root *root = modelGetNodeRwData(objinst, part);
|
|
offset->x = root->pos.x;
|
|
offset->y = root->pos.y;
|
|
offset->z = root->pos.z;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_GROUP:
|
|
{
|
|
ModelRoData_GroupRecord *prt = &part->Data->Group;
|
|
offset->x = prt->Origin.x;
|
|
offset->y = prt->Origin.y;
|
|
offset->z = prt->Origin.z;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_OP03:
|
|
{
|
|
ModelRoData_GroupSimpleRecord *prt = &part->Data->GroupSimple; //UNUSED at this time
|
|
offset->x = prt->Origin.x;
|
|
offset->y = prt->Origin.y;
|
|
offset->z = prt->Origin.z;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_GROUPSIMPLE:
|
|
{
|
|
ModelRoData_GroupSimpleRecord *prt = &part->Data->GroupSimple;
|
|
offset->x = prt->Origin.x;
|
|
offset->y = prt->Origin.y;
|
|
offset->z = prt->Origin.z;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
offset->x = 0.0f;
|
|
offset->y = 0.0f;
|
|
offset->z = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void setpartoffset(Model *model, ModelNode *node, coord3d *pos)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (!model) {
|
|
osSyncPrintf("setpartoffset: no objinst!");
|
|
return_null();
|
|
}
|
|
|
|
if (!node) {
|
|
osSyncPrintf("setpartoffset: no partdesc!");
|
|
return_null();
|
|
}
|
|
else
|
|
{
|
|
// huh?
|
|
}
|
|
#endif
|
|
switch (node->Opcode & 0xff)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
{
|
|
ModelRwData_HeaderRecord *rwdata = modelGetNodeRwData(model, node);
|
|
coord3d diff[1];
|
|
|
|
diff[0].x = pos->x - rwdata->pos.x;
|
|
diff[0].z = pos->z - rwdata->pos.z;
|
|
|
|
rwdata->pos.x = pos->x;
|
|
rwdata->pos.y = pos->y;
|
|
rwdata->pos.z = pos->z;
|
|
|
|
rwdata->unk24.x += diff[0].x; rwdata->unk24.z += diff[0].z;
|
|
rwdata->unk34.x += diff[0].x; rwdata->unk34.z += diff[0].z;
|
|
rwdata->unk40.x += diff[0].x; rwdata->unk40.z += diff[0].z;
|
|
rwdata->unk4c.x += diff[0].x; rwdata->unk4c.z += diff[0].z;
|
|
}
|
|
break;
|
|
case MODELNODE_OPCODE_GROUP:
|
|
{
|
|
ModelRoData_GroupRecord *rodata = &node->Data->Group;
|
|
rodata->Origin.x = pos->x;
|
|
rodata->Origin.y = pos->y;
|
|
rodata->Origin.z = pos->z;
|
|
}
|
|
break;
|
|
case MODELNODE_OPCODE_OP03:
|
|
{
|
|
ModelRoData_GroupRecord *rodata = &node->Data->Group;
|
|
rodata->Origin.x = pos->x;
|
|
rodata->Origin.y = pos->y;
|
|
rodata->Origin.z = pos->z;
|
|
}
|
|
break;
|
|
case MODELNODE_OPCODE_GROUPSIMPLE:
|
|
{
|
|
ModelRoData_GroupSimpleRecord *rodata = &node->Data->GroupSimple;
|
|
rodata->Origin.x = pos->x;
|
|
rodata->Origin.y = pos->y;
|
|
rodata->Origin.z = pos->z;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void getsuboffset(Model *objinst, coord3d *offset) //#MATCH
|
|
{
|
|
#if defined(LEFTOVERDEBUG )
|
|
if (!objinst)
|
|
{
|
|
osSyncPrintf("getsuboffset: no objinst!");
|
|
return_null();
|
|
}
|
|
|
|
if (!objinst->obj)
|
|
{
|
|
osSyncPrintf("getsuboffset: objinst has no object!");
|
|
return_null();
|
|
}
|
|
#endif
|
|
getpartoffset(objinst, objinst->obj->RootNode, offset);
|
|
}
|
|
|
|
|
|
|
|
|
|
void setsuboffset(Model *objinst, coord3d *offset) //#MATCH
|
|
{
|
|
#if defined(LEFTOVERDEBUG )
|
|
if (!objinst)
|
|
{
|
|
osSyncPrintf("setsuboffset: no objinst!");
|
|
return_null();
|
|
}
|
|
if (!objinst->obj)
|
|
{
|
|
osSyncPrintf("setsuboffset: objinst has no object!");
|
|
return_null();
|
|
}
|
|
#endif
|
|
setpartoffset(objinst, objinst->obj->RootNode, offset);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Address 0x7F06CC80.
|
|
*/
|
|
f32 getsubroty(Model *objinst)
|
|
{
|
|
ModelNode *root;
|
|
|
|
#if defined(LEFTOVERDEBUG)
|
|
if(0)
|
|
{
|
|
// removed
|
|
}
|
|
|
|
if (objinst == NULL)
|
|
{
|
|
osSyncPrintf("getsubroty: no objinst!");
|
|
return_null();
|
|
}
|
|
|
|
if(0)
|
|
{
|
|
// removed
|
|
}
|
|
|
|
if (objinst->obj == NULL)
|
|
{
|
|
osSyncPrintf("getsubroty: objinst has no object!");
|
|
return_null();
|
|
}
|
|
|
|
if(0)
|
|
{
|
|
// removed
|
|
}
|
|
|
|
if (objinst->obj->RootNode == NULL)
|
|
{
|
|
osSyncPrintf("getsubroty: objinst has no root part!");
|
|
return_null();
|
|
}
|
|
|
|
if(0)
|
|
{
|
|
// removed
|
|
}
|
|
#endif
|
|
|
|
root = objinst->obj->RootNode;
|
|
if ((root->Opcode & 0xFF) == MODELNODE_OPCODE_HEADER)
|
|
{
|
|
return ((struct modeldata_root *)modelGetNodeRwData(objinst, root))->subroty;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
void setsubroty(Model *model, f32 angle)
|
|
{
|
|
ModelNode* node;
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (!model)
|
|
{
|
|
osSyncPrintf("setsubroty: no objinst!");
|
|
return_null();
|
|
}
|
|
|
|
if (!model->obj) //< needs to be v1 not a1
|
|
{
|
|
osSyncPrintf("setsubroty: objinst has no object!");
|
|
return_null();
|
|
}
|
|
|
|
if (!model->obj->RootNode)
|
|
{
|
|
osSyncPrintf("setsubroty: objinst has no root part!");
|
|
return_null();
|
|
}
|
|
#endif
|
|
node = model->obj->RootNode;
|
|
if ((node->Opcode & 0xff) == MODELNODE_OPCODE_HEADER)
|
|
{
|
|
ModelRwData_HeaderRecord *rwdata = modelGetNodeRwData(model, node);
|
|
f32 diff = angle - rwdata->unk14;
|
|
|
|
if (diff < 0) { diff += M_TAU_F; }
|
|
|
|
rwdata->unk30 += diff;
|
|
|
|
if (rwdata->unk30 >= M_TAU_F) { rwdata->unk30 -= M_TAU_F; }
|
|
|
|
rwdata->unk20 += diff;
|
|
|
|
if (rwdata->unk20 >= M_TAU_F) { rwdata->unk20 -= M_TAU_F; }
|
|
|
|
rwdata->unk14 = angle;
|
|
}
|
|
}
|
|
|
|
|
|
void modelSetScale(Model *objinst, f32 scale)
|
|
{
|
|
objinst->scale = scale;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06CE84
|
|
*
|
|
* Scales only the translation component of the root node of an animation.
|
|
* For example, the animation for the plane flight in Runway's outro doesn't
|
|
* actually move the plane very far. This function is used to scale up the translation
|
|
* ~10x to allow it to fly near the camera.
|
|
*/
|
|
void modelSetAnimTranslationScale(Model* model, f32 scale)
|
|
{
|
|
model->anim_translation_scale = scale;
|
|
}
|
|
|
|
|
|
f32 getjointsize(Model *model, ModelNode *node)
|
|
{
|
|
Model *temp_a2;
|
|
ModelNode *temp_a1;
|
|
s32 temp_t7;
|
|
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (!model)
|
|
{
|
|
osSyncPrintf("getjointsize: no objinst!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
|
|
if (node)
|
|
{
|
|
do
|
|
{
|
|
switch (node->Opcode & 0xFF)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
{
|
|
ModelRoData_HeaderRecord *rodata = &node->Data->Header;
|
|
return rodata->GroupsAsF32 * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_GROUP:
|
|
{
|
|
ModelRoData_GroupRecord *rodata = &node->Data->Group;
|
|
return rodata->BoundingVolumeRadius * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_OP03:
|
|
{
|
|
ModelRoData_GroupRecord *rodata = &node->Data->Group;
|
|
return rodata->BoundingVolumeRadius * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_GROUPSIMPLE:
|
|
{
|
|
ModelRoData_GroupSimpleRecord *rodata = &node->Data->GroupSimple;
|
|
return rodata->BoundingVolumeRadius * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_OP11:
|
|
{
|
|
ModelRoData_Op11Record *rodata = &node->Data->Op11;
|
|
return rodata->BoundingVolumeRadius * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_GUNFIRE:
|
|
{
|
|
ModelRoData_GunfireRecord *rodata = &node->Data->Gunfire;
|
|
return rodata->Scale * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_SHADOW:
|
|
{
|
|
ModelRoData_ShadowRecord *rodata = &node->Data->Shadow;
|
|
return rodata->Scale * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_OP14:
|
|
{
|
|
ModelRoData_Op14Record *rodata = &node->Data->Op14;
|
|
return rodata->Scale * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_INTERLINK:
|
|
{
|
|
ModelRoData_InterlinkageRecord *rodata = &node->Data->Interlinkage;
|
|
return rodata->Scale * model->scale;
|
|
}
|
|
case MODELNODE_OPCODE_OP16:
|
|
{
|
|
ModelNode_Op16Record *rodata = &node->Data->Op16;
|
|
return rodata->Scale * model->scale;
|
|
}
|
|
default:
|
|
node = node->Parent;
|
|
}
|
|
} while (node);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 0x7F06D00C.
|
|
* PD: model0001af80
|
|
*/
|
|
f32 getinstsize(Model *arg0)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (arg0 == NULL)
|
|
{
|
|
osSyncPrintf("getinstsize: no objinst!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (arg0->obj == NULL)
|
|
{
|
|
osSyncPrintf("getinstsize: no objdesc!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
|
|
return arg0->obj->BoundingVolumeRadius * arg0->scale;
|
|
}
|
|
|
|
|
|
|
|
// PD: model0001af98
|
|
void interpolate3dVectors(vec3d *v, vec3d *w, float frac)
|
|
{
|
|
v->x += (w->x - v->x) * frac;
|
|
v->y += (w->y - v->y) * frac;
|
|
v->z += (w->z - v->z) * frac;
|
|
return;
|
|
}
|
|
|
|
|
|
// PD: model0001afe8
|
|
f32 sub_GAME_7F06D0CC(f32 arg0, f32 angle, f32 mult)
|
|
{
|
|
f32 value = angle - arg0;
|
|
|
|
if (angle < arg0)
|
|
{
|
|
value += M_TAU_F;
|
|
}
|
|
|
|
if (value < M_PI_F)
|
|
{
|
|
arg0 += value * mult;
|
|
|
|
if (arg0 >= M_TAU_F)
|
|
{
|
|
arg0 -= M_TAU_F;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arg0 -= (M_TAU_F - value) * mult;
|
|
|
|
if (arg0 < 0)
|
|
{
|
|
arg0 += M_TAU_F;
|
|
}
|
|
}
|
|
|
|
return arg0;
|
|
}
|
|
|
|
|
|
// PD: model0001b07c
|
|
void sub_GAME_7F06D160(coord3d *arg0, coord3d *arg1, f32 mult)
|
|
{
|
|
arg0->x = sub_GAME_7F06D0CC(arg0->x, arg1->x, mult);
|
|
arg0->y = sub_GAME_7F06D0CC(arg0->y, arg1->y, mult);
|
|
arg0->z = sub_GAME_7F06D0CC(arg0->z, arg1->z, mult);
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06D1CC
|
|
*/
|
|
u16 modelAnimReadRootMotionValue(ModelAnimation *anim, s32 fieldIndex, s32 extraBitOffset)
|
|
{
|
|
u32 result;
|
|
u32 new_var2;
|
|
u32 oldResult;
|
|
struct ModelAnimBitField *desc;
|
|
u8 *byteptr;
|
|
u32 totalBitOffset;
|
|
u32 byteIndex;
|
|
u32 mask;
|
|
u8 bitsRemaining;
|
|
u8 bitsThisRead;
|
|
|
|
result = 0;
|
|
desc = (ModelAnimBitField *)anim->bitDescriptors + fieldIndex; // D32: u32 -> ptr
|
|
bitsRemaining = desc->bitCount;
|
|
|
|
if (bitsRemaining > 0)
|
|
{
|
|
totalBitOffset = extraBitOffset + desc->bitOffset;
|
|
byteIndex = totalBitOffset >> 3;
|
|
totalBitOffset &= 7;
|
|
byteptr = (u8 *)anim->bitStream + byteIndex; // D32: u32 -> ptr
|
|
bitsThisRead = 8 - totalBitOffset;
|
|
|
|
if (bitsRemaining >= bitsThisRead)
|
|
{
|
|
do
|
|
{
|
|
mask = (1 << bitsThisRead) - 1;
|
|
bitsRemaining -= bitsThisRead;
|
|
result |= ((*byteptr) & mask) << bitsRemaining;
|
|
result &= 0xffff;
|
|
byteptr++;
|
|
bitsThisRead = 8;
|
|
}
|
|
while (bitsRemaining >= 8);
|
|
}
|
|
|
|
if (bitsRemaining > 0)
|
|
{
|
|
result |= ((*byteptr) >> (bitsThisRead - bitsRemaining)) & ((1 << bitsRemaining) - 1);
|
|
result &= 0xffff;
|
|
}
|
|
|
|
bitsRemaining = desc->bitCount;
|
|
|
|
if (bitsRemaining < 16)
|
|
{
|
|
oldResult = result;
|
|
mask = 1 << (bitsRemaining - 1);
|
|
|
|
if (result & mask)
|
|
{
|
|
result = ((new_var2 = oldResult) | (((1 << (16 - bitsRemaining)) - 1) << bitsRemaining)) & 0xffff;
|
|
}
|
|
}
|
|
}
|
|
|
|
result = desc->valueOffset + ((0, result));
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06D2E4
|
|
*/
|
|
u16 sub_GAME_7F06D2E4(s32 jointnum, s32 flip, ModelSkeleton *skeleton, ModelAnimation *anim, s32 frame, coord16 *out)
|
|
{
|
|
u32 scaled;
|
|
s32 base;
|
|
u32 angle_raw;
|
|
u16 angle_ret;
|
|
|
|
scaled = ((u32) anim->unk0C) * ((u32) frame);
|
|
|
|
if (flip)
|
|
{
|
|
base = skeleton->Joints[jointnum].mtxB;
|
|
}
|
|
else
|
|
{
|
|
base = skeleton->Joints[jointnum].mtxA;
|
|
}
|
|
|
|
out->x = modelAnimReadRootMotionValue(anim, base, scaled);
|
|
out->y = modelAnimReadRootMotionValue(anim, base + 1, scaled);
|
|
out->z = modelAnimReadRootMotionValue(anim, base + 2, scaled);
|
|
angle_raw = modelAnimReadRootMotionValue(anim, base + 3, scaled);
|
|
angle_ret = angle_raw;
|
|
|
|
if (flip)
|
|
{
|
|
out->x = -out->x;
|
|
|
|
if (angle_raw != 0)
|
|
{
|
|
angle_ret = 0x10000 - (angle_raw & 0xFFFFFFFFu);
|
|
}
|
|
}
|
|
|
|
return angle_ret;
|
|
}
|
|
|
|
|
|
f32 sub_GAME_7F06D3F4(s32 jointnum, s32 flip, ModelSkeleton *skeleton, ModelAnimation *anim, s32 frame, coord3d *pos)
|
|
{
|
|
s16 tmp[3];
|
|
u16 angle;
|
|
|
|
angle = sub_GAME_7F06D2E4(jointnum, flip, skeleton, anim, frame, tmp);
|
|
|
|
pos->x = (f32)tmp[0];
|
|
pos->y = (f32)tmp[1];
|
|
pos->z = (f32)tmp[2];
|
|
|
|
#ifdef PORT
|
|
/* GE_D193B=1 (D193): raw root-motion decode trace. Rate-limited. Logs the
|
|
* joint index, the mtx base channel index, the decoded s16 translation
|
|
* triple straight out of the anim bitstream, and the derived angle. This
|
|
* is the value that scales every scripted/AI character's travel distance
|
|
* (model.c:3060+ multiplies it by model->scale*anim_translation_scale and
|
|
* accumulates it into the chr root position). Compare magnitudes against a
|
|
* clean N64 decomp build of the same anim/frame to catch a bitstream /
|
|
* ModelSkeleton.Joints-stride / descriptor decode divergence. */
|
|
{
|
|
static int d193b = -1;
|
|
static unsigned d193b_n = 0;
|
|
if (d193b < 0) d193b = getenv("GE_D193B") != NULL;
|
|
if (d193b && (d193b_n++ % 97) == 0) {
|
|
s32 base = flip ? skeleton->Joints[jointnum].mtxB
|
|
: skeleton->Joints[jointnum].mtxA;
|
|
fprintf(stderr,
|
|
"[D193B] joint=%d flip=%d base=%d frame=%d tmp=(%d,%d,%d) "
|
|
"angle=%u anim=%p bd=%08x bs=%08x njoints=%d\n",
|
|
(int)jointnum, (int)flip, (int)base, (int)frame,
|
|
(int)tmp[0], (int)tmp[1], (int)tmp[2], (unsigned)angle,
|
|
(void *)anim, (unsigned)anim->bitDescriptors,
|
|
(unsigned)anim->bitStream, (int)skeleton->numjoints);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return ((f32)angle * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06D490
|
|
*/
|
|
void sub_GAME_7F06D490(Model *model, ModelNode *modelNode)
|
|
{
|
|
union ModelRwData *rw;
|
|
coord3d sp38;
|
|
coord3d sp2c;
|
|
f32 y;
|
|
|
|
rw = modelGetNodeRwData(model, modelNode);
|
|
|
|
if (rw->Header.unk00 != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sp38.x = rw->Header.unk34.x;
|
|
sp38.y = rw->Header.unk34.y;
|
|
sp38.z = rw->Header.unk34.z;
|
|
rw->Header.unk14 = rw->Header.unk30;
|
|
|
|
if (model->unk2c != 0.0f)
|
|
{
|
|
if (rw->Header.unk01 != 0)
|
|
{
|
|
interpolate3dVectors(&sp38, &rw->Header.unk24, model->unk2c);
|
|
|
|
// Weird do while loop but needed for matching.
|
|
do
|
|
{
|
|
rw->Header.unk14 = sub_GAME_7F06D0CC(rw->Header.unk30, rw->Header.unk20, model->unk2c);
|
|
}
|
|
while (model->unka0 * 0);
|
|
}
|
|
}
|
|
|
|
if ((model->anim2 != 0) || (model->unk84 != 0.0f))
|
|
{
|
|
if (rw->Header.unk02 != 0)
|
|
{
|
|
y = rw->Header.unk4c.y;
|
|
|
|
if (model->unk5c != 0.0f)
|
|
{
|
|
y += (rw->Header.unk40.y - y) * model->unk5c;
|
|
}
|
|
|
|
sp38.y += (y - sp38.y) * model->unk84;
|
|
}
|
|
}
|
|
|
|
sp2c.x = sp38.x;
|
|
sp2c.y = sp38.y;
|
|
sp2c.z = sp38.z;
|
|
|
|
#ifdef PORT
|
|
/* D92: `unka0` is a 32-bit Model field that on N64 holds a function
|
|
* pointer -- always sub_GAME_7F01FC10 (the only value chr.c ever hands
|
|
* the setter). Widening the field would shift the rest of Model, so on
|
|
* PC the setter stores a nonzero flag and the single indirect call is
|
|
* made directly here. */
|
|
if (model->unka0 && !sub_GAME_7F01FC10(model, &rw->Header.pos, &sp2c, &rw->Header.ground))
|
|
{
|
|
return;
|
|
}
|
|
#else
|
|
if (model->unka0 && !((s32 (*)(Model *, coord3d *, coord3d *, f32 *)) model->unka0)(model, &rw->Header.pos, &sp2c, &rw->Header.ground))
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
sp38.x = sp2c.x - sp38.x;
|
|
sp38.z = sp2c.z - sp38.z;
|
|
|
|
rw->Header.pos.x = sp2c.x;
|
|
rw->Header.pos.y = ((f32) sp2c.y) + rw->Header.ground;
|
|
rw->Header.pos.z = sp2c.z;
|
|
|
|
rw->Header.unk34.x += sp38.x;
|
|
rw->Header.unk34.z += sp38.z;
|
|
|
|
if (rw->Header.unk01 != 0)
|
|
{
|
|
rw->Header.unk24.x += sp38.x;
|
|
rw->Header.unk24.z += sp38.z;
|
|
}
|
|
|
|
if (rw->Header.unk02 != 0)
|
|
{
|
|
rw->Header.unk4c.x += sp38.x;
|
|
rw->Header.unk4c.z += sp38.z;
|
|
rw->Header.unk40.x += sp38.x;
|
|
rw->Header.unk40.z += sp38.z;
|
|
}
|
|
}
|
|
|
|
|
|
void subcalcpos(Model *arg0)
|
|
{
|
|
struct ModelNode *root;
|
|
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (arg0 == NULL)
|
|
{
|
|
osSyncPrintf("subcalcpos: no objanim!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (arg0->obj == 0)
|
|
{
|
|
osSyncPrintf("subcalcpos: no objdesc!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
|
|
root = arg0->obj->RootNode;
|
|
if ((root != NULL) && ((root->Opcode & 0xFF) == 1))
|
|
{
|
|
sub_GAME_7F06D490(arg0, root);
|
|
}
|
|
}
|
|
|
|
void process_01_group_heading(ModelRenderData* renderdata, Model* model, ModelNode* node)
|
|
{
|
|
union ModelRoData* rodata;
|
|
union ModelRwData* rwdata;
|
|
f32 scale;
|
|
f32* pos;
|
|
f32 unk14;
|
|
Mtxf* var_a3;
|
|
s32 modeltype;
|
|
RenderPosView* renderpos;
|
|
Mtxf sp20;
|
|
|
|
rodata = node->Data;
|
|
rwdata = modelGetNodeRwData(model, node);
|
|
|
|
scale = model->scale;
|
|
pos = &rwdata->Header.pos.x;
|
|
unk14 = rwdata->Header.unk14;
|
|
modeltype = rodata->Header.MatrixIndex;
|
|
renderpos = &model->render_pos[modeltype];
|
|
|
|
if (node->Parent != NULL)
|
|
{
|
|
var_a3 = modelFindNodeMtx(model, node->Parent, 0);
|
|
}
|
|
else
|
|
{
|
|
var_a3 = renderdata->basemtx;
|
|
}
|
|
|
|
if (rwdata->Header.unk18 != 0.0f)
|
|
{
|
|
unk14 = sub_GAME_7F06D0CC(unk14, rwdata->Header.unk1c, rwdata->Header.unk18);
|
|
}
|
|
|
|
if (var_a3 != NULL)
|
|
{
|
|
matrix_4x4_set_position_and_rotation_around_y(pos, unk14, &sp20);
|
|
|
|
if (scale != 1.0f)
|
|
{
|
|
matrix_scalar_multiply_2(scale, sp20.m[0]);
|
|
}
|
|
|
|
matrix_4x4_multiply_homogeneous(var_a3, &sp20, &renderpos->pos);
|
|
return;
|
|
}
|
|
|
|
matrix_4x4_set_position_and_rotation_around_y(pos, unk14, &renderpos->pos);
|
|
|
|
if (scale != 1.0f)
|
|
{
|
|
matrix_scalar_multiply_2(scale, renderpos->pos.m[0]);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06D8B0
|
|
*/
|
|
void modelBuildGroupMatrices(Mtxf **parentMtx, Model *model, ModelGroupMtxBuildArg *mgm, coord3d *rot)
|
|
{
|
|
u32 flags;
|
|
ModelRoData_GroupRecord *group;
|
|
Mtxf *parent;
|
|
Mtxf *matrix0_mtx;
|
|
Mtxf tmp;
|
|
s32 matrix0;
|
|
s32 matrix1;
|
|
s32 matrix2;
|
|
RenderPosView *render_pos;
|
|
Mtxf *parentNodeMtx;
|
|
f32 *origin;
|
|
s32 has_matrix2;
|
|
quatf q;
|
|
quatf q2;
|
|
Mtxf *dst;
|
|
f32 angle;
|
|
Mtxf **parentMtxPtr;
|
|
|
|
flags = mgm->flags;
|
|
group = mgm->group;
|
|
matrix0 = group->MatrixID0;
|
|
matrix1 = group->MatrixID1;
|
|
parentMtxPtr = parentMtx;
|
|
origin = group->Origin.f;
|
|
matrix2 = group->MatrixID2;
|
|
render_pos = model->render_pos;
|
|
|
|
if (((Mtxf *) mgm->parentnode) != NULL)
|
|
{
|
|
parentNodeMtx = modelFindNodeMtx(model, (ModelNode *) ((Mtxf *) mgm->parentnode), 0);
|
|
parent = parentNodeMtx;
|
|
}
|
|
else
|
|
{
|
|
parent = parentMtxPtr[0];
|
|
}
|
|
|
|
has_matrix2 = flags & MODELGROUP_MTX_HAS_MATRIX2;
|
|
matrix0_mtx = (Mtxf *) mgm->parentnode;
|
|
|
|
if (parent != NULL)
|
|
{
|
|
matrix_4x4_set_position_and_rotation_around_xyz(&group->Origin, rot, &tmp);
|
|
|
|
matrix0_mtx = &render_pos[matrix0].pos;
|
|
matrix_4x4_multiply_homogeneous(parent, &tmp, matrix0_mtx);
|
|
|
|
if (g_ModelJointPositionedFunc != NULL)
|
|
{
|
|
g_ModelJointPositionedFunc(matrix0, matrix0_mtx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
matrix_4x4_set_position_and_rotation_around_xyz(&group->Origin, rot, &render_pos[matrix0].pos);
|
|
}
|
|
|
|
if (flags & MODELGROUP_MTX_HAS_MATRIX1)
|
|
{
|
|
quaternion_set_rotation_around_xyzf(rot->f, q);
|
|
quaternion_7F05BC68(q, 0.5f, q2);
|
|
|
|
if (parent != NULL)
|
|
{
|
|
quaternion_to_transform_matrix(origin, q2, tmp.m);
|
|
matrix_4x4_multiply_homogeneous(parent, &tmp, &render_pos[matrix1].pos);
|
|
}
|
|
else
|
|
{
|
|
quaternion_to_transform_matrix(origin, q2, (render_pos + matrix1)->pos.m);
|
|
}
|
|
}
|
|
|
|
if (has_matrix2)
|
|
{
|
|
if (parent != NULL)
|
|
{
|
|
dst = &tmp;
|
|
}
|
|
else
|
|
{
|
|
dst = &render_pos[matrix2].pos;
|
|
}
|
|
|
|
angle = rot->y;
|
|
|
|
if (angle < M_PI_F)
|
|
{
|
|
angle = angle * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
angle = M_TAU_F - ((M_TAU_F - angle) * 0.5f);
|
|
}
|
|
|
|
matrix_4x4_set_rotation_around_y(angle, dst);
|
|
|
|
if (angle >= M_PI_F)
|
|
{
|
|
angle = M_TAU_F - angle;
|
|
}
|
|
if (angle < 0.890118f)
|
|
{
|
|
angle = modelGetBendStretchScale(angle);
|
|
}
|
|
else
|
|
{
|
|
angle = 1.5f;
|
|
}
|
|
|
|
matrix_column_3_scalar_multiply_2(angle, (f32 *) dst);
|
|
matrix_4x4_set_position(&group->Origin, dst);
|
|
|
|
if (parent != NULL)
|
|
{
|
|
matrix_4x4_multiply_homogeneous(parent, dst, &render_pos[matrix2].pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void sub_GAME_7F06DB5C(ModelRenderData *arg0, Model *arg1, ModelNode *arg2, quatf arg3)
|
|
{
|
|
s32 spA4;
|
|
ModelRoData_GroupRecord *spA0;
|
|
Mtxf *sp9C;
|
|
s32 _gap98;
|
|
Mtxf sp58;
|
|
s32 sp54;
|
|
s32 sp50;
|
|
s32 sp4C;
|
|
RenderPosView *sp48;
|
|
s32 sp44;
|
|
s32 *new_var;
|
|
s32 sp40;
|
|
quatf sp2C;
|
|
Mtxf *sp28;
|
|
f32 sp24;
|
|
s32 _gap20;
|
|
s32 sp1C;
|
|
|
|
spA4 = arg2->Opcode;
|
|
spA0 = (ModelRoData_GroupRecord *)arg2->Data;
|
|
sp54 = spA0->MatrixID0;
|
|
sp50 = spA0->MatrixID1;
|
|
sp4C = spA0->MatrixID2;
|
|
new_var = &sp1C;
|
|
sp48 = arg1->render_pos;
|
|
sp1C = (s32)arg2->Parent;
|
|
|
|
#ifdef PORT
|
|
/* D101: this function stashes `arg2->Parent` (a ModelNode*) and
|
|
* `&sp48[sp54]` (a RenderPosView*) through the s32 `sp1C` scratch, exactly
|
|
* the 32-bit-pointer idiom the sibling modelBuildGroupMatrices() was
|
|
* already cleaned up for. On x86-64 the (s32) cast drops the 0x140000000
|
|
* module base of a static ModelNode, so modelFindNodeMtx(...,
|
|
* (ModelNode*)sp1C, 0) dereferences 0x4012xxxx and faults in
|
|
* modelFindNodeMtxIndex. Do the pointer work at full width (mirrors
|
|
* modelBuildGroupMatrices' parentnode handling and its 2-arg
|
|
* g_ModelJointPositionedFunc call). */
|
|
{
|
|
ModelNode *parentNode = arg2->Parent;
|
|
Mtxf *m0 = (Mtxf *)&sp48[sp54];
|
|
|
|
sp9C = (parentNode != NULL)
|
|
? modelFindNodeMtx(arg1, parentNode, 0)
|
|
: arg0->basemtx;
|
|
|
|
if (sp9C != 0) {
|
|
quaternion_to_transform_matrix(&spA0->Origin, arg3, &sp58);
|
|
matrix_4x4_multiply_homogeneous(sp9C, &sp58, m0);
|
|
if (g_ModelJointPositionedFunc != NULL) {
|
|
g_ModelJointPositionedFunc(sp54, m0);
|
|
}
|
|
} else {
|
|
quaternion_to_transform_matrix(&spA0->Origin, arg3, m0);
|
|
}
|
|
}
|
|
#else
|
|
if (*new_var != 0) {
|
|
sp9C = arg0->basemtx;
|
|
sp9C = modelFindNodeMtx(arg1, (ModelNode *)sp1C, 0);
|
|
} else {
|
|
sp9C = arg0->basemtx;
|
|
}
|
|
|
|
if (sp9C != 0) {
|
|
quaternion_to_transform_matrix(&spA0->Origin, arg3, &sp58);
|
|
sp1C = (s32)&sp48[sp54];
|
|
matrix_4x4_multiply_homogeneous(sp9C, &sp58, (Mtxf *)sp1C);
|
|
if (g_ModelJointPositionedFunc != NULL) {
|
|
((void (*)(s32, s32, s32)) g_ModelJointPositionedFunc)(sp54, sp1C, sp1C);
|
|
}
|
|
} else {
|
|
quaternion_to_transform_matrix(&spA0->Origin, arg3, (Mtxf *)&sp48[sp54]);
|
|
}
|
|
#endif
|
|
|
|
if (spA4 & 0x100) {
|
|
quaternion_7F05BC68(arg3, 0.5f, sp2C);
|
|
if (sp9C != 0) {
|
|
quaternion_to_transform_matrix(&spA0->Origin, sp2C, &sp58);
|
|
matrix_4x4_multiply_homogeneous(sp9C, &sp58, (Mtxf *)&sp48[sp50]);
|
|
} else {
|
|
quaternion_to_transform_matrix(&spA0->Origin, sp2C, (Mtxf *)&sp48[sp50]);
|
|
}
|
|
}
|
|
|
|
if (spA4 & 0x200) {
|
|
if (sp9C != 0) {
|
|
sp28 = &sp58;
|
|
} else {
|
|
sp28 = (Mtxf *)&sp48[sp4C];
|
|
}
|
|
sp24 = 2.0f * acosf(*arg3);
|
|
if (sp24 < 3.1415927f) {
|
|
sp24 = sp24 * 0.5f;
|
|
} else {
|
|
sp24 = 6.2831855f - ((6.2831855f - sp24) * 0.5f);
|
|
}
|
|
matrix_4x4_set_rotation_around_y(sp24, sp28);
|
|
if (sp24 >= 3.1415927f) {
|
|
sp24 = 6.2831855f - sp24;
|
|
}
|
|
if (sp24 < 0.890118f) {
|
|
sp24 = modelGetBendStretchScale(sp24);
|
|
} else {
|
|
sp24 = 1.5f;
|
|
}
|
|
matrix_column_3_scalar_multiply_2(sp24, (f32 *)sp28);
|
|
matrix_4x4_set_position(&spA0->Origin, sp28);
|
|
if (sp9C != 0) {
|
|
matrix_4x4_multiply_homogeneous(sp9C, sp28, (Mtxf *)&sp48[sp4C]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06DE04
|
|
*/
|
|
u32 modelAnimReadBitsAsU16Angle(u8 *bitstream, u8 width, u32 bitOffset)
|
|
{
|
|
u32 value = 0;
|
|
u32 mask;
|
|
u8 numbitsthisbyte;
|
|
u8 remainingbits;
|
|
|
|
remainingbits = width;
|
|
value *= bitOffset / 8;
|
|
|
|
if(1);
|
|
|
|
remainingbits = width;
|
|
bitstream += bitOffset / 8;
|
|
bitOffset %= 8;
|
|
numbitsthisbyte = 8 - bitOffset;
|
|
|
|
while (remainingbits >= numbitsthisbyte)
|
|
{
|
|
remainingbits -= numbitsthisbyte;
|
|
mask = (1 << numbitsthisbyte) - 1;
|
|
value |= ((u16)((*bitstream) & mask)) << remainingbits;
|
|
value &= 0xffff;
|
|
bitstream++;
|
|
numbitsthisbyte = 8;
|
|
}
|
|
|
|
if (remainingbits > 0)
|
|
{
|
|
mask = (1 << remainingbits) - 1;
|
|
value |= ((*bitstream) >> (numbitsthisbyte - remainingbits)) & mask;
|
|
value &= 0xffff;
|
|
}
|
|
|
|
value <<= 16 - width;
|
|
|
|
return value & 0xffff;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06DEC0
|
|
*/
|
|
void sub_GAME_7F06DEC0(s32 jointnum, s32 flip, ModelSkeleton *skeleton, ModelAnimation *anim, u8 *bitstream, coord3d *rot)
|
|
{
|
|
u32 bitoffset;
|
|
u8 width;
|
|
u16 rotation[3];
|
|
|
|
width = anim->unk06;
|
|
|
|
// Mirrored joint rotation?
|
|
if (flip)
|
|
{
|
|
bitoffset = skeleton->Joints[jointnum].mtxB * width;
|
|
}
|
|
else
|
|
{
|
|
bitoffset = skeleton->Joints[jointnum].mtxA * width;
|
|
}
|
|
|
|
width = anim->unk06;
|
|
|
|
rotation[0] = modelAnimReadBitsAsU16Angle(bitstream, width, bitoffset);
|
|
bitoffset += (unsigned long) width;
|
|
|
|
rotation[1] = modelAnimReadBitsAsU16Angle(bitstream, width, bitoffset);
|
|
bitoffset += width;
|
|
|
|
rotation[2] = modelAnimReadBitsAsU16Angle(bitstream, width, bitoffset);
|
|
|
|
rot->x = (rotation[0] * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
|
|
if (flip)
|
|
{
|
|
if (rotation[1] != 0)
|
|
{
|
|
rot->y = ((0x10000 - rotation[1]) * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
}
|
|
else
|
|
{
|
|
rot->y = 0.0f;
|
|
}
|
|
|
|
if (rotation[2] != 0)
|
|
{
|
|
rot->z = ((0x10000 - rotation[2]) * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
}
|
|
else
|
|
{
|
|
rot->z = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rot->y = (rotation[1] * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
rot->z = (rotation[2] * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
}
|
|
}
|
|
|
|
|
|
void process_02_position(ModelRenderData *arg0, Model *model, ModelNode *node)
|
|
{
|
|
union
|
|
{
|
|
s32 v;
|
|
long long int force_structure_alignment;
|
|
} jointnum;
|
|
|
|
ModelSkeleton *skeleton;
|
|
coord3d rot1;
|
|
coord3d rot2;
|
|
coord3d rot3;
|
|
quatf q1;
|
|
quatf q2;
|
|
quatf result;
|
|
coord3d rot4;
|
|
ModelRoData_GroupRecord *group;
|
|
|
|
group = &node->Data->Group;
|
|
jointnum.v = group->JointID;
|
|
skeleton = model->obj->Skeleton;
|
|
|
|
rot1 = D_80036094;
|
|
|
|
sub_GAME_7F06DEC0(jointnum.v, model->gunhand, skeleton, model->anim, model->unk34, &rot1);
|
|
|
|
if (model->unk2c != 0.0f)
|
|
{
|
|
rot2 = D_800360A0;
|
|
sub_GAME_7F06DEC0(jointnum.v, model->gunhand, skeleton, model->anim, model->unk38, &rot2);
|
|
sub_GAME_7F06D160(&rot1, &rot2, model->unk2c);
|
|
}
|
|
|
|
if (model->unk84 != 0.0f)
|
|
{
|
|
rot3 = D_800360AC;
|
|
sub_GAME_7F06DEC0(jointnum.v, model->unk25, skeleton, model->anim2, model->unk64, &rot3);
|
|
|
|
if (model->unk5c != 0.0f)
|
|
{
|
|
rot4 = D_800360B8;
|
|
sub_GAME_7F06DEC0(jointnum.v, model->unk25, skeleton, model->anim2, model->unk68, &rot4);
|
|
sub_GAME_7F06D160(&rot3, &rot4, model->unk5c);
|
|
}
|
|
|
|
quaternion_set_rotation_around_xyzf(&rot1, q1);
|
|
quaternion_set_rotation_around_xyzf(&rot3, q2);
|
|
quaternion_ensure_shortest_path(q1, q2);
|
|
quaternion_slerp(q1, q2, model->unk84, result);
|
|
sub_GAME_7F06DB5C(arg0, model, node, result);
|
|
}
|
|
else
|
|
{
|
|
modelBuildGroupMatrices(arg0, model, node, &rot1);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F06E2B8
|
|
*/
|
|
void sub_GAME_7F06E2B8(ModelRenderData *renderData, Model *model, ModelNode *node, f32 angle)
|
|
{
|
|
s32 opcode;
|
|
union ModelRoData *data;
|
|
Mtxf *mtx;
|
|
RenderPosView *render_pos;
|
|
Mtxf localMtx;
|
|
s32 m0;
|
|
s32 m1;
|
|
s32 m2;
|
|
RenderPosView *renderPosBase;
|
|
f32 *origin;
|
|
s32 matrix2Flag;
|
|
Mtxf *localMtxPtr;
|
|
Mtxf *matrixPtr;
|
|
f32 scalar;
|
|
|
|
opcode = node->Opcode;
|
|
data = node->Data;
|
|
m0 = data->Group.MatrixID0;
|
|
m1 = data->Group.MatrixID1;
|
|
m2 = data->Group.MatrixID2;
|
|
renderPosBase = model->render_pos;
|
|
render_pos = renderPosBase;
|
|
|
|
if (node->Parent != NULL)
|
|
{
|
|
mtx = modelFindNodeMtx(model, ((0, node))->Parent, 0);
|
|
}
|
|
else
|
|
{
|
|
mtx = renderData->basemtx;
|
|
}
|
|
|
|
localMtxPtr = &localMtx;
|
|
|
|
if (mtx != NULL)
|
|
{
|
|
matrix_4x4_set_position_and_rotation_around_y((f32 *) &data->Group.Origin, angle, localMtxPtr);
|
|
matrix_4x4_multiply_homogeneous(mtx, &localMtx, &render_pos[m0].pos);
|
|
}
|
|
else
|
|
{
|
|
origin = (f32 *) &data->Group.Origin;
|
|
matrix_4x4_set_position_and_rotation_around_y(origin, angle, &render_pos[m0].pos);
|
|
}
|
|
|
|
matrix2Flag = 0x200;
|
|
m0 = opcode & matrix2Flag;
|
|
|
|
if ((opcode & 0x100) || m0)
|
|
{
|
|
if (matrixPtr);
|
|
|
|
if (angle < M_PI_F)
|
|
{
|
|
angle = angle * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
angle = M_TAU_F - ((M_TAU_F - angle) * 0.5f);
|
|
}
|
|
}
|
|
|
|
if (opcode & 0x100)
|
|
{
|
|
if (mtx != NULL)
|
|
{
|
|
matrix_4x4_set_position_and_rotation_around_y((f32 *) &data->Group.Origin, angle, &localMtx);
|
|
matrix_4x4_multiply_homogeneous(mtx, localMtxPtr, &render_pos[m1].pos);
|
|
}
|
|
else
|
|
{
|
|
matrix_4x4_set_position_and_rotation_around_y((f32 *) &data->Group.Origin, angle, &render_pos[m1].pos);
|
|
}
|
|
}
|
|
|
|
if (m0)
|
|
{
|
|
if (mtx != NULL)
|
|
{
|
|
matrixPtr = &localMtx;
|
|
}
|
|
else
|
|
{
|
|
matrixPtr = &render_pos[m2].pos;
|
|
}
|
|
|
|
matrix_4x4_set_rotation_around_y(angle, matrixPtr);
|
|
|
|
if (M_PI_F <= angle)
|
|
{
|
|
angle = M_TAU_F - angle;
|
|
}
|
|
|
|
if (angle < 0.890118f)
|
|
{
|
|
scalar = modelGetBendStretchScale(angle);
|
|
}
|
|
else
|
|
{
|
|
scalar = 1.5f;
|
|
}
|
|
|
|
matrix_column_3_scalar_multiply_2(scalar, (f32 *) matrixPtr);
|
|
matrix_4x4_set_position(&data->Group.Origin, matrixPtr);
|
|
|
|
if (mtx != NULL)
|
|
{
|
|
matrix_4x4_multiply_homogeneous(mtx, matrixPtr, &render_pos[m2].pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Decodes a packed joint angle from the animation bitstream using either mtxA or mtxB.
|
|
f32 sub_GAME_7F06E540(s32 jointIndex, s32 useMtxB, ModelSkeleton *skeleton, ModelAnimation *anim, u8 *bitstream)
|
|
{
|
|
u32 bitOffset;
|
|
u32 raw;
|
|
u8 width;
|
|
f32 angle;
|
|
|
|
angle = 0.0f;
|
|
width = anim->unk06;
|
|
|
|
if (useMtxB != 0) {
|
|
bitOffset = skeleton->Joints[jointIndex].mtxB * width;
|
|
} else {
|
|
bitOffset = skeleton->Joints[jointIndex].mtxA * width;
|
|
}
|
|
|
|
raw = modelAnimReadBitsAsU16Angle(bitstream, width, bitOffset);
|
|
|
|
if (useMtxB != 0) {
|
|
if (raw != 0) {
|
|
angle = ((f32)(s32)(0x10000 - raw) * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
}
|
|
} else {
|
|
angle = ((f32)raw * M_TAU_F) / M_U16_MAX_VALUE_F;
|
|
}
|
|
|
|
return angle;
|
|
}
|
|
|
|
|
|
void process_03_unknown(ModelRenderData *renderData, Model *model, ModelNode *node)
|
|
{
|
|
ModelSkeleton *skeleton;
|
|
ModelRoData_GroupRecord *rodata;
|
|
s32 jointIndex;
|
|
f32 angle;
|
|
f32 tmp2;
|
|
f32 tmp;
|
|
|
|
rodata = &node->Data->Group;
|
|
jointIndex = rodata->JointID;
|
|
skeleton = model->obj->Skeleton;
|
|
|
|
angle = sub_GAME_7F06E540(jointIndex, model->gunhand, skeleton, model->anim, (u8 *)model->unk34);
|
|
|
|
if (model->unk2c != 0.0f) {
|
|
tmp = sub_GAME_7F06E540(jointIndex, model->gunhand, skeleton, model->anim, (u8 *)model->unk38);
|
|
angle = sub_GAME_7F06D0CC(angle, tmp, model->unk2c);
|
|
}
|
|
|
|
if (model->unk84 != 0.0f) {
|
|
tmp = sub_GAME_7F06E540(jointIndex, model->unk25, skeleton, model->anim2, (u8 *)model->unk64);
|
|
|
|
if (model->unk5c != 0.0f) {
|
|
tmp2 = sub_GAME_7F06E540(jointIndex, model->unk25, skeleton, model->anim2, (u8 *)model->unk68);
|
|
tmp = sub_GAME_7F06D0CC(tmp, tmp2, model->unk5c);
|
|
}
|
|
|
|
angle = sub_GAME_7F06D0CC(angle, tmp, model->unk84);
|
|
}
|
|
|
|
sub_GAME_7F06E2B8(renderData, model, node, angle);
|
|
}
|
|
|
|
|
|
void process_15_subposition(ModelRenderData* arg0, Model *model, ModelNode *node)
|
|
{
|
|
union ModelRoData *rodata = node->Data;
|
|
Mtxf *sp68;
|
|
Mtxf sp28;
|
|
s32 mtxindex = rodata->GroupSimple.Group1;
|
|
RenderPosView *matrices = model->render_pos;
|
|
|
|
if (node->Parent)
|
|
{
|
|
sp68 = modelFindNodeMtx(model, node->Parent, 0);
|
|
}
|
|
else
|
|
{
|
|
sp68 = arg0->basemtx;
|
|
}
|
|
|
|
if (sp68)
|
|
{
|
|
matrix_4x4_set_identity_and_position(&rodata->GroupSimple.Origin, &sp28);
|
|
matrix_4x4_multiply_homogeneous(sp68, &sp28, &matrices[mtxindex]);
|
|
}
|
|
else
|
|
{
|
|
matrix_4x4_set_identity_and_position(&rodata->GroupSimple.Origin, &matrices[mtxindex]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Address: 0x7F06E858
|
|
*/
|
|
void modelUpdateDistanceRelations(Model* model, ModelNode* node)
|
|
{
|
|
union ModelRoData *rodata = node->Data;
|
|
union ModelRwData *rwdata = modelGetNodeRwData(model, node);
|
|
Mtxf *mtx = modelFindNodeMtx(model, node, 0);
|
|
f32 distance;
|
|
|
|
if (g_ModelDistanceDisabled)
|
|
{
|
|
distance = 0;
|
|
}
|
|
else
|
|
{
|
|
distance = -mtx->m[3][2] * getPlayer_c_lodscalez();
|
|
|
|
if (g_ModelDistanceScale != 1)
|
|
{
|
|
distance *= g_ModelDistanceScale;
|
|
}
|
|
|
|
#ifdef PORT
|
|
/* D249/D218 follow-up: Video.LodDistance. Composed on top of (not
|
|
* replacing) g_ModelDistanceScale so it doesn't fight the game's own
|
|
* transient overrides (chr.c's 0.3125f close-up scale, the
|
|
* modelSetDistanceDisabled front-end/menu paths above). A smaller
|
|
* distance here reads as "closer" to the MinDistance/MaxDistance
|
|
* test below, so a multiplier < 1.0 keeps full-detail LOD children
|
|
* visible farther out (more detail, more cost); > 1.0 drops to the
|
|
* lower LOD sooner (less detail, cheaper) -- a real performance
|
|
* lever, independent of Video.DrawDistance's fog/far-clip distance.
|
|
* Identity at the Video.LodDistance=100 default. */
|
|
{
|
|
extern f32 portLodDistanceMultiplier(void);
|
|
distance *= portLodDistanceMultiplier();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (distance > rodata->LOD.MinDistance * model->scale || rodata->LOD.MinDistance == 0)
|
|
{
|
|
if (distance <= rodata->LOD.MaxDistance * model->scale)
|
|
{
|
|
rwdata->LOD.visible = TRUE;
|
|
node->Child = rodata->LOD.Affects;
|
|
return;
|
|
}
|
|
}
|
|
|
|
rwdata->LOD.visible = FALSE;
|
|
node->Child = NULL;
|
|
}
|
|
|
|
/*
|
|
* Address: 0x7F06E970
|
|
*/
|
|
void modelApplyDistanceRelations(Model* model, ModelNode* node)
|
|
{
|
|
ModelRoData_LODRecord *rodata = &node->Data->LOD;
|
|
ModelRwData_LODRecord *rwdata = modelGetNodeRwData(model, node);
|
|
|
|
if (rwdata->visible)
|
|
{
|
|
node->Child = rodata->Affects;
|
|
}
|
|
else
|
|
{
|
|
node->Child = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void modelApplyToggleRelations(Model* model, ModelNode* node)
|
|
{
|
|
ModelRoData_SwitchRecord *rodata = &node->Data->Switch;
|
|
ModelRwData_SwitchRecord *rwdata = modelGetNodeRwData(model, node);
|
|
|
|
if (rwdata->visible)
|
|
{
|
|
node->Child = rodata->Controls;
|
|
}
|
|
else
|
|
{
|
|
node->Child = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void modelApplyHeadRelations(Model* model, ModelNode* bodynode)
|
|
{
|
|
struct ModelRwData_HeadPlaceholderRecord *rwdata = modelGetNodeRwData(model, bodynode);
|
|
|
|
if (rwdata->ModelFileHeader)
|
|
{
|
|
ModelNode *headnode = rwdata->ModelFileHeader->RootNode;
|
|
|
|
bodynode->Child = headnode;
|
|
|
|
while (headnode)
|
|
{
|
|
headnode->Parent = bodynode;
|
|
headnode = headnode->Next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void modelApplyReorderRelationsByArg(ModelNode *basenode, bool visible)
|
|
{
|
|
union ModelRoData *rodata = basenode->Data;
|
|
ModelNode *node1;
|
|
ModelNode *node2;
|
|
ModelNode *loopnode;
|
|
|
|
if (visible)
|
|
{
|
|
node1 = rodata->BSP.leftChild;
|
|
node2 = rodata->BSP.rightChild;
|
|
}
|
|
else
|
|
{
|
|
node1 = rodata->BSP.rightChild;
|
|
node2 = rodata->BSP.leftChild;
|
|
}
|
|
|
|
if (node1)
|
|
{
|
|
// I think what's happening here is there's two groups of siblings,
|
|
// where node1 and node2 are the head nodes. Either group can be first,
|
|
// and this is ensuring the node1 group is first.
|
|
// Note that node2 might be NULL.
|
|
|
|
basenode->Child = node1;
|
|
node1->Prev = NULL;
|
|
|
|
// Skip through node1's siblings until node2 is found or the end is
|
|
// reached
|
|
loopnode = node1;
|
|
|
|
while (loopnode->Next && loopnode->Next != node2)
|
|
{
|
|
loopnode = loopnode->Next;
|
|
}
|
|
|
|
loopnode->Next = node2;
|
|
|
|
if (node2)
|
|
{
|
|
// Append node2 and its siblings to node1's siblings
|
|
node2->Prev = loopnode;
|
|
loopnode = node2;
|
|
|
|
while (loopnode->Next && loopnode->Next != node1)
|
|
{
|
|
loopnode = loopnode->Next;
|
|
}
|
|
|
|
loopnode->Next = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
basenode->Child = node2;
|
|
|
|
if (node2)
|
|
{
|
|
node2->Prev = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void modelApplyReorderRelations(Model* model, ModelNode* node)
|
|
{
|
|
union ModelRwData *rwdata = modelGetNodeRwData(model, node);
|
|
modelApplyReorderRelationsByArg(node, rwdata->BSP.visible);
|
|
}
|
|
|
|
|
|
void modelUpdateReorderRelations(Model *model, ModelNode *node)
|
|
{
|
|
union ModelRoData *rodata = node->Data;
|
|
union ModelRwData *rwdata = modelGetNodeRwData(model, node);
|
|
Mtxf *mtx = modelFindNodeMtx(model, node, 0);
|
|
coord3d sp38;
|
|
coord3d sp2c;
|
|
f32 tmp;
|
|
|
|
if (rodata->BSP.reserved == 0)
|
|
{
|
|
sp38.x = rodata->BSP.Vector.f[0];
|
|
sp38.y = rodata->BSP.Vector.f[1];
|
|
sp38.z = rodata->BSP.Vector.f[2];
|
|
mtx4RotateVecInPlace(mtx, sp38.f);
|
|
}
|
|
else if (rodata->BSP.reserved == 2)
|
|
{
|
|
sp38.x = mtx->m[1][0] * rodata->BSP.Vector.f[1];
|
|
sp38.y = mtx->m[1][1] * rodata->BSP.Vector.f[1];
|
|
sp38.z = mtx->m[1][2] * rodata->BSP.Vector.f[1];
|
|
}
|
|
else if (rodata->BSP.reserved == 3)
|
|
{
|
|
sp38.x = mtx->m[2][0] * rodata->BSP.Vector.f[2];
|
|
sp38.y = mtx->m[2][1] * rodata->BSP.Vector.f[2];
|
|
sp38.z = mtx->m[2][2] * rodata->BSP.Vector.f[2];
|
|
}
|
|
else if (rodata->BSP.reserved == 1)
|
|
{
|
|
sp38.x = mtx->m[0][0] * rodata->BSP.Vector.f[0];
|
|
sp38.y = mtx->m[0][1] * rodata->BSP.Vector.f[0];
|
|
sp38.z = mtx->m[0][2] * rodata->BSP.Vector.f[0];
|
|
}
|
|
|
|
sp2c.x = rodata->BSP.Point.f[0];
|
|
sp2c.y = rodata->BSP.Point.f[1];
|
|
sp2c.z = rodata->BSP.Point.f[2];
|
|
|
|
mtx4TransformVecInPlace(mtx, &sp2c);
|
|
|
|
tmp = sp38.f[0] * sp2c.f[0] + sp38.f[1] * sp2c.f[1] + sp38.f[2] * sp2c.f[2];
|
|
|
|
if (tmp < 0)
|
|
{
|
|
rwdata->BSP.visible = TRUE;
|
|
}
|
|
else
|
|
{
|
|
rwdata->BSP.visible = FALSE;
|
|
}
|
|
|
|
modelApplyReorderRelations(model, node);
|
|
}
|
|
|
|
|
|
void process_07_unknown(Model *model, ModelNode *node)
|
|
{
|
|
union ModelRoData *rodata = node->Data;
|
|
union ModelRwData *rwdata = modelGetNodeRwData(model, node);
|
|
Mtxf *mtx = modelFindNodeMtx(model, node, 0);
|
|
f32 ratio;
|
|
f32 coord_multiplied;
|
|
coord3d coord;
|
|
s32 index1;
|
|
f32 theta;
|
|
s32 index2;
|
|
s32 index3;
|
|
|
|
modelGetScaledRootToOriginDir(model, &coord);
|
|
|
|
theta = acosf(((coord.x * mtx->m[1][0]) + (coord.y * mtx->m[1][1])) + (coord.z * mtx->m[1][2]));
|
|
ratio = acosf((((coord.x * mtx->m[2][0]) + (coord.y * mtx->m[2][1])) + (coord.z * mtx->m[2][2])) / sinf(theta));
|
|
coord_multiplied = ((coord.x * mtx->m[0][0]) + (coord.y * mtx->m[0][1])) + (coord.z * mtx->m[0][2]);
|
|
|
|
if ((coord_multiplied < 0.0f) && (ratio > 0.0f))
|
|
{
|
|
ratio = M_TAU_F - ratio;
|
|
}
|
|
|
|
index1 = (theta * 64.0f) / M_TAU_F;
|
|
|
|
index2 = (s32) ((ratio * M_U16_MAX_VALUE_F) / M_TAU_F);
|
|
index2 += D_800360C4[index1].unk04;
|
|
index2 = index2 >> D_800360C4[index1].unk0C;
|
|
|
|
index3 = index2 + D_800360C4[index1].unk00;
|
|
|
|
rwdata->Op07.index = rodata->Op07.Data[index3];
|
|
}
|
|
|
|
|
|
void modelUpdateRelationsQuick(Model *model, ModelNode *parent)
|
|
{
|
|
ModelNode *node = parent->Child;
|
|
ModelNode **unused_parent;
|
|
|
|
while (node)
|
|
{
|
|
s32 type = node->Opcode & 0xff;
|
|
bool dochildren = TRUE;
|
|
|
|
switch (type)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
case MODELNODE_OPCODE_GROUP:
|
|
case MODELNODE_OPCODE_OP03:
|
|
case MODELNODE_OPCODE_OP11:
|
|
case MODELNODE_OPCODE_GUNFIRE:
|
|
case MODELNODE_OPCODE_SHADOW:
|
|
case MODELNODE_OPCODE_OP14:
|
|
case MODELNODE_OPCODE_INTERLINK:
|
|
case MODELNODE_OPCODE_OP16:
|
|
case MODELNODE_OPCODE_GROUPSIMPLE:
|
|
dochildren = FALSE;
|
|
break;
|
|
case MODELNODE_OPCODE_LOD:
|
|
modelUpdateDistanceRelations(model, node);
|
|
break;
|
|
case MODELNODE_OPCODE_BSP:
|
|
modelUpdateReorderRelations(model, node);
|
|
break;
|
|
case MODELNODE_OPCODE_OP07:
|
|
process_07_unknown(model, node);
|
|
break;
|
|
case MODELNODE_OPCODE_HEAD:
|
|
modelApplyHeadRelations(model, node);
|
|
break;
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
break;
|
|
}
|
|
|
|
if (dochildren && node->Child)
|
|
{
|
|
node = node->Child;
|
|
}
|
|
else
|
|
{
|
|
unused_parent = &parent;
|
|
while (node)
|
|
{
|
|
if (node == parent->Parent)
|
|
{
|
|
node = NULL;
|
|
break;
|
|
}
|
|
|
|
if (node->Next)
|
|
{
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Address: 0x7F06EFC4
|
|
*/
|
|
void modelUpdateNodeRelations(Model *model)
|
|
{
|
|
ModelNode *node = model->obj->RootNode;
|
|
|
|
while (node)
|
|
{
|
|
u32 type = node->Opcode & 0xff;
|
|
|
|
switch (type)
|
|
{
|
|
case MODELNODE_OPCODE_LOD:
|
|
modelUpdateDistanceRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_BSP:
|
|
modelUpdateReorderRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_OP07:
|
|
process_07_unknown(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
modelApplyToggleRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_HEAD:
|
|
modelApplyHeadRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_HEADER:
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (node->Child)
|
|
{
|
|
node = node->Child;
|
|
}
|
|
else
|
|
{
|
|
while (node)
|
|
{
|
|
if (node->Next)
|
|
{
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void modelUpdateMatrices(ModelRenderData *arg0, Model *model)
|
|
{
|
|
ModelNode *node = model->obj->RootNode;
|
|
|
|
while (node)
|
|
{
|
|
u32 type = node->Opcode & 0xff;
|
|
|
|
switch (type)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
process_01_group_heading(arg0, model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_GROUP:
|
|
process_02_position(arg0, model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_OP03:
|
|
process_03_unknown(arg0, model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_GROUPSIMPLE:
|
|
process_15_subposition(arg0, model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_LOD:
|
|
modelUpdateDistanceRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_BSP:
|
|
modelUpdateReorderRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_OP07:
|
|
process_07_unknown(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
modelApplyToggleRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_HEAD:
|
|
modelApplyHeadRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (node->Child)
|
|
{
|
|
node = node->Child;
|
|
}
|
|
else
|
|
{
|
|
while (node)
|
|
{
|
|
if (node->Next)
|
|
{
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void instcalcmatrices(ModelRenderData* arg0, Model* arg1)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (arg1 == NULL)
|
|
{
|
|
osSyncPrintf("instcalcmatrices: no objinst!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (arg0->basemtx == NULL)
|
|
{
|
|
osSyncPrintf("instcalcmatrices: no basemtx!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (arg0->mtxlist == NULL)
|
|
{
|
|
osSyncPrintf("instcalcmatrices: no mtxlist!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
arg1->render_pos = (RenderPosView* ) arg0->mtxlist;
|
|
arg0->mtxlist += arg1->obj->numMatrices;
|
|
modelUpdateMatrices((ModelRenderData* ) arg0, arg1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 0x7F06F2F8 (VERSION_US, VERSION_JP)
|
|
* Address 0x7F06F670 (VERSION_EU)
|
|
*/
|
|
void subcalcmatrices(ModelRenderData *arg0, struct Model *arg1)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (arg1 == NULL)
|
|
{
|
|
osSyncPrintf("subcalcmatrices: no objanim!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (arg0->basemtx == NULL)
|
|
{
|
|
osSyncPrintf("subcalcmatrices: no basemtx!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (arg0->mtxlist == NULL)
|
|
{
|
|
osSyncPrintf("subcalcmatrices: no mtxlist!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
|
|
if (arg1->anim != NULL)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if ((arg1->attachedto != NULL) && (arg1->attachedto_objinst == NULL))
|
|
{
|
|
osSyncPrintf("subcalcmatrices: no attach for objinst!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (((s32) arg1->framea < 0) || ((s32) arg1->framea >= (s32) arg1->anim->unk04))
|
|
{
|
|
osSyncPrintf("subcalcmatrices: framea out of range!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (((s32) arg1->frameb < 0) || ((s32) arg1->frameb >= (s32) arg1->anim->unk04))
|
|
{
|
|
osSyncPrintf("subcalcmatrices: frameb out of range!\n");
|
|
return_null();
|
|
}
|
|
|
|
if ((arg1->unk84 == 0) || ((arg1->unk84 != 0) && (arg1->anim2 != NULL)))
|
|
{
|
|
//
|
|
}
|
|
else
|
|
{
|
|
osSyncPrintf("subcalcmatrices: no anim2!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (
|
|
(arg1->anim2 != NULL)
|
|
&& (
|
|
(arg1->anim2 == NULL)
|
|
|| (arg1->frame2a < 0)
|
|
|| ((s32) arg1->frame2a >= (s32) arg1->anim2->unk04)
|
|
)
|
|
)
|
|
{
|
|
osSyncPrintf("subcalcmatrices: frame2a out of range!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (
|
|
(arg1->anim2 == NULL)
|
|
|| (
|
|
(arg1->anim2 != NULL)
|
|
&& (arg1->frame2b >= 0)
|
|
&& ((s32) arg1->frame2b < (s32) arg1->anim2->unk04)
|
|
)
|
|
)
|
|
{
|
|
//
|
|
}
|
|
else
|
|
{
|
|
osSyncPrintf("subcalcmatrices: frame2b out of range!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
|
|
arg1->unk34 = loadAnimationFrame(arg1->anim, arg1->framea, arg1->obj->Skeleton);
|
|
|
|
if (arg1->unk2c != 0.0f)
|
|
{
|
|
arg1->unk38 = loadAnimationFrame(arg1->anim, arg1->frameb, arg1->obj->Skeleton);
|
|
}
|
|
|
|
if (arg1->anim2 != NULL)
|
|
{
|
|
arg1->unk64 = loadAnimationFrame(arg1->anim2, arg1->frame2a, arg1->obj->Skeleton);
|
|
|
|
if (arg1->unk5c != 0.0f)
|
|
{
|
|
arg1->unk68 = loadAnimationFrame(arg1->anim2, arg1->frame2b, arg1->obj->Skeleton);
|
|
}
|
|
}
|
|
|
|
modelResetAnimationsScratchBuffer();
|
|
}
|
|
|
|
instcalcmatrices(arg0, arg1);
|
|
}
|
|
|
|
/**
|
|
* Address 0x7F06F5AC.
|
|
*/
|
|
struct ModelAnimation * objecthandlerGetModelAnim(struct Model* model) {
|
|
return model->anim;
|
|
}
|
|
|
|
s8 objecthandlerGetModelGunhand(Model *model)
|
|
{
|
|
return model->gunhand;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 0x7F06F5BC.
|
|
*/
|
|
f32 modelGetAnimFrame(Model *model)
|
|
{
|
|
return model->animframe1;
|
|
}
|
|
|
|
|
|
f32 modelGetAnimEndFrame(Model *model)
|
|
{
|
|
f32 end;
|
|
ModelAnimation *modelAnimation;
|
|
|
|
end = model->endframe;
|
|
|
|
if (end >= 0.0f)
|
|
{
|
|
return end;
|
|
}
|
|
|
|
modelAnimation = model->anim;
|
|
|
|
if (modelAnimation != NULL)
|
|
{
|
|
return modelAnimation->unk04 - 1;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
f32 modelGetAnimSpeed(Model *model)
|
|
{
|
|
return model->speed;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 0x7F06F618.
|
|
* PD: modelGetAbsAnimSpeed
|
|
*/
|
|
f32 modelGetAbsAnimSpeed(Model *model)
|
|
{
|
|
f32 speed;
|
|
|
|
speed = model->speed;
|
|
|
|
if (speed < 0.0f)
|
|
{
|
|
speed = -speed;
|
|
}
|
|
|
|
return speed;
|
|
}
|
|
|
|
/**
|
|
* Unused Function
|
|
* Unreferenced
|
|
*/
|
|
f32 modelGetEffectiveAnimSpeed(Model *model) {
|
|
return modelGetAnimSpeed(model) * model->playspeed;
|
|
}
|
|
|
|
|
|
s32 modelConstrainOrWrapAnimFrame(s32 frame, ModelAnimation *anim, f32 endframe)
|
|
{
|
|
if (frame < 0) {
|
|
if (anim->unk07 & 1) {
|
|
frame = anim->unk04 - ((-frame) % anim->unk04);
|
|
} else {
|
|
frame = 0;
|
|
}
|
|
}
|
|
else if ((0.0f <= endframe) && ((s32)endframe < frame)) {
|
|
frame = ceilFloatToInt(endframe);
|
|
}
|
|
else if (frame >= anim->unk04) {
|
|
if (anim->unk07 & 1) {
|
|
frame %= anim->unk04;
|
|
} else {
|
|
frame = anim->unk04 - 1;
|
|
}
|
|
}
|
|
else {
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
|
|
void modelCopyAnimForMerge(Model *model, f32 timemerge)
|
|
{
|
|
ModelAnimation *anim;
|
|
ModelNode *root;
|
|
struct modeldata_root *rwdata;
|
|
s32 opcode;
|
|
|
|
if (0.0f < timemerge) {
|
|
anim = model->anim;
|
|
|
|
if (anim != NULL) {
|
|
root = model->obj->RootNode;
|
|
opcode = root->Opcode & 0xff;
|
|
|
|
model->anim2 = anim;
|
|
model->animframe2 = model->animframe1;
|
|
model->unk5c = model->unk2c;
|
|
model->unk25 = model->gunhand;
|
|
model->frame2a = model->framea;
|
|
model->frame2b = model->frameb;
|
|
model->speed2 = model->speed;
|
|
model->unk74 = model->newspeed;
|
|
model->unk78 = model->oldspeed;
|
|
model->unk7c = model->timespeed;
|
|
model->unk80 = model->elapsespeed;
|
|
model->unk6c = model->endframe;
|
|
|
|
if (opcode == MODELNODE_OPCODE_HEADER) {
|
|
rwdata = (struct modeldata_root *)modelGetNodeRwData(model, root);
|
|
rwdata->unk02 = 1;
|
|
rwdata->unk4c.x = rwdata->unk34.x;
|
|
rwdata->unk4c.y = rwdata->unk34.y;
|
|
rwdata->unk4c.z = rwdata->unk34.z;
|
|
rwdata->unk40.x = rwdata->unk24.x;
|
|
rwdata->unk40.y = rwdata->unk24.y;
|
|
rwdata->unk40.z = rwdata->unk24.z;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
model->anim2 = NULL;
|
|
}
|
|
|
|
|
|
#ifdef PORT
|
|
/* D311 (M-194): the upstream poison source is still unknown -- GDB caught
|
|
* oldspeed=-1.75e18 / newspeed=-3.2e7 but not which call first wrote a
|
|
* garbage speed. Every write of model->speed funnels through
|
|
* modelSetAnimSpeed (startframe<=0 path) or modelSetAnimation2, so flag any
|
|
* anomalous value entering OR already resident at those two choke points.
|
|
* Fires only on |v|>1e4 or NaN -- silent in healthy play; the line pins the
|
|
* exact transition that poisons the model. Same unconditional-on-fire
|
|
* pattern as the D156 guards (which log when they fire, no env var). */
|
|
static int d311Anomalous(f32 v) { return (v != v) || (v > 10000.0f) || (v < -10000.0f); }
|
|
#endif
|
|
|
|
void modelSetAnimation2(Model *model, ModelAnimation *anim, s32 flip, f32 frame, f32 speed, f32 arg5)
|
|
{
|
|
#ifdef PORT
|
|
if (model != NULL && d311Anomalous(speed)) {
|
|
osSyncPrintf("D311: setanimation2 ANOMALY in=%.6g resident=%.6g frame=%.6g model=%p\n",
|
|
(double)speed, (double)model->speed, (double)frame, (void *)model);
|
|
}
|
|
#endif
|
|
s32 hadNoAnim = !model->anim;
|
|
s32 padding;
|
|
s32 type;
|
|
|
|
if (model->anim2 != NULL) {
|
|
model->unk88 = arg5;
|
|
model->unk8c = 0.0f;
|
|
model->unk84 = 1.0f;
|
|
} else {
|
|
model->unk88 = 0.0f;
|
|
model->unk84 = 0.0f;
|
|
}
|
|
|
|
model->anim = anim;
|
|
model->gunhand = flip;
|
|
model->endframe = -1.0f;
|
|
model->speed = speed;
|
|
model->timespeed = 0.0f;
|
|
|
|
modelSetAnimFrame(model, frame);
|
|
|
|
model->animlooping = 0;
|
|
|
|
type = model->obj->RootNode->Opcode & 0xff;
|
|
|
|
if (type == MODELNODE_OPCODE_HEADER) {
|
|
ModelRoData_HeaderRecord *rodata = &model->obj->RootNode->Data->Header;
|
|
ModelRwData_HeaderRecord *rwdata = modelGetNodeRwData(model, model->obj->RootNode);
|
|
f32 temp_f14;
|
|
f32 sinAngle;
|
|
f32 scale;
|
|
f32 cosAngle;
|
|
s32 animPart;
|
|
coord3d pos;
|
|
f32 angleDelta;
|
|
coord3d tmppos[2];
|
|
ModelSkeleton *skeleton;
|
|
|
|
animPart = rodata->AnimPart;
|
|
skeleton = model->obj->Skeleton;
|
|
scale = model->scale * model->anim_translation_scale;
|
|
pos = D_80036244;
|
|
|
|
angleDelta = sub_GAME_7F06D3F4(animPart, model->gunhand, skeleton, model->anim, model->frameb, &pos
|
|
);
|
|
|
|
if (scale != 1.0f) {
|
|
pos.f[0] *= scale;
|
|
pos.f[1] *= scale;
|
|
pos.f[2] *= scale;
|
|
}
|
|
|
|
cosAngle = cosf(rwdata->unk14);
|
|
sinAngle = sinf(rwdata->unk14);
|
|
scale = model->unk2c;
|
|
|
|
if (model->unk2c == 0.0f) {
|
|
rwdata->unk34.x = rwdata->pos.f[0];
|
|
rwdata->unk34.y = rwdata->pos.f[1] - rwdata->ground;
|
|
rwdata->unk34.z = rwdata->pos.f[2];
|
|
|
|
rwdata->unk30 = rwdata->unk14;
|
|
|
|
tmppos[1].x = rwdata->unk34.f[0] + (pos.f[0] * cosAngle) + (pos.f[2] * sinAngle);
|
|
tmppos[1].y = pos.f[1];
|
|
tmppos[1].z = rwdata->unk34.f[2] - (pos.f[0] * sinAngle) + (pos.f[2] * cosAngle);
|
|
|
|
rwdata->unk24.x = tmppos[1].f[0];
|
|
rwdata->unk24.y = tmppos[1].f[1];
|
|
rwdata->unk24.z = tmppos[1].f[2];
|
|
|
|
if (rwdata->unk18 == 0.0f) {
|
|
rwdata->unk20 = rwdata->unk30 + angleDelta;
|
|
|
|
if (rwdata->unk20 >= M_TAU_F) {
|
|
rwdata->unk20 -= M_TAU_F;
|
|
}
|
|
}
|
|
|
|
rwdata->unk01 = 1;
|
|
} else {
|
|
f32 x;
|
|
f32 y;
|
|
f32 z;
|
|
|
|
x = pos.f[0] * cosAngle + pos.f[2] * sinAngle;
|
|
y = pos.f[1];
|
|
z = -pos.f[0] * sinAngle + pos.f[2] * cosAngle;
|
|
|
|
tmppos[0].x = rwdata->pos.f[0] + x * (1.0f - model->unk2c);
|
|
tmppos[0].y = y;
|
|
tmppos[0].z = rwdata->pos.f[2] + z * (1.0f - model->unk2c);
|
|
|
|
rwdata->unk24.x = tmppos[0].x;
|
|
rwdata->unk24.y = tmppos[0].y;
|
|
rwdata->unk24.z = tmppos[0].z;
|
|
|
|
rwdata->unk34.f[0] = rwdata->unk24.f[0] - x;
|
|
rwdata->unk34.f[1] = (rwdata->pos.f[1] - rwdata->ground) - (y - (rwdata->pos.f[1] - rwdata->ground)) * model->unk2c / (1.0f - model->unk2c);
|
|
rwdata->unk34.f[2] = rwdata->unk24.f[2] - z;
|
|
|
|
temp_f14 = rwdata->unk14 - angleDelta;
|
|
|
|
if (temp_f14 < 0.0f) {
|
|
temp_f14 += M_TAU_F;
|
|
}
|
|
|
|
rwdata->unk30 = sub_GAME_7F06D0CC(rwdata->unk14, temp_f14, model->unk2c);
|
|
|
|
if (rwdata->unk18 == 0.0f) {
|
|
rwdata->unk20 = rwdata->unk30 + angleDelta;
|
|
|
|
if (rwdata->unk20 >= M_TAU_F) {
|
|
rwdata->unk20 -= M_TAU_F;
|
|
}
|
|
}
|
|
|
|
rwdata->unk01 = 1;
|
|
}
|
|
|
|
if (hadNoAnim) {
|
|
rwdata->unk34.y = rwdata->unk24.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void modelSetAnimationWithMerge(Model *model, ModelAnimation *modelAnimation, s32 flip, f32 startframe, f32 speed, f32 timemerge, s32 domerge) {
|
|
if (domerge != 0) {
|
|
modelCopyAnimForMerge(model, timemerge);
|
|
}
|
|
modelSetAnimation2(model, modelAnimation, flip, startframe, speed, timemerge);
|
|
}
|
|
|
|
|
|
void modelSetAnimation(Model *model, ModelAnimation *modelAnimation, s32 flip, f32 startframe, f32 speed, f32 merge) {
|
|
modelCopyAnimForMerge(model, merge);
|
|
modelSetAnimation2(model, modelAnimation, flip, startframe, speed, merge);
|
|
}
|
|
|
|
|
|
/*
|
|
* Match-only overlay types for sub_GAME_7F06FCFC.
|
|
*/
|
|
typedef struct ModelCopyHead {
|
|
u32 words[8]; // 0x00-0x1f
|
|
} ModelCopyHead;
|
|
|
|
|
|
typedef struct ModelCopyBc {
|
|
u32 words[0x2f]; // 0x00-0xbb
|
|
} ModelCopyBc;
|
|
|
|
|
|
/**
|
|
* Unreferenced.
|
|
*
|
|
* The function copies the Model data through anim_translation_scale (0x00-0xbb),
|
|
* then restores the destination's base/resource fields (0x00-0x1f).
|
|
* Maybe some kind of old/abandoned anim copy function.
|
|
*/
|
|
void sub_GAME_7F06FCFC(Model *src, Model *dst)
|
|
{
|
|
ModelCopyHead tmp;
|
|
|
|
tmp = *(ModelCopyHead *)dst;
|
|
*(ModelCopyBc *)dst = *(ModelCopyBc *)src;
|
|
*(ModelCopyHead *)dst = tmp;
|
|
}
|
|
|
|
|
|
void modelSetAnimLooping(Model *model, f32 loopframe, f32 loopmerge) {
|
|
model->animlooping = 1;
|
|
model->animloopframe = loopframe;
|
|
model->animloopmerge = loopmerge;
|
|
}
|
|
|
|
|
|
void modelSetAnimEndFrame(Model *model, f32 endframe) {
|
|
ModelAnimation *modelAnimation = model->anim;
|
|
|
|
if ((modelAnimation != NULL) && (endframe < (modelAnimation->unk04 - 1))) {
|
|
model->endframe = endframe;
|
|
} else {
|
|
model->endframe = -1.0f;
|
|
}
|
|
#ifdef DEBUG
|
|
// not too sure why debug wants to call this - must have some significance when most debug has been stripped from this file in XBLA
|
|
modelSetAnimFrame(model, (int)model->animframe1);
|
|
#endif
|
|
}
|
|
|
|
void modelSetAnimFlipFunction(Model *model, void *callback) {
|
|
#ifdef PORT
|
|
/* D99: `animflipfunc` is an s32 field and is only ever set to
|
|
* bheadFlipAnimation (initBondDATAdefaults.c, bondhead.c -- both for
|
|
* g_CurrentPlayer->model). Storing a 64-bit fn pointer truncates it and
|
|
* the indirect call in modelTickAnim jumps to 0x00010100. Same pattern as
|
|
* D92/unka0: store a flag, call bheadFlipAnimation directly at the one
|
|
* call site. */
|
|
model->animflipfunc = (callback != NULL);
|
|
#else
|
|
model->animflipfunc = callback;
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* Unused Function
|
|
*/
|
|
void sub_GAME_7F06FE44(Model *model, s32 arg1) {
|
|
model->unk9c = arg1;
|
|
}
|
|
|
|
void modelSetAnimSpeed(Model *model, f32 anim_speed, f32 startframe) {
|
|
#ifdef PORT
|
|
if (model != NULL && (d311Anomalous(anim_speed) || d311Anomalous(model->speed))) {
|
|
osSyncPrintf("D311: setanimspeed ANOMALY in=%.6g resident=%.6g startframe=%.6g "
|
|
"oldspeed=%.6g newspeed=%.6g timespeed=%.6g model=%p\n",
|
|
(double)anim_speed, (double)model->speed, (double)startframe,
|
|
(double)model->oldspeed, (double)model->newspeed,
|
|
(double)model->timespeed, (void *)model);
|
|
}
|
|
#endif
|
|
|
|
if (startframe > 0.0f) {
|
|
model->timespeed = startframe;
|
|
model->newspeed = anim_speed;
|
|
model->elapsespeed = 0.0f;
|
|
model->oldspeed = model->speed;
|
|
return;
|
|
}
|
|
|
|
model->speed = anim_speed;
|
|
model->timespeed = 0.0f;
|
|
}
|
|
|
|
/**
|
|
* @param arg0:
|
|
* @param arg1:
|
|
* @param arg2: must be non-zero.
|
|
*
|
|
* Address 0x7F06FE90.
|
|
*/
|
|
void sub_GAME_7F06FE90(Model *model, f32 arg1, f32 arg2)
|
|
{
|
|
f32 temp_f0;
|
|
f32 phi_f2;
|
|
f32 t;
|
|
|
|
temp_f0 = model->animframe1;
|
|
|
|
if (temp_f0 <= arg1)
|
|
{
|
|
phi_f2 = arg1 - temp_f0;
|
|
}
|
|
else
|
|
{
|
|
phi_f2 = ( (f32)model->anim->unk04 - temp_f0) + arg1;
|
|
}
|
|
|
|
t = model->speed + ((2.0f * phi_f2) / arg2);
|
|
modelSetAnimSpeed(model, t, arg2);
|
|
}
|
|
|
|
void modelSetAnimPlaySpeed(Model *model, f32 animation_rate, f32 startframe) {
|
|
if (startframe > 0.0f) {
|
|
model->unkb0 = startframe;
|
|
model->animrate = animation_rate;
|
|
model->unkb4 = 0.0f;
|
|
model->unkac = model->playspeed;
|
|
return;
|
|
}
|
|
model->playspeed = animation_rate;
|
|
model->unkb0 = 0.0f;
|
|
}
|
|
|
|
|
|
void sub_GAME_7F06FF5C(Model *model, s32 arg1) {
|
|
model->unka0 = arg1;
|
|
}
|
|
|
|
|
|
void modelSetAnimFrame(Model* model, f32 frame)
|
|
{
|
|
s32 framea;
|
|
s32 frameb;
|
|
bool forwards;
|
|
|
|
framea = floorFloatToInt(frame);
|
|
|
|
forwards = (model->speed >= 0);
|
|
frameb = (forwards ? framea + 1 : framea - 1);
|
|
|
|
model->framea = modelConstrainOrWrapAnimFrame(framea, model->anim, model->endframe);
|
|
model->frameb = modelConstrainOrWrapAnimFrame(frameb, model->anim, model->endframe);
|
|
|
|
if (model->framea == model->frameb)
|
|
{
|
|
model->unk2c = 0.0f;
|
|
model->animframe1 = model->framea;
|
|
}
|
|
else if (forwards)
|
|
{
|
|
f32 tmp = frame - framea;
|
|
model->unk2c = tmp;
|
|
model->animframe1 = model->framea + tmp;
|
|
}
|
|
else
|
|
{
|
|
f32 tmp = 1.0f - (frame - (f32) frameb);
|
|
model->unk2c = tmp;
|
|
model->animframe1 = model->frameb + (1.0f - tmp);
|
|
}
|
|
}
|
|
|
|
|
|
void modelSetAnimFrame2(Model* model, f32 frame1, f32 frame2)
|
|
{
|
|
s32 framea;
|
|
s32 frameb;
|
|
bool forwards;
|
|
|
|
modelSetAnimFrame(model, frame1);
|
|
|
|
if (model->anim2 != NULL)
|
|
{
|
|
framea = floorFloatToInt(frame2);
|
|
|
|
forwards = (model->speed2 >= 0.0f);
|
|
frameb = forwards ? (framea + 1) : (framea - 1);
|
|
|
|
model->frame2a = modelConstrainOrWrapAnimFrame(framea, model->anim2, model->unk6c);
|
|
model->frame2b = modelConstrainOrWrapAnimFrame(frameb, model->anim2, model->unk6c);
|
|
|
|
if (model->frame2a == model->frame2b)
|
|
{
|
|
model->unk5c = 0.0f;
|
|
model->animframe2 = model->frame2a;
|
|
}
|
|
else if (forwards != 0)
|
|
{
|
|
f32 tmp = frame2 - framea;
|
|
model->unk5c = tmp;
|
|
model->animframe2 = model->frame2a + tmp;
|
|
}
|
|
else
|
|
{
|
|
f32 tmp = 1.0f - (frame2 - (f32) frameb);
|
|
model->unk5c = tmp;
|
|
model->animframe2 = model->frame2b + (1.0f - tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 0x7F0701D4.
|
|
*/
|
|
void modelSetAnimMergingEnabled(s32 arg0)
|
|
{
|
|
g_ModelAnimMergingEnabled = arg0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 0x7F0701E0.
|
|
*/
|
|
u32 modelIsAnimMergingEnabled(void)
|
|
{
|
|
return g_ModelAnimMergingEnabled;
|
|
}
|
|
|
|
|
|
void modelSetAnimFrame2WithChrStuff(Model *model, f32 framea, f32 frameb, f32 frame2a, f32 frame2b)
|
|
{
|
|
Model *modelptr;
|
|
ModelRwData_HeaderRecord *header;
|
|
ModelNode *root;
|
|
ModelNode *node;
|
|
s32 jointnum;
|
|
ModelSkeleton *skeleton;
|
|
f32 scale;
|
|
s32 curframe;
|
|
f32 speed;
|
|
coord3d pos;
|
|
f32 anglecur;
|
|
f32 cosangle;
|
|
s32 vb;
|
|
s32 endframe;
|
|
coord3d pos34;
|
|
s32 forward;
|
|
coord3d pos24;
|
|
f32 angle20;
|
|
s32 flag;
|
|
f32 angledelta;
|
|
f32 sinangle;
|
|
f32 speed2;
|
|
f32 v[3];
|
|
s32 framenum;
|
|
s32 va;
|
|
f32 inv;
|
|
s32 end2;
|
|
|
|
modelptr = model;
|
|
root = modelptr->obj->RootNode;
|
|
va = root->Opcode;
|
|
|
|
if ((va & 0xff) == 1)
|
|
{
|
|
node = (ModelNode *) root->Data;
|
|
header = (ModelRwData_HeaderRecord *) modelGetNodeRwData(modelptr, root);
|
|
|
|
if (header->unk00 == 0)
|
|
{
|
|
jointnum = node->Opcode;
|
|
skeleton = modelptr->obj->Skeleton;
|
|
scale = modelptr->scale * modelptr->anim_translation_scale;
|
|
pos = D_80036254;
|
|
|
|
pos34.f[0] = header->unk34.x;
|
|
pos34.f[1] = header->unk34.y;
|
|
pos34.f[2] = header->unk34.z;
|
|
anglecur = header->unk30;
|
|
pos24.f[0] = header->unk24.x;
|
|
pos24.f[1] = header->unk24.y;
|
|
pos24.f[2] = header->unk24.z;
|
|
angle20 = header->unk20;
|
|
flag = header->unk01;
|
|
|
|
speed = modelptr->speed;
|
|
|
|
if (speed < 0.0f)
|
|
{
|
|
speed = -speed;
|
|
}
|
|
|
|
speed2 = modelptr->speed2;
|
|
|
|
if (speed2 < 0.0f)
|
|
{
|
|
speed2 = -speed2;
|
|
}
|
|
|
|
forward = 0;
|
|
|
|
if (framea <= frameb)
|
|
{
|
|
forward = 1;
|
|
}
|
|
|
|
#ifdef PORT
|
|
/* D156: this loop steps the root-motion accumulation one anim
|
|
* frame at a time from framea to frameb. If frameb arrives NaN or
|
|
* blown up -- a cutscene anim whose speed/timespeed math in
|
|
* modelTickAnim produced a huge/non-finite model->speed, which
|
|
* feeds `frame` and lands here -- then floorFloatToInt(frameb)
|
|
* yields garbage and the loop walks ~2^31 steps -> permanent hang
|
|
* (seen at the Facility outro cutscene: mainThread pinned here for
|
|
* minutes, frames=N frozen). The animation has at most anim->unk04
|
|
* frames, so a valid step is tiny; if frameb is non-finite or
|
|
* absurd, snap it to framea (loop then does ~0 iterations) and let
|
|
* the single modelConstrainOrWrapAnimFrame at :3211 clamp frameb
|
|
* to a real frame. N64 was fixed-timestep with bounded speeds and
|
|
* never reached this. `!(x > -1e6 && x < 1e6)` is also true for
|
|
* NaN. */
|
|
if (!(frameb > -1.0e6f && frameb < 1.0e6f))
|
|
{
|
|
frameb = (framea > -1.0e6f && framea < 1.0e6f) ? framea : 0.0f;
|
|
}
|
|
/* D311 (RULE-2-SIGNOFF 2026-09-19, release review: user approved
|
|
* retention as the stability-maximizing option; classified as a
|
|
* defensive guard against non-finite state in the same class as
|
|
* the accepted D156 guards -- it only fires on poison values that
|
|
* indicate an already-broken state, and snaps to a valid frame
|
|
* rather than altering any legitimate animation):
|
|
* the original guard only validated frameb. If framea
|
|
* (model->animframe1) is ALREADY poisoned, "frameb = framea" left
|
|
* both garbage and floorFloatToInt saturated the two near-equal
|
|
* huge floats to far-apart ints -- observed live in Caverns:
|
|
* framea=frameb=-1.67e31 -> curframe=-966612173, endframe=INT_MAX,
|
|
* ~3.1e9 loop iterations = permanent hang (D156 guard had fired
|
|
* upstream but passed the poison through). Validate framea too;
|
|
* with both snapped to 0 the loop does ~0 iterations and the
|
|
* animframe1 rewrite at :3447 heals the stored state next tick. */
|
|
if (!(framea > -1.0e6f && framea < 1.0e6f))
|
|
{
|
|
framea = (frameb > -1.0e6f && frameb < 1.0e6f) ? frameb : 0.0f;
|
|
}
|
|
#endif
|
|
|
|
if (forward)
|
|
{
|
|
curframe = floorFloatToInt(framea) + 1;
|
|
endframe = floorFloatToInt(frameb);
|
|
}
|
|
else
|
|
{
|
|
curframe = ceilFloatToInt(framea) - 1;
|
|
endframe = ceilFloatToInt(frameb);
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
if (forward)
|
|
{
|
|
if (endframe < curframe)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (curframe < endframe)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
framenum = modelConstrainOrWrapAnimFrame(curframe, modelptr->anim, modelptr->endframe);
|
|
modelptr->framea = framenum;
|
|
|
|
if (flag)
|
|
{
|
|
pos34.f[0] = pos24.f[0];
|
|
pos34.f[1] = pos24.f[1];
|
|
pos34.f[2] = pos24.f[2];
|
|
|
|
if (header->unk18 == 0.0f)
|
|
{
|
|
anglecur = angle20;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
angledelta = sub_GAME_7F06D3F4(jointnum, modelptr->gunhand, skeleton, modelptr->anim, framenum, &pos);
|
|
|
|
if (scale != 1.0f)
|
|
{
|
|
pos.x *= scale;
|
|
pos.y *= scale;
|
|
pos.z *= scale;
|
|
}
|
|
|
|
if (!forward)
|
|
{
|
|
pos.x = -pos.x;
|
|
pos.z = -pos.z;
|
|
|
|
if (0.0f < angledelta)
|
|
{
|
|
angledelta = M_TAU_F - angledelta;
|
|
}
|
|
}
|
|
|
|
cosangle = cosf(header->unk14);
|
|
sinangle = sinf(header->unk14);
|
|
|
|
pos34.f[0] += (pos.x * cosangle) + (pos.z * sinangle);
|
|
pos34.f[1] = pos.y;
|
|
pos34.f[2] += (-pos.x * sinangle) + (pos.z * cosangle);
|
|
|
|
if (header->unk18 == 0.0f)
|
|
{
|
|
anglecur += angledelta;
|
|
|
|
if (M_TAU_F <= anglecur)
|
|
{
|
|
anglecur -= M_TAU_F;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (forward)
|
|
{
|
|
curframe += 1;
|
|
}
|
|
else
|
|
{
|
|
curframe -= 1;
|
|
}
|
|
|
|
framenum = modelConstrainOrWrapAnimFrame(curframe, modelptr->anim, modelptr->endframe);
|
|
modelptr->frameb = framenum;
|
|
|
|
if (modelptr->frameb != modelptr->framea)
|
|
{
|
|
angledelta = sub_GAME_7F06D3F4(jointnum, modelptr->gunhand, skeleton, modelptr->anim, framenum, &pos);
|
|
flag = 1;
|
|
|
|
if (scale != 1.0f)
|
|
{
|
|
pos.x *= scale;
|
|
pos.y *= scale;
|
|
pos.z *= scale;
|
|
}
|
|
|
|
if (!forward)
|
|
{
|
|
pos.x = -pos.x;
|
|
pos.z = -pos.z;
|
|
|
|
if (0.0f < angledelta)
|
|
{
|
|
angledelta = M_TAU_F - angledelta;
|
|
}
|
|
}
|
|
|
|
cosangle = cosf(header->unk30);
|
|
sinangle = sinf(header->unk30);
|
|
|
|
if (g_ModelAnimMergingEnabled && modelptr->anim2 != NULL)
|
|
{
|
|
pos24.f[0] = (pos.z * sinangle) + (pos.x * cosangle);
|
|
pos24.f[2] = (pos.z * cosangle) + (-pos.x * sinangle);
|
|
|
|
if (0.0f < speed)
|
|
{
|
|
f32 t = modelptr->unk84 - (modelptr->playspeed / (speed * modelptr->unk88));
|
|
|
|
if (t < 0.0f)
|
|
{
|
|
t = 0.0f;
|
|
}
|
|
|
|
t = (modelptr->unk84 + t) * 0.5f;
|
|
|
|
v[0] = ((header->unk40.x - header->unk4c.x) * speed2) / speed;
|
|
v[2] = ((header->unk40.z - header->unk4c.z) * speed2) / speed;
|
|
|
|
pos24.f[0] += (v[0] - pos24.f[0]) * t;
|
|
pos24.f[2] += (v[2] - pos24.f[2]) * t;
|
|
}
|
|
else
|
|
{
|
|
pos24.f[0] += (header->unk40.x - header->unk4c.x) * modelptr->unk84;
|
|
pos24.f[2] += (header->unk40.z - header->unk4c.z) * modelptr->unk84;
|
|
}
|
|
|
|
pos24.f[0] += pos34.f[0];
|
|
pos24.f[2] += pos34.f[2];
|
|
pos24.f[1] = pos.y;
|
|
}
|
|
else
|
|
{
|
|
pos24.f[0] = (pos34.f[0] + (pos.f[0] * cosangle)) + (pos.f[2] * sinangle);
|
|
pos24.f[1] = pos.f[1];
|
|
pos24.f[2] = (pos34.f[2] - (pos.f[0] * sinangle)) + (pos.f[2] * cosangle);
|
|
}
|
|
|
|
if (0.0f < header->unk5c)
|
|
{
|
|
if (0.0f < speed)
|
|
{
|
|
inv = 1.0f / speed;
|
|
|
|
if (header->unk5c < inv)
|
|
{
|
|
inv = header->unk5c;
|
|
header->unk5c = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
header->unk5c -= inv;
|
|
}
|
|
|
|
angledelta += header->unk58 * inv;
|
|
|
|
if (angledelta < 0.0f)
|
|
{
|
|
angledelta += M_TAU_F;
|
|
}
|
|
else if (M_TAU_F <= angledelta)
|
|
{
|
|
angledelta -= M_TAU_F;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (header->unk18 == 0.0f)
|
|
{
|
|
angle20 = anglecur + angledelta;
|
|
|
|
if (M_TAU_F <= angle20)
|
|
{
|
|
angle20 -= M_TAU_F;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
header->unk34.x = pos34.f[0];
|
|
header->unk34.y = pos34.f[1];
|
|
header->unk34.z = pos34.f[2];
|
|
header->unk30 = anglecur;
|
|
header->unk24.x = pos24.f[0];
|
|
header->unk24.y = pos24.f[1];
|
|
header->unk24.z = pos24.f[2];
|
|
header->unk20 = angle20;
|
|
|
|
va = modelptr->framea;
|
|
vb = modelptr->frameb;
|
|
|
|
#ifdef PORT
|
|
/* D329 (RULE-2-SIGNOFF 2026-09-23, user approved): tick-
|
|
* granularity fix. The per-frame loop above only writes
|
|
* framea/frameb when floor(frame) crosses an integer. N64 GE
|
|
* renders at ~20-30 fps, so modelTickAnim folds 2-3 ticks into
|
|
* this call and any attack-speed advance (0.5-0.8/tick) crosses
|
|
* at once; the port at 60 fps folds 1 tick, which does not. A
|
|
* pair clamped equal by an earlier endframe (aim hold: both at
|
|
* ceil(H)) is then re-emitted stale after the endframe is raised
|
|
* (actor_fire_or_aim_at_target_update -> chrlvAttackActionRelated
|
|
* changes only the endframe) and the sub-frame progress below is
|
|
* discarded every call -> permanent pin (D318/D320). Only when
|
|
* the loop did not run, progress was requested, and the CURRENT
|
|
* endframe no longer blocks the next frame, re-derive frameb the
|
|
* way modelSetAnimFrame does, so the model advances at the rate
|
|
* the N64 does. Genuine holds (endframe still at the frame) and
|
|
* every >=2-tick call are untouched. */
|
|
/* GE_D329=0 disables (A/B only); GE_D329LOG=1 logs each trigger
|
|
* (capped). Both cached -- this is a per-model per-frame path. */
|
|
static s32 s_d329 = -1;
|
|
static s32 s_d329log = 0;
|
|
static s32 s_d329n = 0;
|
|
|
|
if (s_d329 < 0)
|
|
{
|
|
const char *e = getenv("GE_D329");
|
|
|
|
s_d329 = !(e && e[0] == '0');
|
|
s_d329log = (getenv("GE_D329LOG") != NULL);
|
|
}
|
|
|
|
if (s_d329 && (vb == va)
|
|
&& (forward ? ((endframe < floorFloatToInt(framea) + 1) && (framea < frameb))
|
|
: ((ceilFloatToInt(framea) - 1 < endframe) && (frameb < framea))))
|
|
{
|
|
s32 next = modelConstrainOrWrapAnimFrame(forward ? va + 1 : va - 1, modelptr->anim, modelptr->endframe);
|
|
|
|
if (next == (forward ? va + 1 : va - 1))
|
|
{
|
|
if (s_d329log && s_d329n < 2000)
|
|
{
|
|
extern s32 g_GlobalTimer;
|
|
extern s32 g_ClockTimer;
|
|
|
|
s_d329n++;
|
|
osSyncPrintf("D329: t=%d clk=%d model=%p va=%d next=%d framea=%.4f frameb=%.4f "
|
|
"endframe=%.4f speed=%.4f playspeed=%.4f\n",
|
|
(int) g_GlobalTimer, (int) g_ClockTimer, (void *) modelptr, (int) va,
|
|
(int) next, (double) framea, (double) frameb,
|
|
(double) modelptr->endframe, (double) modelptr->speed,
|
|
(double) modelptr->playspeed);
|
|
}
|
|
modelptr->frameb = next;
|
|
vb = next;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (vb == va)
|
|
{
|
|
modelptr->unk2c = 0.0f;
|
|
modelptr->animframe1 = va;
|
|
}
|
|
else if (forward)
|
|
{
|
|
modelptr->unk2c = frameb - (f32)endframe;
|
|
modelptr->animframe1 = (f32)va + modelptr->unk2c;
|
|
}
|
|
else
|
|
{
|
|
modelptr->unk2c = (f32)endframe - frameb;
|
|
modelptr->animframe1 = (f32)vb + (1.0f - modelptr->unk2c);
|
|
}
|
|
|
|
if (modelptr->anim2 != NULL)
|
|
{
|
|
curframe = floorFloatToInt(frame2a);
|
|
end2 = floorFloatToInt(frame2b);
|
|
|
|
if ((forward && (curframe < end2)) || (!forward && (end2 < curframe)))
|
|
{
|
|
if (header->unk02)
|
|
{
|
|
header->unk4c.y = header->unk40.y;
|
|
}
|
|
else
|
|
{
|
|
header->unk4c.y = header->unk34.y;
|
|
}
|
|
|
|
modelptr->frame2a = modelConstrainOrWrapAnimFrame(end2, modelptr->anim2, modelptr->unk6c);
|
|
framenum = modelConstrainOrWrapAnimFrame(end2 + 1, modelptr->anim2, modelptr->unk6c);
|
|
modelptr->frame2b = framenum;
|
|
|
|
sub_GAME_7F06D3F4(jointnum, modelptr->unk25, skeleton, modelptr->anim2, framenum, &pos);
|
|
|
|
if (scale != 1.0f)
|
|
{
|
|
pos.y *= scale;
|
|
}
|
|
|
|
header->unk40.y = pos.y;
|
|
header->unk02 = 1;
|
|
}
|
|
|
|
if (forward)
|
|
{
|
|
modelptr->unk5c = frame2b - (f32)end2;
|
|
modelptr->animframe2 = (f32)modelptr->frame2a + modelptr->unk5c;
|
|
}
|
|
else
|
|
{
|
|
modelptr->unk5c = 1.0f - (frame2b - (f32)end2);
|
|
modelptr->animframe2 = (f32)modelptr->frame2b + (1.0f - modelptr->unk5c);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
header->unk02 = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
modelSetAnimFrame2(modelptr, frameb, frame2b);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
modelSetAnimFrame2(modelptr, frameb, frame2b);
|
|
}
|
|
}
|
|
|
|
|
|
void modelTickAnim(struct Model *model, s32 numticks, s32 update_chrstuff)
|
|
{
|
|
f32 frame;
|
|
f32 frame2;
|
|
f32 animlast;
|
|
|
|
frame = model->animframe1;
|
|
frame2 = model->animframe2;
|
|
|
|
if (numticks > 0)
|
|
{
|
|
while (numticks > 0)
|
|
{
|
|
f32 playspeed;
|
|
f32 speed;
|
|
f32 limit;
|
|
f32 endframe;
|
|
f32 saved_newspeed;
|
|
f32 saved_oldspeed;
|
|
f32 saved_timespeed;
|
|
f32 saved_elapsespeed;
|
|
f32 loopframe;
|
|
|
|
if (model->unkb0 > 0.0f)
|
|
{
|
|
model->unkb4 += 1.0f;
|
|
|
|
if (model->unkb4 < model->unkb0)
|
|
{
|
|
model->playspeed = model->unkac + ((model->animrate - model->unkac) * model->unkb4) / model->unkb0;
|
|
}
|
|
else
|
|
{
|
|
model->unkb0 = 0.0f;
|
|
model->playspeed = model->animrate;
|
|
}
|
|
}
|
|
|
|
playspeed = model->playspeed;
|
|
|
|
if (model->unk88 > 0.0f)
|
|
{
|
|
model->unk8c += playspeed;
|
|
|
|
if (model->unk8c == 0.0f)
|
|
{
|
|
model->unk84 = 1.0f;
|
|
playspeed = model->playspeed;
|
|
}
|
|
else if (model->unk8c < model->unk88)
|
|
{
|
|
model->unk84 = (model->unk88 - model->unk8c) / model->unk88;
|
|
playspeed = model->playspeed;
|
|
}
|
|
else
|
|
{
|
|
model->unk88 = 0.0f;
|
|
model->unk84 = 0.0f;
|
|
model->anim2 = NULL;
|
|
playspeed = model->playspeed;
|
|
}
|
|
}
|
|
|
|
if (model->timespeed > 0.0f)
|
|
{
|
|
model->elapsespeed += playspeed;
|
|
|
|
if (model->elapsespeed < model->timespeed)
|
|
{
|
|
model->speed = model->oldspeed
|
|
+ ((model->newspeed - model->oldspeed) * model->elapsespeed)
|
|
/ model->timespeed;
|
|
playspeed = model->playspeed;
|
|
}
|
|
else
|
|
{
|
|
model->timespeed = 0.0f;
|
|
playspeed = model->playspeed;
|
|
model->speed = model->newspeed;
|
|
}
|
|
}
|
|
|
|
speed = model->speed;
|
|
|
|
#ifdef PORT
|
|
/* D243 M-183: clamp playspeed to a sane range during scripted
|
|
* camera modes when it exceeds a threshold. M-186 correction:
|
|
* this is NOT a D100/D140 struct-punning/pointer-widening issue
|
|
* -- Model is accessed everywhere by named field, no raw-offset
|
|
* alias exists here. The actual mechanism (still not fully root-
|
|
* caused, see findings.md D243): modelSetAnimation2 (line ~2720,
|
|
* unmodified decomp) never resets unkb0/unkb4/unkac/animrate on
|
|
* a scripted animation restart; a restart landing while unkb0 is
|
|
* stale-nonzero divides by it using a mismatched unkac/animrate
|
|
* pair from the previous animation, producing huge playspeed
|
|
* spikes (~388-410, vs. a legitimate max of ~2.0).
|
|
* M-187: was mistakenly gated on a diagnostic env var, so it
|
|
* never fired for a real player. Fixed to gate on the camera-mode test
|
|
* alone (gameScriptedCameraActive()) so it's actually active by
|
|
* default.
|
|
* M-189 (2026-09-18) found and fixed the ACTUAL root cause (a
|
|
* stale hardcoded 32-bit sizeof(Model) literal in bondview2.c's
|
|
* model-carving buffer, user-verified live): this clamp no longer
|
|
* fires in practice. It is retained as defense-in-depth against
|
|
* any future out-of-range playspeed reaching a scripted camera.
|
|
* RULE-2-SIGNOFF (2026-09-19, release review): ungated
|
|
* behavior-modifying guard in src/game, approved for retention
|
|
* by the user as the stability-maximizing option for v0.3.0;
|
|
* candidate for removal post-release once all cutscenes are
|
|
* re-verified clean without it (findings.md D243 / M-192).
|
|
* Threshold: 10.0 is way above any legitimate playspeed (normal
|
|
* is ~1.0, max observed in gameplay is ~2.0). */
|
|
extern int gameScriptedCameraActive(void);
|
|
if (gameScriptedCameraActive() && playspeed > 10.0f)
|
|
{
|
|
playspeed = 1.0f;
|
|
}
|
|
#endif
|
|
|
|
frame += playspeed * speed;
|
|
|
|
if (model->anim2 != NULL)
|
|
{
|
|
if (model->unk7c > 0.0f)
|
|
{
|
|
model->unk80 += playspeed;
|
|
|
|
if (model->unk80 < model->unk7c)
|
|
{
|
|
model->speed2 = model->unk78 + ((model->unk74 - model->unk78) * model->unk80) / model->unk7c;
|
|
playspeed = model->playspeed;
|
|
}
|
|
else
|
|
{
|
|
model->unk7c = 0.0f;
|
|
playspeed = model->playspeed;
|
|
model->speed2 = model->unk74;
|
|
}
|
|
}
|
|
|
|
if (frame2);
|
|
|
|
frame2 += playspeed * model->speed2;
|
|
}
|
|
|
|
if (model->animlooping)
|
|
{
|
|
animlast = model->anim->unk04 - 1;
|
|
endframe = model->endframe;
|
|
|
|
#ifdef PORT
|
|
/* D243 M-185: clamp corrupted endframe values during scripted
|
|
* camera modes, producing huge negative values like
|
|
* -604462909807314587353088.0 that cause the animation to loop
|
|
* indefinitely or behave unexpectedly. Clamp to a reasonable
|
|
* range (0-1000) to prevent this.
|
|
* M-186 correction: NOT a D100/D140 struct-punning/pointer-
|
|
* widening issue -- see the M-183 comment above for the
|
|
* corrected mechanism and for M-189's actual root-cause fix
|
|
* (stale 32-bit sizeof(Model) literal in bondview2.c); with that
|
|
* fix in place this clamp no longer fires in practice and is
|
|
* retained as defense-in-depth. RULE-2-SIGNOFF (2026-09-19,
|
|
* release review): approved for retention by the user for
|
|
* v0.3.0; candidate for removal post-release (findings.md D243 /
|
|
* M-192).
|
|
* M-187: was mistakenly gated on a diagnostic env var, so it
|
|
* never fired for a real player. Fixed to
|
|
* gate on the camera-mode test alone (gameScriptedCameraActive()). */
|
|
extern int gameScriptedCameraActive(void);
|
|
if (gameScriptedCameraActive() && (endframe < 0.0f || endframe > 1000.0f))
|
|
{
|
|
endframe = 100.0f;
|
|
}
|
|
#endif
|
|
|
|
if (endframe);
|
|
|
|
if (speed >= 0.0f)
|
|
{
|
|
limit = animlast;
|
|
loopframe = model->animloopframe;
|
|
|
|
if (endframe >= 0.0f && endframe < animlast)
|
|
{
|
|
limit = endframe;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
limit = model->animloopframe;
|
|
loopframe = animlast;
|
|
|
|
if (endframe >= 0.0f && endframe < animlast)
|
|
{
|
|
loopframe = endframe;
|
|
}
|
|
}
|
|
|
|
if ((speed >= 0.0f && frame >= limit) || (speed < 0.0f && frame <= limit))
|
|
{
|
|
saved_newspeed = model->newspeed;
|
|
saved_oldspeed = model->oldspeed;
|
|
saved_timespeed = model->timespeed;
|
|
saved_elapsespeed = model->elapsespeed;
|
|
|
|
if (update_chrstuff)
|
|
{
|
|
modelSetAnimFrame2WithChrStuff(model, model->animframe1, limit, 0.0f, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
modelSetAnimFrame2(model, limit, 0.0f);
|
|
}
|
|
|
|
modelSetAnimation(model, model->anim, model->gunhand, loopframe, model->speed, model->animloopmerge);
|
|
|
|
model->animlooping = 1;
|
|
model->endframe = endframe;
|
|
model->newspeed = saved_newspeed;
|
|
model->oldspeed = saved_oldspeed;
|
|
model->timespeed = saved_timespeed;
|
|
model->elapsespeed = saved_elapsespeed;
|
|
|
|
frame2 = frame;
|
|
frame = loopframe + frame - limit;
|
|
|
|
if (model->animflipfunc != 0)
|
|
{
|
|
#ifdef PORT
|
|
/* D99: `animflipfunc` is a 32-bit Model field holding a
|
|
* function pointer -- always bheadFlipAnimation (the
|
|
* only value modelSetAnimFlipFunction is ever handed:
|
|
* initBondDATAdefaults.c:198, bondhead.c:430, both for
|
|
* g_CurrentPlayer->model). The (int)truncation drops the
|
|
* 0x140000000 module base -> the indirect call jumps to
|
|
* 0x00010100 and faults. Same fix pattern as D92/unka0:
|
|
* the field stays a truncated-nonzero flag and the one
|
|
* call is made directly. */
|
|
{
|
|
extern void bheadFlipAnimation(void);
|
|
bheadFlipAnimation();
|
|
}
|
|
#else
|
|
((void (*)(void))model->animflipfunc)();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
numticks--;
|
|
}
|
|
|
|
#ifdef PORT
|
|
/* D156: if the per-tick `frame += playspeed * speed` accumulation
|
|
* went non-finite or absurd (bad cutscene anim speed/timespeed math),
|
|
* don't let it reach modelSetAnimFrame2* -- it would drive the
|
|
* frame-interpolation loop for ~2^31 steps (Facility outro hang) and
|
|
* poison model->animframe1 for every later frame. Fall back to the
|
|
* pre-loop frame; the anim just holds instead of hanging. */
|
|
if (!(frame > -1.0e6f && frame < 1.0e6f))
|
|
{
|
|
static s32 ge_d156_logged = 0;
|
|
if (!ge_d156_logged)
|
|
{
|
|
ge_d156_logged = 1;
|
|
fprintf(stderr,
|
|
"[D156] bad anim frame guarded: model=%p frame=%g animframe1=%g "
|
|
"speed=%g playspeed=%g newspeed=%g oldspeed=%g "
|
|
"timespeed=%g elapsespeed=%g unkb0=%g unk88=%g anim=%p\n",
|
|
(void *)model, (double)frame, (double)model->animframe1,
|
|
(double)model->speed, (double)model->playspeed,
|
|
(double)model->newspeed, (double)model->oldspeed,
|
|
(double)model->timespeed, (double)model->elapsespeed,
|
|
(double)model->unkb0, (double)model->unk88,
|
|
(void *)model->anim);
|
|
/* TEMP D294: hexdump the struct to identify the overwriter. */
|
|
{
|
|
const unsigned char *p = (const unsigned char *)model;
|
|
int i;
|
|
fprintf(stderr, "[D156] model=%p bytes:", (void *)model);
|
|
for (i = 0; i < 128; i++) { if ((i & 15) == 0) fprintf(stderr, "\n +%02x: ", i); fprintf(stderr, "%02x ", p[i]); }
|
|
fprintf(stderr, "\n");
|
|
}
|
|
fflush(stderr);
|
|
}
|
|
/* D311: heal the stored state BEFORE falling back to it -- if
|
|
* animframe1 is the poison source, "frame = animframe1" would just
|
|
* re-derive garbage every tick and pin the anim forever. Snapping
|
|
* it to 0 (a valid frame; modelConstrainOrWrapAnimFrame clamps
|
|
* further) lets normal accumulation resume next tick. */
|
|
if (!(model->animframe1 > -1.0e6f && model->animframe1 < 1.0e6f))
|
|
{
|
|
model->animframe1 = 0.0f;
|
|
}
|
|
frame = model->animframe1;
|
|
}
|
|
if (!(model->animframe2 > -1.0e6f && model->animframe2 < 1.0e6f))
|
|
{
|
|
model->animframe2 = 0.0f;
|
|
}
|
|
if (!(frame2 > -1.0e6f && frame2 < 1.0e6f))
|
|
{
|
|
frame2 = model->animframe2;
|
|
}
|
|
#endif
|
|
|
|
if (update_chrstuff)
|
|
{
|
|
if (model->anim2 != NULL)
|
|
{
|
|
modelSetAnimFrame2WithChrStuff(model, model->animframe1, frame, model->animframe2, frame2);
|
|
}
|
|
else
|
|
{
|
|
modelSetAnimFrame2WithChrStuff(model, model->animframe1, frame, 0.0f, 0.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (model->anim2 != NULL)
|
|
{
|
|
modelSetAnimFrame2(model, frame, frame2);
|
|
}
|
|
else
|
|
{
|
|
modelSetAnimFrame2(model, frame, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Model Type 1: 1Cycle No Secondary
|
|
* @param[in,out] renderdata append cycle, CC and RM to display List
|
|
*/
|
|
void modelApplyRenderModeType1(ModelRenderData *renderdata)
|
|
{
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_1CYCLE);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_AA_ZB_OPA_SURF2, G_RM_AA_ZB_OPA_SURF);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2);
|
|
}
|
|
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_MODULATEIA, G_CC_MODULATEIA);
|
|
}
|
|
|
|
/**
|
|
* @brief Model Type 3: GunLighting - Reduced Secondary Commands (guns)
|
|
This Type Uses Vertex Alpha for Secondary Surfaces and uses the FOG Alpha value for applying Fog/"Lighting".
|
|
* @param renderdata
|
|
* @param isPrimary
|
|
*/
|
|
void modelApplyRenderModeType3(ModelRenderData *renderdata, bool isPrimary)
|
|
{
|
|
if (renderdata->PropType == PROP_TYPE_VIEWER+1)
|
|
{
|
|
if (isPrimary)
|
|
{
|
|
u8 r, g, b, a;
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
|
|
r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
r = _SHIFTR(renderdata->envcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->envcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->envcolour.word, 8, 8);
|
|
a = 0xFF;
|
|
gDPSetEnvColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL0, ENVIRONMENT, SHADE_ALPHA, ENVIRONMENT, TEXEL0, ENVIRONMENT, SHADE, ENVIRONMENT, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_EXPLOSION+1)
|
|
{
|
|
if (isPrimary)
|
|
{
|
|
u8 r, g, b, a;
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
|
|
r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
r = _SHIFTR(renderdata->envcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->envcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->envcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->envcolour.word, 0, 8);
|
|
gDPSetEnvColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL0, ENVIRONMENT, SHADE_ALPHA, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_SMOKE+1)
|
|
{
|
|
if ((renderdata->envcolour.word & 0xFF) == 0)
|
|
{
|
|
if (isPrimary)
|
|
{
|
|
u8 r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
u8 g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
u8 b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
u8 a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
gDPSetEnvColor(renderdata->gdl++, 0xFF, 0xFF, 0xFF, 0xFF);
|
|
gDPSetPrimColor(renderdata->gdl++, 0, 0, 0, 0, 0, (renderdata->envcolour.word >> 8) & 0xFF);
|
|
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, PRIMITIVE);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (isPrimary)
|
|
{
|
|
u8 r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
u8 g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
u8 b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
u8 a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
gDPSetEnvColor(renderdata->gdl++, 0, 0, 0, renderdata->envcolour.word & 0xFF);
|
|
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, 1, 0, SHADE, ENVIRONMENT, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_TEX_EDGE2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_TEX_EDGE2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gDPSetPrimColor(renderdata->gdl++, 0, 0, 0, 0, 0, (renderdata->envcolour.word >> 8) & 0xFF);
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, SHADE, ENVIRONMENT, TEXEL0, 0, COMBINED, 0, SHADE, 0, 1, 0, PRIMITIVE, COMBINED);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_TEX_EDGE2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_TEX_EDGE2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_CHR+1)
|
|
{
|
|
if (isPrimary)
|
|
{
|
|
u8 r = _SHIFTR(renderdata->envcolour.word, 24, 8);
|
|
u8 g = _SHIFTR(renderdata->envcolour.word, 16, 8);
|
|
u8 b = _SHIFTR(renderdata->envcolour.word, 8, 8);
|
|
u8 a = _SHIFTR(renderdata->envcolour.word, 0, 8);
|
|
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_WEAPON+1)
|
|
{
|
|
u8 r, g, b, a;
|
|
if (isPrimary)
|
|
{
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
|
|
r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
a = renderdata->envcolour.word & 0xFF;
|
|
|
|
if (a < 255)
|
|
{
|
|
gDPSetEnvColor(renderdata->gdl++, 0xFF, 0xFF, 0xFF, a);
|
|
|
|
if (renderdata->envcolour.word & 0xFF00)
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, 1, SHADE, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0);
|
|
}
|
|
else
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, 1, 0, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
}
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
a = renderdata->envcolour.word & 0xFF;
|
|
|
|
if (a < 255)
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL0, 0, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0);
|
|
}
|
|
else
|
|
{
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (isPrimary)
|
|
{
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_PASS, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_PASS, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_PASS, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_PASS, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Model Type 4: Normal Fog/Lighting object
|
|
This Type Uses Vertex Alpha for Secondary Surfaces and uses the FOG Alpha value for applying Fog/"Lighting".
|
|
* @param renderdata
|
|
* @param isPrimary Type of DisplayList
|
|
*/
|
|
void modelApplyRenderModeType4(ModelRenderData *renderdata, bool isPrimary)
|
|
{
|
|
if (renderdata->PropType == PROP_TYPE_VIEWER+1)
|
|
{
|
|
u8 r, g, b, a;
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
|
|
r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
r = _SHIFTR(renderdata->envcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->envcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->envcolour.word, 8, 8);
|
|
a = 0xFF;
|
|
gDPSetEnvColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL0, ENVIRONMENT, SHADE_ALPHA, ENVIRONMENT, TEXEL0, ENVIRONMENT, SHADE, ENVIRONMENT, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED);
|
|
|
|
if (isPrimary)
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_EXPLOSION+1)
|
|
{
|
|
u8 r, g, b, a;
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
|
|
r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
r = _SHIFTR(renderdata->envcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->envcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->envcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->envcolour.word, 0, 8);
|
|
gDPSetEnvColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL0, ENVIRONMENT, SHADE_ALPHA, ENVIRONMENT, TEXEL0, 0, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_SMOKE+1)
|
|
{
|
|
if ((renderdata->envcolour.word & 0xFF) == 0)
|
|
{
|
|
u8 r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
u8 g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
u8 b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
u8 a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
gDPSetEnvColor(renderdata->gdl++, 0xFF, 0xFF, 0xFF, 0xFF);
|
|
gDPSetPrimColor(renderdata->gdl++, 0, 0, 0, 0, 0, ((renderdata->envcolour.word >> 8 ) & 0xFF));
|
|
|
|
if (isPrimary)
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, PRIMITIVE);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, PRIMITIVE);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
u8 r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
u8 g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
u8 b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
u8 a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
gDPSetEnvColor(renderdata->gdl++, 0, 0, 0, renderdata->envcolour.word & 0xFF);
|
|
|
|
if (isPrimary)
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, 1, 0, SHADE, ENVIRONMENT, COMBINED, 0, SHADE, 0, 0, 0, 0, COMBINED);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_TEX_EDGE2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_TEX_EDGE2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gDPSetPrimColor(renderdata->gdl++, 0, 0, 0, 0, 0, (renderdata->envcolour.word >> 8) & 0xFF);
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, SHADE, ENVIRONMENT, TEXEL0, 0, COMBINED, 0, SHADE, 0, 1, 0, PRIMITIVE, COMBINED);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_TEX_EDGE2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_TEX_EDGE2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_CHR+1)
|
|
{
|
|
u8 r = _SHIFTR(renderdata->envcolour.word, 24, 8);
|
|
u8 g = _SHIFTR(renderdata->envcolour.word, 16, 8);
|
|
u8 b = _SHIFTR(renderdata->envcolour.word, 8, 8);
|
|
u8 a = _SHIFTR(renderdata->envcolour.word, 0, 8);
|
|
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
|
|
if (isPrimary)
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
else if (renderdata->PropType == PROP_TYPE_WEAPON+1)
|
|
{
|
|
u8 r, g, b, a;
|
|
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
|
|
r = _SHIFTR(renderdata->fogcolour.word, 24, 8);
|
|
g = _SHIFTR(renderdata->fogcolour.word, 16, 8);
|
|
b = _SHIFTR(renderdata->fogcolour.word, 8, 8);
|
|
a = _SHIFTR(renderdata->fogcolour.word, 0, 8);
|
|
gDPSetFogColor(renderdata->gdl++, r, g, b, a);
|
|
|
|
a = renderdata->envcolour.word & 0xFF;
|
|
|
|
if (a < 255)
|
|
{
|
|
gDPSetEnvColor(renderdata->gdl++, 0xFF, 0xFF, 0xFF, a);
|
|
|
|
if (isPrimary)
|
|
{
|
|
if (renderdata->envcolour.word & 0xFF00) //apply inverse vertex alpha if any
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, 1, SHADE, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0);
|
|
}
|
|
else
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, 1, 0, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gDPSetCombineLERP(renderdata->gdl++, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL0, 0, ENVIRONMENT, 0, COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
}
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
gDPSetFogColor(renderdata->gdl++, 0xFF, 0xFF, 0xFF, 0x00);
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
|
|
if (isPrimary)
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_OPA_SURF2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_ZB_XLU_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_FOG_PRIM_A, G_RM_AA_XLU_SURF2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Model Type 2: 2Cycle No Secondary
|
|
* @param[in,out] renderdata append cycle, CC and RM to display List
|
|
*/
|
|
void modelApplyRenderModeType2(ModelRenderData *renderdata)
|
|
{
|
|
gDPPipeSync(renderdata->gdl++);
|
|
gDPSetCycleType(renderdata->gdl++, G_CYC_2CYCLE);
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_PASS, G_RM_AA_ZB_OPA_SURF2);
|
|
}
|
|
else
|
|
{
|
|
gDPSetRenderMode(renderdata->gdl++, G_RM_PASS, G_RM_AA_OPA_SURF2);
|
|
}
|
|
|
|
gDPSetCombineMode(renderdata->gdl++, G_CC_TRILERP, G_CC_MODULATEIA2);
|
|
}
|
|
|
|
|
|
void modelApplyCullMode(ModelRenderData *renderdata)
|
|
{
|
|
if (renderdata->cullmode == CULLMODE_NONE)
|
|
{
|
|
gSPClearGeometryMode(renderdata->gdl++, G_CULL_BOTH);
|
|
}
|
|
else if (renderdata->cullmode == CULLMODE_FRONT)
|
|
{
|
|
gSPSetGeometryMode(renderdata->gdl++, G_CULL_FRONT);
|
|
}
|
|
else if (renderdata->cullmode == CULLMODE_BACK)
|
|
{
|
|
gSPSetGeometryMode(renderdata->gdl++, G_CULL_BACK);
|
|
}
|
|
}
|
|
|
|
|
|
void modelRenderNodeGundl(ModelRenderData* renderdata, ModelNode* arg1)
|
|
{
|
|
ModelRoData_DisplayListRecord* rodata = &arg1->Data->DisplayList;
|
|
|
|
if (renderdata->unk18 == 0)
|
|
{
|
|
if ((renderdata->flags & 1) && rodata->Primary)
|
|
{
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(rodata->BaseAddr));
|
|
|
|
if (renderdata->cullmode)
|
|
{
|
|
modelApplyCullMode(renderdata);
|
|
}
|
|
|
|
if (rodata->ModelType == 1)
|
|
{
|
|
modelApplyRenderModeType1(renderdata);
|
|
}
|
|
else if (rodata->ModelType == 3)
|
|
{
|
|
modelApplyRenderModeType3(renderdata, 1);
|
|
}
|
|
else if (rodata->ModelType == 4)
|
|
{
|
|
modelApplyRenderModeType4(renderdata, 1);
|
|
}
|
|
else if (rodata->ModelType == 2)
|
|
{
|
|
modelApplyRenderModeType2(renderdata);
|
|
}
|
|
|
|
gSPDisplayList(renderdata->gdl++, rodata->Primary);
|
|
|
|
if ((rodata->ModelType == 3) && rodata->Secondary)
|
|
{
|
|
modelApplyRenderModeType3(renderdata, 0);
|
|
gSPDisplayList(renderdata->gdl++, rodata->Secondary);
|
|
}
|
|
}
|
|
|
|
if ((renderdata->flags & 2) && rodata->Primary && (rodata->ModelType == 4) && rodata->Secondary)
|
|
{
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(rodata->BaseAddr));
|
|
|
|
if (renderdata->cullmode)
|
|
{
|
|
modelApplyCullMode(renderdata);
|
|
}
|
|
|
|
modelApplyRenderModeType4(renderdata, 0);
|
|
gSPDisplayList(renderdata->gdl++, rodata->Secondary);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
A1 is primary = 1, secondary = 0
|
|
Inside the T8 or whatever temporary register indicates gun or not gun (0 = gun, or UseZ = 1), for different render mode
|
|
|
|
Bool UseZ //guns = false
|
|
Bool
|
|
|
|
|
|
Model Type 0: NoSetup.
|
|
type 0 Has No DL Setup and will use whaterver is currently set.
|
|
|
|
Model Type 1: 1Cycle No Sec
|
|
E700000000000000 pipesync()
|
|
BA00140200000000 CycleType(1c)
|
|
if UseZ
|
|
B900031D00552078 SetRendermode(AA_ZB_OPA_1) //cin ain cmem amem
|
|
else
|
|
B900031D00552048 SetRendermode(AA_OPA_1) //cin ain cmem amem
|
|
end if
|
|
FC121824FF33FFFF SetCombine(MODULATERGBA)
|
|
No Secondary
|
|
|
|
Model Type 2: 2Cycle No Sec
|
|
E700000000000000 pipesync()
|
|
BA00140200100000 CycleType(2c)
|
|
if UseZ
|
|
B900031D0C192078 SetRendermode(AA_ZB_OPA_2) // cin 0 cin 1 //colour only
|
|
else
|
|
B900031D0C192048 SetRendermode(AA_OPA_2) // cin 0 cin 1 //colour only
|
|
end if
|
|
FC26A0041F1093FF SetCombine(TRILERP, MODULATERGBA)
|
|
No Secondary
|
|
Model Type 3: GunLighting - Reduced Secondary Commands (guns)
|
|
This Type Uses Vertex Alpha for Secondary Surfaces and uses the FOG Alpha value for applying Fog/"Lighting".
|
|
Primary
|
|
E700000000000000 pipesync()
|
|
BA00140200100000 CycleType(2c)
|
|
F800000000000026 SetFogColor(0,0,0,38)
|
|
if dltype = full
|
|
if guard
|
|
FB0000005A0000FF SetEnvColor(90,0,0,255)
|
|
FC1598045FFEDBF8 SetCombine(((Texel0-Env)*ShadeA+Env)
|
|
((Texel0-Env)*Shade+Env),
|
|
MODULATERGB_DECALA)
|
|
else if prop
|
|
FB000000FFFFFFFF SetEnvColor(255,255,255,255)
|
|
FA00000000000000 SetPrimColor(0,0,0,0)
|
|
FC26A0041F1093FB SetCombine(TRILERP, MODULATERGB_ADDPRIM_A)
|
|
endif
|
|
else
|
|
FC26A0041F1093FF SetCombine(TRILERP, MODULATERGBA)
|
|
endif
|
|
if UseZ
|
|
B900031DC4112078 SetRendermode(AA_ZB_OPA_StanFOG_2)
|
|
else
|
|
B900031DC4112048 SetRendermode(AA_OPA_StanFOG_2) //acvg
|
|
endif
|
|
|
|
Secondary
|
|
if UseZ
|
|
B900031DC41049D8 SetRendermode(AA_Zcmp_XLU_StanFOG_2)
|
|
else
|
|
B900031DC41041C8 SetRendermode(AA_OPA_StanFOG_2)//FcBl ClrOnCvg
|
|
endif
|
|
|
|
Model Type 4: Normal Fog/Lighting object
|
|
This Type Uses Vertex Alpha for Secondary Surfaces and uses the FOG Alpha value for applying Fog/"Lighting".
|
|
Primary
|
|
E700000000000000 pipesync()
|
|
BA00140200100000 CycleType(2c)
|
|
F800000000000026 SetFogColor(0,0,0,38)
|
|
if dltype = full
|
|
if guard
|
|
FB0000005A0000FF SetEnvColor(90,0,0,255)
|
|
FC1598045FFEDBF8 SetCombine(((Texel0-Env)*ShadeA+Env)
|
|
((Texel0-Env)*Shade+Env),
|
|
MODULATERGB_DECALA)
|
|
else if prop
|
|
FB000000FFFFFFFF SetEnvColor(255,255,255,255)
|
|
FA00000000000000 SetPrimColor(0,0,0,0)
|
|
FC26A0041F1093FB SetCombine(TRILERP, MODULATERGB_ADDPRIM_A)
|
|
endif
|
|
else
|
|
FC26A0041F1093FF SetCombine(TRILERP, MODULATERGBA)
|
|
endif
|
|
if UseZ
|
|
B900031DC4112078 SetRendermode(AA_ZB_OPA_StanFOG_2)
|
|
else
|
|
B900031DC4112048 SetRendermode(AA_OPA_StanFOG_2) //acvg
|
|
endif
|
|
|
|
Secondary
|
|
E700000000000000 pipesync()
|
|
BA00140200100000 CycleType(2c)
|
|
F800000000000026 SetFogColor(0,0,0,38)
|
|
if dltype = full
|
|
if guard
|
|
FB0000005A0000FF SetEnvColor(90,0,0,255)
|
|
FC1598045FFEDBF8 SetCombine(((Texel0-Env)*ShadeA+Env)
|
|
((Texel0-Env)*Shade+Env),
|
|
MODULATERGB_DECALA)
|
|
else if prop
|
|
FB000000FFFFFFFF SetEnvColor(255,255,255,255)
|
|
FA00000000000000 SetPrimColor(0,0,0,0)
|
|
FC26A0041F1093FB SetCombine(TRILERP, MODULATERGB_ADDPRIM_A)
|
|
endif
|
|
else
|
|
FA00000000000000 SetPrimColor(0,0,0,0)
|
|
FC26A0041F1093FB SetCombine(TRILERP, MODULATERGB_ADDPRIM_A)
|
|
endif
|
|
if UseZ
|
|
B900031DC41049D8 SetRendermode(AA_Zcmp_XLU_StanFOG_2)
|
|
else
|
|
B900031DC41041C8 SetRendermode(AA_OPA_StanFOG_2)//FcBl ClrOnCvg
|
|
endif
|
|
*/
|
|
|
|
/**
|
|
* 7F072A0C
|
|
* DisplayList Setups Depend on Object Type, Prop Guard or Gun.
|
|
These are applied to each part of an object at runtime and can be overridden. loading the next part will use these values once more.
|
|
GeometryMode is not in setup and is persistent accross parts.
|
|
*/
|
|
void modelRenderNodeDl(ModelRenderData *renderdata, Model *model, ModelNode *node)
|
|
{
|
|
union ModelRoData *rodata = node->Data;
|
|
|
|
if (!renderdata->unk18)
|
|
{
|
|
if (renderdata->flags & 1)
|
|
{
|
|
union ModelRwData *rwdata = modelGetNodeRwData(model, node);
|
|
|
|
if (rwdata->DisplayListCollisions.gdl)
|
|
{
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(rodata->DisplayListCollisions.BaseAddr));
|
|
|
|
if (renderdata->cullmode)
|
|
{
|
|
modelApplyCullMode(renderdata);
|
|
}
|
|
|
|
if (rodata->DisplayListCollisions.ModelType == 1)
|
|
{
|
|
modelApplyRenderModeType1(renderdata);
|
|
}
|
|
else if (rodata->DisplayListCollisions.ModelType == 3)
|
|
{
|
|
modelApplyRenderModeType3(renderdata, TRUE);
|
|
}
|
|
else if (rodata->DisplayListCollisions.ModelType == 4)
|
|
{
|
|
modelApplyRenderModeType4(renderdata, TRUE);
|
|
}
|
|
else if (rodata->DisplayListCollisions.ModelType == 2)
|
|
{
|
|
modelApplyRenderModeType2(renderdata);
|
|
}
|
|
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_VTX, osVirtualToPhysical(rwdata->DisplayListCollisions.Vertices));
|
|
|
|
gSPDisplayList(renderdata->gdl++, rwdata->DisplayListCollisions.gdl);
|
|
|
|
if (rodata->DisplayListCollisions.ModelType == 3 && rodata->DisplayListCollisions.Secondary)
|
|
{
|
|
modelApplyRenderModeType3(renderdata, FALSE);
|
|
gSPDisplayList(renderdata->gdl++, rodata->DisplayListCollisions.Secondary);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (renderdata->flags & 2)
|
|
{
|
|
union ModelRwData *rwdata = modelGetNodeRwData(model, node);
|
|
|
|
if (rwdata->DisplayListCollisions.gdl && rodata->DisplayListCollisions.ModelType == 4 && rodata->DisplayListCollisions.Secondary)
|
|
{
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(rodata->DisplayListCollisions.BaseAddr));
|
|
|
|
if (renderdata->cullmode)
|
|
{
|
|
modelApplyCullMode(renderdata);
|
|
}
|
|
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_VTX, osVirtualToPhysical(rwdata->DisplayListCollisions.Vertices));
|
|
|
|
modelApplyRenderModeType4(renderdata, FALSE);
|
|
|
|
gSPDisplayList(renderdata->gdl++, rodata->DisplayListCollisions.Secondary);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void sub_GAME_7F072C10(ModelRenderData *param_1, struct Model *param_2, struct ModelNode *param_3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* Star gunfire is a muzzle flash in a first person perspective, where the
|
|
* muzzle flash has 3 or 4 "arms" that flare out from the main body.
|
|
*
|
|
* This function reads vertices from the model definition, tweaks them randomly,
|
|
* writes them to a newly allocated vertices table and queues the node's
|
|
* displaylist to the renderdata's DL.
|
|
*/
|
|
void dorottex(ModelRenderData *renderdata, ModelNode *node)
|
|
{
|
|
if (renderdata->unk18 == 0 && (renderdata->flags & 2))
|
|
{
|
|
|
|
ModelRoData_DisplayListPrimaryRecord *rodata = &node->Data->DisplayListPrimary;
|
|
s32 i;
|
|
|
|
if (rodata->Primary)
|
|
{
|
|
Vertex *src;
|
|
Vertex *dst;
|
|
|
|
src = (Vertex *) rodata->Vertices;
|
|
|
|
#ifndef VERSION_EU
|
|
if (vtxallocator != NULL)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
osSyncPrintf("dorottex: no vtx allocator!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
dst = vtxallocator(rodata->numVertices * 4);
|
|
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_VTX, osVirtualToPhysical(dst));
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(rodata->BaseAddr));
|
|
|
|
gDPSetFogColor(renderdata->gdl++, 0x00, 0x00, 0x00, 0x00);
|
|
gSPDisplayList(renderdata->gdl++, rodata->Primary);
|
|
|
|
for (i = 0; i < rodata->numVertices; i++)
|
|
{
|
|
u16 rand1 = (randomGetNext() << 10) & 0xffff;
|
|
s32 s4 = ((coss(rand1) << 5) * 181) >> 18;
|
|
s32 s3 = ((sins(rand1) << 5) * 181) >> 18;
|
|
s32 s1 = (u32)randomGetNext() >> 31;
|
|
s32 mult = 0x10000 - (randomGetNext() & 0x3fff);
|
|
s32 corner1 = 0x200 + s3;
|
|
s32 corner2 = 0x200 - s3;
|
|
s32 corner3 = 0x200 - s4;
|
|
s32 corner4 = 0x200 + s4;
|
|
|
|
dst[0] = src[0];
|
|
dst[1] = src[1];
|
|
dst[2] = src[2];
|
|
dst[3] = src[3];
|
|
|
|
dst[0].s = corner3;
|
|
dst[0].t = corner2;
|
|
dst[0].coord.x = (src[(s1 + 0) % 4].coord.x * mult) >> 16;
|
|
dst[0].coord.y = (src[(s1 + 0) % 4].coord.y * mult) >> 16;
|
|
dst[0].coord.z = (src[(s1 + 0) % 4].coord.z * mult) >> 16;
|
|
|
|
dst[1].s = corner1;
|
|
dst[1].t = corner3;
|
|
dst[1].coord.x = (src[(s1 + 1) % 4].coord.x * mult) >> 16;
|
|
dst[1].coord.y = (src[(s1 + 1) % 4].coord.y * mult) >> 16;
|
|
dst[1].coord.z = (src[(s1 + 1) % 4].coord.z * mult) >> 16;
|
|
|
|
dst[2].s = corner4;
|
|
dst[2].t = corner1;
|
|
dst[2].coord.x = (src[(s1 + 2) % 4].coord.x * mult) >> 16;
|
|
dst[2].coord.y = (src[(s1 + 2) % 4].coord.y * mult) >> 16;
|
|
dst[2].coord.z = (src[(s1 + 2) % 4].coord.z * mult) >> 16;
|
|
|
|
dst[3].s = corner2;
|
|
dst[3].t = corner4;
|
|
dst[3].coord.x = (src[(s1 + 3) % 4].coord.x * mult) >> 16;
|
|
dst[3].coord.y = (src[(s1 + 3) % 4].coord.y * mult) >> 16;
|
|
dst[3].coord.z = (src[(s1 + 3) % 4].coord.z * mult) >> 16;
|
|
|
|
src += 4;
|
|
dst += 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void sub_GAME_7F073038(ModelRenderData *renderdata, struct sImageTableEntry *tconfig, s32 arg2)
|
|
{
|
|
texSelect(&renderdata->gdl, tconfig, arg2, renderdata->zbufferenabled, 2);
|
|
}
|
|
|
|
|
|
void sub_GAME_7F07306C(s32 param_1,struct Model *param_2,struct ModelNode *param_3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
void dotube(ModelRenderData* renderdata, Model* model, ModelNode* node)
|
|
{
|
|
s32 rw_index_sel;
|
|
s32 rw_index_sel2;
|
|
s32 c_entry_count;
|
|
struct ModelRoData_Op07Record *rodata2;
|
|
s32 c_entry2_count;
|
|
s32 c_entry_index;
|
|
s32 renderpos_index;
|
|
ModelNode *node_from_07;
|
|
struct ModelRoData_Child *c_entry2;
|
|
Vertex *vtx2;
|
|
u8 *entry2_04;
|
|
s32 unused1;
|
|
bool swap_order;
|
|
struct ModelRoData_Op07Record *rodata;
|
|
struct ModelRoData_Child *c_entry;
|
|
struct ModelRwData_Op07Record *rwdata;
|
|
Vertex *vtx_10;
|
|
Vertex *vtx_10_2;
|
|
RenderPosView *render_pos2;
|
|
RenderPosView *render_pos;
|
|
s32 rw_index2;
|
|
s32 rw_index;
|
|
Vertex *vtx1;
|
|
struct ModelRwData_Op07Record *rwdata2;
|
|
u8 *entry_04;
|
|
s32 unused2;
|
|
s32 renderpos_index2;
|
|
s32 unused3;
|
|
|
|
rodata = &node->Data->Op07;
|
|
rwdata = &modelGetNodeRwData(model, node)->Op07;
|
|
|
|
if (rodata->unk00 != NULL)
|
|
{
|
|
node_from_07 = rodata->unk00;
|
|
}
|
|
else
|
|
{
|
|
node_from_07 = rodata->unk04;
|
|
}
|
|
|
|
rodata2 = &node_from_07->Data->Op07;
|
|
rwdata2 = &modelGetNodeRwData(model, node_from_07)->Op07;
|
|
swap_order = 1;
|
|
|
|
if (renderdata->flags & 1)
|
|
{
|
|
renderpos_index2 = modelFindNodeMtxIndex(node, 0);
|
|
render_pos2 = &model->render_pos[renderpos_index2];
|
|
rw_index = rwdata->index;
|
|
rw_index2 = rwdata2->index;
|
|
c_entry = &rodata->Children[rw_index];
|
|
|
|
if (rodata->unk00 != NULL)
|
|
{
|
|
rw_index_sel = rw_index2;
|
|
rw_index_sel2 = rw_index;
|
|
renderpos_index = modelFindNodeMtxIndex(node, 0x200);
|
|
}
|
|
else
|
|
{
|
|
rw_index_sel = rw_index;
|
|
rw_index_sel2 = rw_index2;
|
|
renderpos_index = modelFindNodeMtxIndex(rodata->unk04, 0x200);
|
|
}
|
|
|
|
render_pos = &model->render_pos[renderpos_index];
|
|
|
|
c_entry_index = ((rw_index_sel2 - rw_index_sel) + rodata->NumChildren) % rodata->NumChildren;
|
|
|
|
if ((c_entry_index >= 2) && (c_entry_index < 7))
|
|
{
|
|
if (c_entry_index < 4)
|
|
{
|
|
c_entry_index = ((c_entry_index / 2) + rw_index_sel + rodata->NumChildren) % rodata->NumChildren;
|
|
}
|
|
else
|
|
{
|
|
c_entry_index = ((rw_index_sel - ((8 - c_entry_index) / 2)) + rodata->NumChildren) % rodata->NumChildren;
|
|
swap_order = 0;
|
|
}
|
|
}
|
|
else if ((c_entry_index >= 0xA) && (c_entry_index < 0xF))
|
|
{
|
|
if (c_entry_index >= 0xD)
|
|
{
|
|
c_entry_index = ((rw_index_sel - ((0x10 - c_entry_index) / 2)) + rodata->NumChildren) % rodata->NumChildren;
|
|
}
|
|
else
|
|
{
|
|
c_entry_index = (((c_entry_index - 8) / 2) + rw_index_sel + rodata->NumChildren) % rodata->NumChildren;
|
|
swap_order = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((c_entry_index >= 7) && (c_entry_index < 0xA))
|
|
{
|
|
swap_order = 0;
|
|
}
|
|
c_entry_index = rw_index_sel;
|
|
}
|
|
|
|
entry_04 = c_entry->unk04;
|
|
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(rodata->BaseAddr));
|
|
|
|
for (c_entry_count = c_entry->NumEntries; c_entry_count > 0; c_entry_count--)
|
|
{
|
|
switch (*entry_04)
|
|
{
|
|
case MODELNODE_CHILD_VTX:
|
|
{
|
|
struct ModelRoData_Child_Vtx* child_vtx = ((struct ModelRoData_Child_Vtx*)entry_04);
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (vtxallocator == NULL)
|
|
{
|
|
osSyncPrintf("dotube: no vtx allocator!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
vtx1 = vtxallocator(2);
|
|
vtx2 = &vtx1[1];
|
|
|
|
*vtx1 = rodata->Vertices[child_vtx->VtxIndex];
|
|
*vtx2 = rodata->Vertices[child_vtx->VtxIndex+1];
|
|
|
|
if (rodata->unk04 != NULL)
|
|
{
|
|
c_entry2 = &rodata->Children[c_entry_index];
|
|
entry2_04 = c_entry2->unk04;
|
|
|
|
for (c_entry2_count = c_entry2->NumEntries; c_entry2_count > 0; c_entry2_count--)
|
|
{
|
|
struct ModelRoData_Child_Vtx* entry2_04_child = ((struct ModelRoData_Child_Vtx*)entry2_04);
|
|
if (entry2_04_child->Type == (u8) 1) {
|
|
vtx_10 = &rodata->Vertices[entry2_04_child->VtxIndex];
|
|
vtx_10_2 = vtx_10+1;
|
|
|
|
vtx1->coord.AsArray[0] = vtx_10->coord.AsArray[0];
|
|
vtx1->coord.AsArray[1] = vtx_10->coord.AsArray[1];
|
|
vtx1->coord.AsArray[2] = vtx_10->coord.AsArray[2];
|
|
|
|
vtx2->coord.AsArray[0] = vtx_10_2->coord.AsArray[0];
|
|
vtx2->coord.AsArray[1] = vtx_10_2->coord.AsArray[1];
|
|
vtx2->coord.AsArray[2] = vtx_10_2->coord.AsArray[2];
|
|
break;
|
|
}
|
|
|
|
switch (*entry2_04)
|
|
{
|
|
case MODELNODE_CHILD_VTX:
|
|
entry2_04 += sizeof(struct ModelRoData_Child_Vtx);
|
|
break;
|
|
case MODELNODE_CHILD_IMAGE:
|
|
entry2_04 += sizeof(struct ModelRoData_Child_Image);
|
|
break;
|
|
case MODELNODE_CHILD_TRI:
|
|
entry2_04 += sizeof(struct ModelRoData_Child_Tri);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
c_entry2 = &rodata2->Children[c_entry_index];
|
|
entry2_04 = c_entry2->unk04;
|
|
|
|
for (c_entry2_count = c_entry2->NumEntries; c_entry2_count > 0; c_entry2_count--)
|
|
{
|
|
struct ModelRoData_Child_Vtx* entry2_04_child = ((struct ModelRoData_Child_Vtx*)entry2_04);
|
|
if (entry2_04_child->Type == (u8) 1)
|
|
{
|
|
vtx_10 = &rodata2->Vertices[entry2_04_child->VtxIndex];
|
|
vtx_10_2 = vtx_10 + 1;
|
|
if (swap_order != 0)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
if (vtx_10->coord.AsArray);
|
|
#endif
|
|
vtx1->coord.AsArray[0] = vtx_10_2->coord.AsArray[0];
|
|
vtx1->coord.AsArray[1] = vtx_10_2->coord.AsArray[1];
|
|
vtx1->coord.AsArray[2] = vtx_10_2->coord.AsArray[2];
|
|
|
|
vtx2->coord.AsArray[0] = vtx_10->coord.AsArray[0];
|
|
vtx2->coord.AsArray[1] = vtx_10->coord.AsArray[1];
|
|
vtx2->coord.AsArray[2] = vtx_10->coord.AsArray[2];
|
|
}
|
|
else
|
|
{
|
|
vtx1->coord.AsArray[0] = vtx_10->coord.AsArray[0];
|
|
vtx1->coord.AsArray[1] = vtx_10->coord.AsArray[1];
|
|
vtx1->coord.AsArray[2] = vtx_10->coord.AsArray[2];
|
|
|
|
vtx2->coord.AsArray[0] = vtx_10_2->coord.AsArray[0];
|
|
vtx2->coord.AsArray[1] = vtx_10_2->coord.AsArray[1];
|
|
vtx2->coord.AsArray[2] = vtx_10_2->coord.AsArray[2];
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (*entry2_04)
|
|
{
|
|
case MODELNODE_CHILD_VTX:
|
|
entry2_04 += sizeof(struct ModelRoData_Child_Vtx);
|
|
break;
|
|
|
|
case MODELNODE_CHILD_IMAGE:
|
|
entry2_04 += sizeof(struct ModelRoData_Child_Image);
|
|
break;
|
|
|
|
case MODELNODE_CHILD_TRI:
|
|
entry2_04 += sizeof(struct ModelRoData_Child_Tri);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gSPMatrix(renderdata->gdl++, osVirtualToPhysical(render_pos), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
|
gSPVertex(renderdata->gdl++, osVirtualToPhysical(vtx1), 2, 0);
|
|
gSPMatrix(renderdata->gdl++, osVirtualToPhysical(render_pos2), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
|
gSPVertex(renderdata->gdl++, osVirtualToPhysical(&rodata->Vertices[child_vtx->VtxIndex] + 2), (u32)(child_vtx->unk01 - 2), 2);
|
|
|
|
entry_04 += sizeof(struct ModelRoData_Child_Vtx);
|
|
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_CHILD_IMAGE:
|
|
{
|
|
struct ModelRoData_Child_Image* child_image = (struct ModelRoData_Child_Image*)entry_04;
|
|
if (child_image->ImageIndex != 0xFF)
|
|
{
|
|
sub_GAME_7F073038(renderdata, &rodata->Images[child_image->ImageIndex], 1);
|
|
entry_04 += sizeof(struct ModelRoData_Child_Image);
|
|
}
|
|
else
|
|
{
|
|
sub_GAME_7F073038(renderdata, NULL, 1);
|
|
entry_04 += sizeof(struct ModelRoData_Child_Image);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_CHILD_TRI:
|
|
{
|
|
struct ModelRoData_Child_Tri* child_tri = (struct ModelRoData_Child_Tri*)entry_04;
|
|
gSP1Triangle(renderdata->gdl++, child_tri->VtxIndex1, child_tri->VtxIndex2, child_tri->VtxIndex3, 0);
|
|
entry_04 += sizeof(struct ModelRoData_Child_Tri);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void sub_GAME_7F0737EC(s32 param_1,struct Model *param_2, struct ModelNode *param_3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
void sub_GAME_7F0737FC(s32 param_1,struct Model *param_2,struct ModelNode *param_3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// PD: modelRenderNodeChrGunfire
|
|
void dogfnegx(ModelRenderData *renderdata, Model *model, ModelNode *node)
|
|
{
|
|
u32 unused[3];
|
|
f32 negspc0;
|
|
ModelRoData_GunfireRecord *rodata = &node->Data->Gunfire;
|
|
union ModelRwData *rwdata = modelGetNodeRwData(model, node);
|
|
sImageTableEntry *tconfig;
|
|
f32 spf0;
|
|
f32 spec;
|
|
coord3d spe0;
|
|
f32 spdc;
|
|
f32 spd8;
|
|
f32 spd4;
|
|
f32 spd0;
|
|
f32 spcc;
|
|
f32 spc8;
|
|
f32 spc4;
|
|
f32 spc0;
|
|
f32 spbc;
|
|
f32 negspcc;
|
|
f32 negspc8;
|
|
f32 scale;
|
|
Mtxf *mtx;
|
|
f32 tmp;
|
|
coord3d sp9c;
|
|
coord3d sp90;
|
|
Vertex vtxtemplate = D_800363E0;
|
|
Vertex *vertices;
|
|
f32 distance;
|
|
|
|
if ((renderdata->flags & 2) && rwdata->Gunfire.visible)
|
|
{
|
|
s32 index = modelFindNodeMtxIndex(node, 0);
|
|
mtx = &model->render_pos[index].pos;
|
|
|
|
spe0.x = -(rodata->Offset.f[0] * mtx->m[0][0] + rodata->Offset.f[1] * mtx->m[1][0] + rodata->Offset.f[2] * mtx->m[2][0] + mtx->m[3][0]);
|
|
spe0.y = -(rodata->Offset.f[0] * mtx->m[0][1] + rodata->Offset.f[1] * mtx->m[1][1] + rodata->Offset.f[2] * mtx->m[2][1] + mtx->m[3][1]);
|
|
spe0.z = -(rodata->Offset.f[0] * mtx->m[0][2] + rodata->Offset.f[1] * mtx->m[1][2] + rodata->Offset.f[2] * mtx->m[2][2] + mtx->m[3][2]);
|
|
|
|
distance = sqrtf(spe0.f[0] * spe0.f[0] + spe0.f[1] * spe0.f[1] + spe0.f[2] * spe0.f[2]);
|
|
|
|
if (distance > 0)
|
|
{
|
|
f32 tmp = 1 / (model->scale * distance);
|
|
spe0.f[0] *= tmp;
|
|
spe0.f[1] *= tmp;
|
|
spe0.f[2] *= tmp;
|
|
}
|
|
else
|
|
{
|
|
spe0.f[0] = 0;
|
|
spe0.f[1] = 0;
|
|
spe0.f[2] = 1 / model->scale;
|
|
}
|
|
|
|
spec = acosf(spe0.f[0] * mtx->m[1][0] + spe0.f[1] * mtx->m[1][1] + spe0.f[2] * mtx->m[1][2]);
|
|
spf0 = acosf(-(spe0.f[0] * mtx->m[2][0] + spe0.f[1] * mtx->m[2][1] + spe0.f[2] * mtx->m[2][2]) / sinf(spec));
|
|
|
|
tmp = -(spe0.f[0] * mtx->m[0][0] + spe0.f[1] * mtx->m[0][1] + spe0.f[2] * mtx->m[0][2]);
|
|
|
|
if (tmp < 0)
|
|
{
|
|
spf0 = M_TAU_F - spf0;
|
|
}
|
|
|
|
spdc = cosf(spf0);
|
|
spd8 = sinf(spf0);
|
|
spd4 = cosf(spec);
|
|
spd0 = sinf(spec);
|
|
|
|
scale = 0.75f + (randomGetNext() % 128) * (1.0f / 256.0f); // 0.75 to 1.25
|
|
|
|
sp9c.f[0] = rodata->Size.f[0] * scale;
|
|
sp9c.f[1] = rodata->Size.f[1] * scale;
|
|
sp9c.f[2] = rodata->Size.f[2] * scale;
|
|
|
|
spcc = sp9c.f[0] * spdc * 0.5f;
|
|
spc8 = sp9c.f[2] * spd8 * 0.5f;
|
|
spc4 = sp9c.f[1] * spd0 * 0.5f;
|
|
|
|
spc0 = sp9c.f[0] * spd4 * spd8 * 0.5f;
|
|
spbc = sp9c.f[2] * spd4 * spdc * 0.5f;
|
|
|
|
negspcc = -spcc;
|
|
negspc8 = -spc8;
|
|
negspc0 = -spc0;
|
|
|
|
sp90.f[0] = rodata->Offset.f[0] - sp9c.f[0] * 0.5f;
|
|
sp90.f[1] = rodata->Offset.f[1];
|
|
sp90.f[2] = rodata->Offset.f[2];
|
|
|
|
#if defined (LEFTOVERDEBUG)
|
|
if (vtxallocator == NULL) {
|
|
osSyncPrintf("dogfnegx: no vtx allocator!\n");
|
|
return_null();
|
|
}
|
|
#endif
|
|
|
|
vertices = vtxallocator(4);
|
|
|
|
vertices[0] = vtxtemplate;
|
|
vertices[1] = vtxtemplate;
|
|
vertices[2] = vtxtemplate;
|
|
vertices[3] = vtxtemplate;
|
|
|
|
vertices[0].coord.x = sp90.f[0] + negspcc + negspc0;
|
|
vertices[0].coord.y = sp90.f[1] - spc4;
|
|
vertices[0].coord.z = sp90.f[2] - negspc8 + -spbc;
|
|
vertices[1].coord.x = sp90.f[0] + negspcc - negspc0;
|
|
vertices[1].coord.y = sp90.f[1] + spc4;
|
|
vertices[1].coord.z = sp90.f[2] - negspc8 - -spbc;
|
|
vertices[2].coord.x = sp90.f[0] - negspcc - negspc0;
|
|
vertices[2].coord.y = sp90.f[1] + spc4;
|
|
vertices[2].coord.z = sp90.f[2] + negspc8 - -spbc;
|
|
vertices[3].coord.x = sp90.f[0] - negspcc + negspc0;
|
|
vertices[3].coord.y = sp90.f[1] - spc4;
|
|
vertices[3].coord.z = sp90.f[2] + negspc8 + -spbc;
|
|
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(rodata->BaseAddr));
|
|
|
|
if (rodata->Image)
|
|
{
|
|
s32 centre;
|
|
u16 sp62;
|
|
s32 sp5c;
|
|
s32 sp58;
|
|
|
|
tconfig = rodata->Image;
|
|
|
|
sp62 = (randomGetNext() * 1024) & 0xffff;
|
|
sp5c = (coss(sp62) * tconfig->width * 0xb5) >> 18;
|
|
sp58 = (sins(sp62) * tconfig->width * 0xb5) >> 18;
|
|
|
|
centre = tconfig->width << 4;
|
|
|
|
vertices[0].s = centre - sp5c;
|
|
vertices[0].t = centre - sp58;
|
|
vertices[1].s = centre + sp58;
|
|
vertices[1].t = centre - sp5c;
|
|
vertices[2].s = centre + sp5c;
|
|
vertices[2].t = centre + sp58;
|
|
vertices[3].s = centre - sp58;
|
|
vertices[3].t = centre + sp5c;
|
|
|
|
sub_GAME_7F073038(renderdata, tconfig, 4);
|
|
}
|
|
else
|
|
{
|
|
sub_GAME_7F073038(renderdata, NULL, 1);
|
|
}
|
|
|
|
gSPSetGeometryMode(renderdata->gdl++, G_CULL_BACK);
|
|
gSPMatrix(renderdata->gdl++, osVirtualToPhysical(mtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
|
gSPVertex(renderdata->gdl++, osVirtualToPhysical(vertices), 4, 0);
|
|
if (1);
|
|
gSP2Triangles(renderdata->gdl++, 0, 1, 2, 0, 2, 3, 0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
void sub_GAME_7F073FC8(s32 arg0)
|
|
{
|
|
D_800363F0 = arg0;
|
|
}
|
|
|
|
|
|
#ifdef LEFTOVERDEBUG
|
|
//D:80054A94
|
|
const char aDoshadowNoVtxAllocator[] = "doshadow: no vtx allocator!\n";
|
|
#endif
|
|
|
|
void doshadow(ModelRenderData *renderdata, Model *model, ModelNode *node)
|
|
{
|
|
sImageTableEntry *image;
|
|
RenderPosView *mtx;
|
|
ModelRoData_ShadowRecord *shadow;
|
|
Vtx vtxtemplate;
|
|
Vtx *vtx;
|
|
s16 y;
|
|
s32 temp;
|
|
f32 sizex;
|
|
f32 sizey;
|
|
f32 height;
|
|
ModelRwData_HeaderRecord *rwdata;
|
|
|
|
if ((s32) D_800363F0 <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
shadow = &node->Data->Shadow;
|
|
vtxtemplate = D_800363F8;
|
|
rwdata = (ModelRwData_HeaderRecord *) modelGetNodeRwData(model, shadow->HeaderNode);
|
|
height = rwdata->pos.y - rwdata->ground;
|
|
sizex = shadow->size.x;
|
|
sizey = shadow->size.y;
|
|
|
|
if (!(renderdata->flags & 2))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((renderdata->PropType == PROP_TYPE_CHR) || (renderdata->PropType == PROP_TYPE_SMOKE))
|
|
{
|
|
temp = (vtxtemplate.v.cn[3] = ((renderdata->envcolour.word & 0xff) * D_800363F0) / 255);
|
|
}
|
|
else
|
|
{
|
|
vtxtemplate.v.cn[3] = D_800363F0;
|
|
}
|
|
|
|
temp = modelFindNodeMtxIndex(node, 0);
|
|
mtx = &model->render_pos[temp];
|
|
|
|
if (renderdata->zbufferenabled)
|
|
{
|
|
y = (2.0f - height) / model->scale;
|
|
}
|
|
else
|
|
{
|
|
y = (-height) / model->scale;
|
|
}
|
|
|
|
if (height < 50.0f)
|
|
{
|
|
sizex *= 1.25f;
|
|
sizey *= 1.25f;
|
|
}
|
|
else if (300.0f < height)
|
|
{
|
|
sizex = 0.0f;
|
|
sizey = sizex;
|
|
}
|
|
else
|
|
{
|
|
sizex *= (300.0f - height) / 200.0f;
|
|
sizey *= (300.0f - height) / 200.0f;
|
|
}
|
|
|
|
#ifdef LEFTOVERDEBUG
|
|
if (vtxallocator == NULL)
|
|
{
|
|
if (1);
|
|
osSyncPrintf(aDoshadowNoVtxAllocator);
|
|
return_null();
|
|
}
|
|
#endif
|
|
|
|
vtx = (Vtx *) vtxallocator(4);
|
|
|
|
vtx[0] = vtxtemplate;
|
|
vtx[1] = vtxtemplate;
|
|
vtx[2] = vtxtemplate;
|
|
vtx[3] = vtxtemplate;
|
|
|
|
vtx[0].v.ob[0] = shadow->pos.x - sizex;
|
|
vtx[0].v.ob[1] = y;
|
|
vtx[0].v.ob[2] = shadow->pos.y - sizey;
|
|
vtx[1].v.ob[0] = shadow->pos.x - sizex;
|
|
vtx[1].v.ob[1] = y;
|
|
vtx[1].v.ob[2] = shadow->pos.y + sizey;
|
|
vtx[2].v.ob[0] = shadow->pos.x + sizex;
|
|
vtx[2].v.ob[1] = y;
|
|
vtx[2].v.ob[2] = shadow->pos.y + sizey;
|
|
vtx[3].v.ob[0] = shadow->pos.x + sizex;
|
|
vtx[3].v.ob[1] = y;
|
|
vtx[3].v.ob[2] = shadow->pos.y - sizey;
|
|
|
|
gSPSegment(renderdata->gdl++, SPSEGMENT_MODEL_COL1, osVirtualToPhysical(shadow->BaseAddr));
|
|
|
|
if (shadow->image != NULL)
|
|
{
|
|
image = shadow->image;
|
|
|
|
vtx[0].v.tc[0] = 0;
|
|
vtx[0].v.tc[1] = 0;
|
|
vtx[1].v.tc[0] = (image->width << 5) - 1;
|
|
vtx[1].v.tc[1] = 0;
|
|
vtx[2].v.tc[0] = (image->width << 5) - 1;
|
|
vtx[2].v.tc[1] = (image->height << 5) - 1;
|
|
vtx[3].v.tc[0] = 0;
|
|
vtx[3].v.tc[1] = (image->height << 5) - 1;
|
|
|
|
sub_GAME_7F073038(renderdata, image, 4);
|
|
}
|
|
else
|
|
{
|
|
sub_GAME_7F073038(renderdata, NULL, 1);
|
|
}
|
|
|
|
gSPSetGeometryMode(renderdata->gdl++, G_CULL_BACK);
|
|
|
|
{
|
|
Gfx *gdl;
|
|
gSPMatrix(renderdata->gdl++, osVirtualToPhysical(mtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
|
gSPVertex(renderdata->gdl++, osVirtualToPhysical(vtx), 4, 0);
|
|
// gSP2Triangles(gdl, 0, 1, 2, 0, 2, 3, 0, 0) via GE's G_TRI4. The macro
|
|
// itself can't be used here: its inner Gfx temp would be the block's gfx
|
|
// pointer local and shift the spill homes off the original layout.
|
|
gdl = renderdata->gdl++;
|
|
gdl->words.w0 = _SHIFTL(G_TRI4, 24, 8) | _SHIFTL(2, 0, 4); gdl->words.w1 = _SHIFTL(3, 12, 4) | _SHIFTL(2, 8, 4) | _SHIFTL(1, 4, 4);
|
|
}
|
|
}
|
|
|
|
|
|
void sub_GAME_7F074514(s32 param_1,struct Model *param_2,struct ModelNode *param_3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
void sub_GAME_7F074524(Gfx *param_1,struct Model *param_2, struct ModelNode *param_3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
void sub_GAME_7F074534(ModelRenderData* data, Model* model, ModelNode* node) {
|
|
u32 id = node->Opcode & 0xFF;
|
|
switch (id) {
|
|
case MODELNODE_OPCODE_LOD:
|
|
modelApplyDistanceRelations(model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
modelApplyToggleRelations(model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_HEAD:
|
|
modelApplyHeadRelations(model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_BSP:
|
|
modelApplyReorderRelations(model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_OP11:
|
|
sub_GAME_7F0737FC(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_GUNFIRE:
|
|
dogfnegx(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_SHADOW:
|
|
doshadow(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_BBOX:
|
|
sub_GAME_7F074514(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_OP17:
|
|
sub_GAME_7F074524(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_DL:
|
|
modelRenderNodeGundl(data, node);
|
|
return;
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
modelRenderNodeDl(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_OP20:
|
|
sub_GAME_7F072C10(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_DLPRIMARY:
|
|
dorottex(data, node);
|
|
return;
|
|
case MODELNODE_OPCODE_OP05:
|
|
sub_GAME_7F07306C(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_OP07:
|
|
dotube(data, model, node);
|
|
return;
|
|
case MODELNODE_OPCODE_OP06:
|
|
sub_GAME_7F0737EC(data,model,node);
|
|
return;
|
|
case MODELNODE_OPCODE_HEADER:
|
|
case MODELNODE_OPCODE_GROUP:
|
|
case MODELNODE_OPCODE_OP03:
|
|
case MODELNODE_OPCODE_OP14:
|
|
case MODELNODE_OPCODE_INTERLINK:
|
|
case MODELNODE_OPCODE_OP16:
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void subdraw(ModelRenderData *mrData, Model *mdl)
|
|
{
|
|
ModelNode *root = mdl->obj->RootNode;
|
|
#if defined(LEFTOVERDEBUG)
|
|
|
|
if (mrData->gdl == NULL)
|
|
{
|
|
osSyncPrintf("subdraw: no gfxlist!\n");
|
|
return_null();
|
|
}
|
|
|
|
if (mdl->obj->isLoaded)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
osSyncPrintf("subdraw: object not initialised! (0x%X)\n", (u32)mdl->obj);
|
|
return_null();
|
|
}
|
|
#endif
|
|
gSPSegment(mrData->gdl++, 3, osVirtualToPhysical(mdl->render_pos));
|
|
|
|
while (root != NULL)
|
|
{
|
|
sub_GAME_7F074534(mrData, mdl, root);
|
|
|
|
if (root->Child)
|
|
{
|
|
root = root->Child;
|
|
}
|
|
else
|
|
{
|
|
while (root)
|
|
{
|
|
if (root->Next)
|
|
{
|
|
root = root->Next;
|
|
break;
|
|
}
|
|
root = root->Parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// unreferenced
|
|
void sub_GAME_7F074790(ModelRenderData* arg0, Model* arg1)
|
|
{
|
|
subcalcpos(arg1);
|
|
subcalcmatrices(arg0, arg1);
|
|
subdraw((s32) arg0, arg1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F0747D0
|
|
*
|
|
* Ray vs transformed bounding box test. The bbox is transformed by mtx while the ray
|
|
* is defined by pos and dir.
|
|
*
|
|
* @returns TRUE if the ray intersects the bbox, otherwise FALSE.
|
|
*/
|
|
bool modelTestRayIntersectsTransformedBBox(ModelRoData_BoundingBoxRecord *bbox, Mtxf *mtx, coord3d *pos, coord3d *dir)
|
|
{
|
|
f32 xthingx;
|
|
f32 xthingy;
|
|
f32 xthingz;
|
|
u32 stack1[1];
|
|
f32 xpmin;
|
|
f32 xpmax;
|
|
f32 xsum1;
|
|
f32 xsum2;
|
|
f32 xsum3;
|
|
f32 negL2;
|
|
f32 pmin;
|
|
f32 ythingx;
|
|
f32 ythingy;
|
|
f32 ythingz;
|
|
f32 pmax;
|
|
f32 ypmin;
|
|
f32 ypmax;
|
|
f32 ysum1;
|
|
f32 ysum2;
|
|
f32 ysum3;
|
|
f32 mult1;
|
|
f32 mult2;
|
|
f32 bestsum2;
|
|
f32 bestsum1;
|
|
f32 anotherbestsum3;
|
|
f32 anotherbestsum1;
|
|
f32 mult3;
|
|
f32 mult4;
|
|
f32 zthingx;
|
|
f32 zthingy;
|
|
f32 zthingz;
|
|
u32 stack3[1];
|
|
f32 zpmin;
|
|
f32 zpmax;
|
|
f32 zsum1;
|
|
f32 zsum2;
|
|
f32 zsum3;
|
|
|
|
xthingx = mtx->m[0][0] * mtx->m[0][0];
|
|
xthingy = mtx->m[0][1] * mtx->m[0][1];
|
|
xthingz = mtx->m[0][2] * mtx->m[0][2];
|
|
|
|
negL2 = -((xthingx + xthingy) + xthingz);
|
|
|
|
xpmin = negL2;
|
|
xpmin *= bbox->Bounds.xmin;
|
|
|
|
xpmax = negL2;
|
|
xpmax *= bbox->Bounds.xmax;
|
|
|
|
xsum1 = ((mtx->m[0][0] * dir->f[0]) + (mtx->m[0][1] * dir->f[1])) + (mtx->m[0][2] * dir->f[2]);
|
|
xsum2 = ((mtx->m[0][0] * (pos->f[0] - mtx->m[3][0])) + (mtx->m[0][1] * (pos->f[1] - mtx->m[3][1]))) + (mtx->m[0][2] * (pos->f[2] - mtx->m[3][2]));
|
|
|
|
xsum3 = -(xsum2 + xpmax);
|
|
xsum2 = -(xsum2 + xpmin);
|
|
|
|
if (xsum1 < 0.0f)
|
|
{
|
|
xsum1 = -xsum1;
|
|
xsum2 = -xsum2;
|
|
xsum3 = -xsum3;
|
|
}
|
|
|
|
if ((xsum2 < 0.0f) && (xsum3 < 0.0f))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (xsum3 < xsum2)
|
|
{
|
|
f32 tmp = xsum2;
|
|
|
|
xsum2 = xsum3;
|
|
xsum3 = tmp;
|
|
}
|
|
|
|
ythingx = mtx->m[1][0] * mtx->m[1][0];
|
|
ythingy = mtx->m[1][1] * mtx->m[1][1];
|
|
ythingz = mtx->m[1][2] * mtx->m[1][2];
|
|
|
|
negL2 = -((ythingx + ythingy) + ythingz);
|
|
|
|
ypmin = negL2;
|
|
ypmin *= bbox->Bounds.ymin;
|
|
|
|
ypmax = negL2;
|
|
ypmax *= bbox->Bounds.ymax;
|
|
|
|
ysum1 = ((mtx->m[1][0] * dir->f[0]) + (mtx->m[1][1] * dir->f[1])) + (mtx->m[1][2] * dir->f[2]);
|
|
ysum2 = ((mtx->m[1][0] * (pos->f[0] - mtx->m[3][0])) + (mtx->m[1][1] * (pos->f[1] - mtx->m[3][1]))) + (mtx->m[1][2] * (pos->f[2] - mtx->m[3][2]));
|
|
|
|
ysum3 = -(ysum2 + ypmax);
|
|
ysum2 = -(ysum2 + ypmin);
|
|
|
|
if (ysum1 < 0.0f)
|
|
{
|
|
ysum1 = -ysum1;
|
|
ysum2 = -ysum2;
|
|
ysum3 = -ysum3;
|
|
}
|
|
|
|
if ((ysum2 < 0.0f) && (ysum3 < 0.0f))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (ysum3 < ysum2)
|
|
{
|
|
f32 tmp = ysum2;
|
|
|
|
ysum2 = ysum3;
|
|
ysum3 = tmp;
|
|
}
|
|
|
|
mult1 = ysum2 * xsum1;
|
|
mult2 = xsum2 * ysum1;
|
|
mult3 = xsum3 * ysum1;
|
|
mult4 = ysum3 * xsum1;
|
|
|
|
if (mult1 < mult2)
|
|
{
|
|
if (mult4 < mult2)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bestsum2 = xsum2;
|
|
bestsum1 = xsum1;
|
|
}
|
|
else
|
|
{
|
|
if (mult3 < mult1)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bestsum2 = ysum2;
|
|
bestsum1 = ysum1;
|
|
}
|
|
|
|
if (mult3 < mult4)
|
|
{
|
|
anotherbestsum3 = xsum3;
|
|
anotherbestsum1 = xsum1;
|
|
}
|
|
else
|
|
{
|
|
anotherbestsum3 = ysum3;
|
|
anotherbestsum1 = ysum1;
|
|
}
|
|
|
|
zthingx = mtx->m[2][0] * mtx->m[2][0];
|
|
zthingy = mtx->m[2][1] * mtx->m[2][1];
|
|
zthingz = mtx->m[2][2] * mtx->m[2][2];
|
|
|
|
negL2 = -((zthingx + zthingy) + zthingz);
|
|
|
|
zpmin = negL2;
|
|
zpmin *= bbox->Bounds.zmin;
|
|
|
|
zpmax = negL2;
|
|
zpmax *= bbox->Bounds.zmax;
|
|
|
|
zsum1 = ((mtx->m[2][0] * dir->f[0]) + (mtx->m[2][1] * dir->f[1])) + (mtx->m[2][2] * dir->f[2]);
|
|
zsum2 = ((mtx->m[2][0] * (pos->f[0] - mtx->m[3][0])) + (mtx->m[2][1] * (pos->f[1] - mtx->m[3][1]))) + (mtx->m[2][2] * (pos->f[2] - mtx->m[3][2]));
|
|
|
|
zsum3 = -(zsum2 + zpmax);
|
|
zsum2 = -(zsum2 + zpmin);
|
|
|
|
if (zsum1 < 0.0f)
|
|
{
|
|
zsum1 = -zsum1;
|
|
zsum2 = -zsum2;
|
|
zsum3 = -zsum3;
|
|
}
|
|
|
|
if ((zsum2 < 0.0f) && (zsum3 < 0.0f))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (zsum3 < zsum2)
|
|
{
|
|
f32 tmp = zsum2;
|
|
|
|
zsum2 = zsum3;
|
|
zsum3 = tmp;
|
|
}
|
|
|
|
if ((bestsum2 * zsum1) < (zsum2 * bestsum1))
|
|
{
|
|
if ((anotherbestsum3 * zsum1) < (zsum2 * anotherbestsum1))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if ((zsum3 * bestsum1) < (bestsum2 * zsum1))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F074C68
|
|
*/
|
|
bool modelTestRayIntersectsNodeBBox(Model *model, ModelNode *node, coord3d *pos, coord3d *dir)
|
|
{
|
|
ModelRoData_BoundingBoxRecord *bbox = &node->Data->BoundingBox;
|
|
|
|
return modelTestRayIntersectsTransformedBBox(bbox, modelFindNodeMtx(model, node, 0), pos, dir);
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F074CAC
|
|
*/
|
|
s32 sub_GAME_7F074CAC(Model *model, ModelNode *node, coord3d *raypos, coord3d *raydir)
|
|
{
|
|
ModelRoData_Op17Record *hitData;
|
|
Mtxf *nodeMtx;
|
|
ModelOp17MainStack rayData[1];
|
|
f32 centerProjection;
|
|
Mtxf *otherNodeMtx;
|
|
ModelOp17AxisStack axisData[1];
|
|
f32 scaledProjection;
|
|
f32 projectionScalar;
|
|
f32 secondAxisScale;
|
|
f32 centerDistanceSq;
|
|
u32 nodeFlags;
|
|
f32 directionDotProduct;
|
|
|
|
hitData = (ModelRoData_Op17Record *) node->Data;
|
|
nodeMtx = modelFindNodeMtx(model, node, 0);
|
|
rayData->data.rel = D_80036408;
|
|
rayData->data.radiusSq = hitData->radiusSq;
|
|
rayData->data.dir.f[0] = raydir->x;
|
|
rayData->data.dir.f[1] = raydir->y;
|
|
rayData->data.dir.f[2] = raydir->z;
|
|
nodeFlags = node->Opcode;
|
|
|
|
if (nodeFlags & 0x100)
|
|
{
|
|
rayData->data.pos.f[0] = hitData->pos.f[0];
|
|
rayData->data.pos.f[1] = hitData->pos.f[1];
|
|
rayData->data.pos.f[2] = hitData->pos.f[2];
|
|
rayData->data.rel.f[0] = (((rayData->data.pos.f[0] * nodeMtx->m[0][0]) + (rayData->data.pos.f[1] * nodeMtx->m[1][0])) + (rayData->data.pos.f[2] * nodeMtx->m[2][0])) + (nodeMtx->m[3][0] - raypos->x);
|
|
rayData->data.rel.f[1] = (((rayData->data.pos.f[0] * nodeMtx->m[0][1]) + (rayData->data.pos.f[1] * nodeMtx->m[1][1])) + (rayData->data.pos.f[2] * nodeMtx->m[2][1])) + (nodeMtx->m[3][1] - raypos->y);
|
|
rayData->data.rel.f[2] = (((rayData->data.pos.f[0] * nodeMtx->m[0][2]) + (rayData->data.pos.f[1] * nodeMtx->m[1][2])) + (rayData->data.pos.f[2] * nodeMtx->m[2][2])) + (nodeMtx->m[3][2] - raypos->z);
|
|
}
|
|
else if (nodeFlags & 0x200)
|
|
{
|
|
if (hitData->othernode != NULL)
|
|
{
|
|
otherNodeMtx = modelFindNodeMtx(model, hitData->othernode, 0);
|
|
rayData->data.rel.f[0] = ((nodeMtx->m[3][0] + otherNodeMtx->m[3][0]) * 0.5f) - (*raypos).f[0];
|
|
rayData->data.rel.f[1] = ((nodeMtx->m[3][1] + otherNodeMtx->m[3][1]) * 0.5f) - (*raypos).f[1];
|
|
rayData->data.rel.f[2] = ((nodeMtx->m[3][2] + otherNodeMtx->m[3][2]) * 0.5f) - (*raypos).f[2];
|
|
}
|
|
else
|
|
{
|
|
rayData->data.rel.f[0] = nodeMtx->m[3][0] - raypos->x;
|
|
rayData->data.rel.f[1] = nodeMtx->m[3][1] - raypos->y;
|
|
rayData->data.rel.f[2] = nodeMtx->m[3][2] - raypos->z;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rayData->data.rel.f[0] = nodeMtx->m[3][0] - raypos->x;
|
|
rayData->data.rel.f[1] = nodeMtx->m[3][1] - raypos->y;
|
|
rayData->data.rel.f[2] = nodeMtx->m[3][2] - raypos->z;
|
|
}
|
|
|
|
nodeFlags = node->Opcode;
|
|
|
|
if (nodeFlags & 0x400)
|
|
{
|
|
projectionScalar = hitData->scale1;
|
|
secondAxisScale = hitData->scale2;
|
|
scaledProjection = (((rayData->data.dir.f[0] * nodeMtx->m[0][0]) + (rayData->data.dir.f[1] * nodeMtx->m[0][1])) + (rayData->data.dir.f[2] * nodeMtx->m[0][2])) * projectionScalar;
|
|
directionDotProduct = scaledProjection;
|
|
rayData->data.dir.f[0] = rayData->data.dir.f[0] + (nodeMtx->m[0][0] * directionDotProduct);
|
|
rayData->data.dir.f[1] = rayData->data.dir.f[1] + (nodeMtx->m[0][1] * directionDotProduct);
|
|
rayData->data.dir.f[2] = rayData->data.dir.f[2] + (nodeMtx->m[0][2] * directionDotProduct);
|
|
centerProjection = (((rayData->data.rel.f[0] * nodeMtx->m[0][0]) + (rayData->data.rel.f[1] * nodeMtx->m[0][1])) + (rayData->data.rel.f[2] * nodeMtx->m[0][2])) * projectionScalar;
|
|
rayData->data.rel.f[0] = rayData->data.rel.f[0] + (nodeMtx->m[0][0] * centerProjection);
|
|
rayData->data.rel.f[1] = rayData->data.rel.f[1] + (nodeMtx->m[0][1] * centerProjection);
|
|
rayData->data.rel.f[2] = rayData->data.rel.f[2] + (nodeMtx->m[0][2] * centerProjection);
|
|
scaledProjection = (((rayData->data.dir.f[0] * nodeMtx->m[1][0]) + (rayData->data.dir.f[1] * nodeMtx->m[1][1])) + (rayData->data.dir.f[2] * nodeMtx->m[1][2])) * secondAxisScale;
|
|
directionDotProduct = scaledProjection;
|
|
rayData->data.dir.f[0] = rayData->data.dir.f[0] + (nodeMtx->m[1][0] * directionDotProduct);
|
|
rayData->data.dir.f[1] = rayData->data.dir.f[1] + (nodeMtx->m[1][1] * directionDotProduct);
|
|
rayData->data.dir.f[2] = rayData->data.dir.f[2] + (nodeMtx->m[1][2] * directionDotProduct);
|
|
projectionScalar = (((rayData->data.rel.f[0] * nodeMtx->m[1][0]) + (rayData->data.rel.f[1] * nodeMtx->m[1][1])) + (rayData->data.rel.f[2] * nodeMtx->m[1][2])) * secondAxisScale;
|
|
rayData->data.rel.f[0] = rayData->data.rel.f[0] + (nodeMtx->m[1][0] * projectionScalar);
|
|
rayData->data.rel.f[1] = rayData->data.rel.f[1] + (nodeMtx->m[1][1] * projectionScalar);
|
|
rayData->data.rel.f[2] = rayData->data.rel.f[2] + (nodeMtx->m[1][2] * projectionScalar);
|
|
}
|
|
else if (((nodeFlags & 0x800) || (nodeFlags & 0x1000)) || (nodeFlags & 0x2000))
|
|
{
|
|
axisData->axisAdjustment.scale = hitData->scale1;
|
|
if ((nodeFlags & 0x800))
|
|
{
|
|
axisData->axisAdjustment.axis.f[0] = nodeMtx->m[0][0];
|
|
axisData->axisAdjustment.axis.f[1] = nodeMtx->m[0][1];
|
|
axisData->axisAdjustment.axis.f[2] = nodeMtx->m[0][2];
|
|
}
|
|
else if (nodeFlags & 0x1000)
|
|
{
|
|
axisData->axisAdjustment.axis.f[0] = nodeMtx->m[1][0];
|
|
axisData->axisAdjustment.axis.f[1] = nodeMtx->m[1][1];
|
|
axisData->axisAdjustment.axis.f[2] = nodeMtx->m[1][2];
|
|
}
|
|
else if (nodeFlags & 0x2000)
|
|
{
|
|
axisData->axisAdjustment.axis.f[0] = nodeMtx->m[2][0];
|
|
axisData->axisAdjustment.axis.f[1] = nodeMtx->m[2][1];
|
|
axisData->axisAdjustment.axis.f[2] = nodeMtx->m[2][2];
|
|
}
|
|
|
|
directionDotProduct = axisData->axisAdjustment.scale * (
|
|
(rayData->data.dir.f[0] * axisData->axisAdjustment.axis.f[0]) +
|
|
(rayData->data.dir.f[1] * axisData->axisAdjustment.axis.f[1]) +
|
|
(rayData->data.dir.f[2] * axisData->axisAdjustment.axis.f[2])
|
|
);
|
|
|
|
rayData->data.dir.f[0] = rayData->data.dir.f[0] + (axisData->axisAdjustment.axis.f[0] * directionDotProduct);
|
|
rayData->data.dir.f[1] = rayData->data.dir.f[1] + (axisData->axisAdjustment.axis.f[1] * directionDotProduct);
|
|
rayData->data.dir.f[2] = rayData->data.dir.f[2] + (axisData->axisAdjustment.axis.f[2] * directionDotProduct);
|
|
|
|
scaledProjection = axisData->axisAdjustment.scale * (
|
|
(rayData->data.rel.f[0] * axisData->axisAdjustment.axis.f[0]) +
|
|
(rayData->data.rel.f[1] * axisData->axisAdjustment.axis.f[1]) +
|
|
(rayData->data.rel.f[2] * axisData->axisAdjustment.axis.f[2])
|
|
);
|
|
|
|
rayData->data.rel.f[0] = rayData->data.rel.f[0] + (axisData->axisAdjustment.axis.f[0] * scaledProjection);
|
|
rayData->data.rel.f[1] = rayData->data.rel.f[1] + (axisData->axisAdjustment.axis.f[1] * scaledProjection);
|
|
rayData->data.rel.f[2] = rayData->data.rel.f[2] + (axisData->axisAdjustment.axis.f[2] * scaledProjection);
|
|
}
|
|
|
|
projectionScalar =
|
|
(rayData->data.dir.f[0] * rayData->data.rel.f[0]) +
|
|
(rayData->data.dir.f[1] * rayData->data.rel.f[1]) +
|
|
(rayData->data.dir.f[2] * rayData->data.rel.f[2]);
|
|
|
|
if (0.0f < projectionScalar)
|
|
{
|
|
directionDotProduct = (rayData->data.dir.f[0] * rayData->data.dir.f[0]) + (rayData->data.dir.f[1] * rayData->data.dir.f[1]) + (rayData->data.dir.f[2] * rayData->data.dir.f[2]);
|
|
centerDistanceSq = (rayData->data.rel.f[0] * rayData->data.rel.f[0]) + (rayData->data.rel.f[1] * rayData->data.rel.f[1]) + (0, rayData->data.rel.f[2] * rayData->data.rel.f[2]);
|
|
|
|
if (((centerDistanceSq - rayData->data.radiusSq) * directionDotProduct) <= (projectionScalar * projectionScalar))
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address: 7F0752FC
|
|
*/
|
|
u32 modelFindNextProjectileHitCandidate(Model *model, coord3d *arg1, coord3d *arg2, ModelNode **nodeptr)
|
|
{
|
|
ModelNode *node;
|
|
s32 descend;
|
|
u32 opcode;
|
|
|
|
descend = TRUE;
|
|
|
|
if (*nodeptr != NULL) {
|
|
node = *nodeptr;
|
|
*nodeptr = NULL;
|
|
} else {
|
|
node = model->obj->RootNode;
|
|
}
|
|
|
|
if (node != NULL) {
|
|
do {
|
|
if (descend != 0 && node->Child != NULL) {
|
|
node = node->Child;
|
|
} else {
|
|
if (node != NULL) {
|
|
do {
|
|
if (node->Next != NULL) {
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
} while (node != NULL);
|
|
}
|
|
|
|
if (node == NULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
descend = TRUE;
|
|
opcode = node->Opcode & 0xff;
|
|
|
|
/*
|
|
* This switch is written as opcode - 1 to match the compiler's jump-table generation.
|
|
* The real opcodes run from HEADER=1 through DLCOLLISION=24, so the compiler
|
|
* normalizes them to a zero-based table index by subtracting 1.
|
|
*/
|
|
switch (opcode - 1) {
|
|
case MODELNODE_OPCODE_BBOX - 1:
|
|
if (modelTestRayIntersectsNodeBBox(model, node, arg1, arg2) != 0) {
|
|
*nodeptr = node;
|
|
return *(u32 *)node->Data;
|
|
}
|
|
descend = FALSE;
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_OP17 - 1:
|
|
if (sub_GAME_7F074CAC(model, node, arg1, arg2) != 0) {
|
|
*nodeptr = node;
|
|
return *(u32 *)node->Data;
|
|
}
|
|
descend = FALSE;
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_LOD - 1:
|
|
modelApplyDistanceRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_SWITCH - 1:
|
|
modelApplyToggleRelations(model, node);
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_HEAD - 1:
|
|
modelApplyHeadRelations(model, node);
|
|
break;
|
|
case MODELNODE_OPCODE_HEADER - 1:
|
|
case MODELNODE_OPCODE_DLCOLLISION - 1:
|
|
default:
|
|
break;
|
|
}
|
|
} while (node != NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Unreferenced
|
|
*/
|
|
u32 *sub_GAME_7F07549C(void *arg0, f32 *arg1, f32 *arg2, ModelNode **nodeptr)
|
|
{
|
|
*nodeptr = NULL;
|
|
return modelFindNextProjectileHitCandidate(arg0, arg1, arg2, nodeptr);
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 7F0754BC.
|
|
* Copy animation from ROM to RAM
|
|
*/
|
|
#ifdef PORT
|
|
u8 *loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused)
|
|
#else
|
|
s32 loadAnimationFrame(ModelAnimation* anim, s32 frame, ModelSkeleton* unused)
|
|
#endif
|
|
{
|
|
#ifdef PORT
|
|
/* D457: the result is a frame-buffer address (host pointer), and the copy
|
|
* destination is computed at pointer width (was s32 ret / u32 dest). */
|
|
u8 *ret;
|
|
s32 source;
|
|
s32 frameSize;
|
|
uintptr_t dest;
|
|
u32 size;
|
|
|
|
ret = NULL;
|
|
#else
|
|
s32 ret;
|
|
s32 source;
|
|
s32 frameSize;
|
|
u32 dest;
|
|
u32 size;
|
|
|
|
ret = 0;
|
|
#endif
|
|
frameSize = anim->unk0E >> 3; // divide by 8
|
|
|
|
if (anim->address & 0x80000000) // If animation's address is in RAM
|
|
{
|
|
// Load that frame from RAM
|
|
#ifdef PORT
|
|
ret = (u8 *)(uintptr_t)(u32)(anim->address + (frame * frameSize));
|
|
#else
|
|
ret = anim->address + (frame * frameSize);
|
|
#endif
|
|
}
|
|
else if (D_80036414 != NULL) // should never be NULL after initAnimationsBuffer is called
|
|
{
|
|
// Get dest from this D_80036414 which points to an array. Align to 16 bytes.
|
|
#ifdef PORT
|
|
dest = ((uintptr_t) (D_80036414->animBufferPtr2 + 15) >> 4) * 16;
|
|
ret = (u8 *)dest;
|
|
#else
|
|
dest = ((u32) (D_80036414->animBufferPtr2 + 15) >> 4) * 16;
|
|
ret = dest;
|
|
#endif
|
|
|
|
// Get source of this animation in ROM with the offset of the frame we'll load
|
|
source = anim->address + (frame * frameSize);
|
|
if (source & 1)
|
|
{
|
|
source--;
|
|
frameSize++;
|
|
ret++;
|
|
}
|
|
|
|
// Size of frame but 16-bytes aligned. Observed to be 80 bytes. Might differ for non-guards.
|
|
size = ((u32) (frameSize + 15) >> 4) * 16;
|
|
|
|
// This copies one animation frame from ROM to the destination in RAM
|
|
romCopy((void* ) dest, (void* ) source, size);
|
|
|
|
// Increment this which serves nothing
|
|
D_80036414->uselessPointer += 1;
|
|
|
|
// Set this to point to the end of the copied frame
|
|
// This allows to copy another frame after this one
|
|
#ifdef PORT
|
|
D_80036414->animBufferPtr2 = (char *)(dest + size);
|
|
#else
|
|
D_80036414->animBufferPtr2 = dest + size;
|
|
#endif
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Address 7F0755B0.
|
|
*/
|
|
void modelResetAnimationsScratchBuffer(void)
|
|
{
|
|
if (D_80036414 != NULL) // should never be NULL after initAnimationsBuffer is called
|
|
{
|
|
// Reset the pointer to point to the start of the array
|
|
D_80036414->animBufferPtr2 = D_80036414->animBufferPtr1;
|
|
D_80036414->uselessPointer = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PORT
|
|
/* D457: rebase at pointer width. The N64 form wraps the sum at 32 bits
|
|
* (`(u32)var + diff`), which truncates any file base at or above 4 GiB.
|
|
* `diff` is now an intptr_t (fileramaddr - vma) and `var` is read at full
|
|
* width (model slots hold the raw vma zero-extended). */
|
|
#define PROMOTE(var) \
|
|
if (var) \
|
|
var = (void *)((uintptr_t)var + diff)
|
|
#else
|
|
#define PROMOTE(var) \
|
|
if (var) \
|
|
var = (void *)((u32)var + diff)
|
|
#endif
|
|
|
|
#ifdef PORT
|
|
/* PC port (D43/D45): Vertex.LinkedTo is a raw vma (u32), not a pointer —
|
|
* rebases the same way, keeps sizeof(Vertex) at 16 bytes. */
|
|
#define PROMOTE32(var) \
|
|
if (var) \
|
|
var = (u32)((u32)var + diff)
|
|
#endif
|
|
|
|
#ifdef PORT
|
|
void modelPromoteNodeOffsetsToPointers(ModelNode *node, u32 vma, uintptr_t fileramaddr)
|
|
{
|
|
intptr_t diff = (intptr_t)(fileramaddr - (uintptr_t)vma);
|
|
#else
|
|
void modelPromoteNodeOffsetsToPointers(ModelNode *node, u32 vma, u32 fileramaddr)
|
|
{
|
|
s32 diff = fileramaddr - vma;
|
|
#endif
|
|
s32 i;
|
|
|
|
while (node)
|
|
{
|
|
u32 type = node->Opcode & 0xff;
|
|
|
|
PROMOTE(node->Data);
|
|
PROMOTE(node->Parent);
|
|
PROMOTE(node->Next);
|
|
PROMOTE(node->Prev);
|
|
PROMOTE(node->Child);
|
|
|
|
switch (type)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
{
|
|
ModelRoData_HeaderRecord* rodata = &node->Data->Header;
|
|
PROMOTE(rodata->FirstGroup);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_GROUP:
|
|
{
|
|
ModelRoData_GroupRecord* rodata = &node->Data->Group;
|
|
PROMOTE(rodata->ChildGroup);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP03:
|
|
{
|
|
ModelRoData_GroupRecord* rodata = &node->Data->Group;
|
|
PROMOTE(rodata->ChildGroup);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_DL:
|
|
{
|
|
ModelRoData_DisplayListRecord* rodata = &node->Data->DisplayList;
|
|
PROMOTE(rodata->Vertices);
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
{
|
|
ModelRoData_DisplayList_CollisionRecord* rodata = &node->Data->DisplayListCollisions;
|
|
PROMOTE(rodata->Vertices);
|
|
PROMOTE(rodata->CollisionVertices);
|
|
PROMOTE(rodata->PointUsage);
|
|
for (i = 0; i < rodata->numCollisionVertices; i++)
|
|
{
|
|
#ifdef PORT
|
|
PROMOTE32(rodata->CollisionVertices[i].LinkedTo);
|
|
#else
|
|
PROMOTE(rodata->CollisionVertices[i].LinkedTo);
|
|
#endif
|
|
}
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP20:
|
|
{
|
|
ModelRoData_HeaderRecord* rodata = &node->Data->Header;
|
|
PROMOTE(rodata->FirstGroup);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP05:
|
|
{
|
|
ModelRoData_Op05Record* rodata = &node->Data->Op05;
|
|
|
|
// shared with op07
|
|
PROMOTE(rodata->Children);
|
|
PROMOTE(rodata->Vertices);
|
|
PROMOTE(rodata->Images);
|
|
for (i = 0; i < rodata->NumChildren; i++)
|
|
{
|
|
PROMOTE(rodata->Children[i].unk04);
|
|
}
|
|
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP07:
|
|
{
|
|
ModelRoData_Op07Record* rodata = &node->Data->Op07;
|
|
PROMOTE(rodata->unk00);
|
|
PROMOTE(rodata->unk04);
|
|
|
|
// shared with op05
|
|
PROMOTE(rodata->Children);
|
|
PROMOTE(rodata->Vertices);
|
|
PROMOTE(rodata->Images);
|
|
for (i = 0; i < rodata->NumChildren; i++)
|
|
{
|
|
PROMOTE(rodata->Children[i].unk04);
|
|
}
|
|
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP06:
|
|
{
|
|
ModelRoData_Op06Record* rodata = &node->Data->Op06;
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_LOD:
|
|
{
|
|
ModelRoData_LODRecord* rodata = &node->Data->LOD;
|
|
PROMOTE(rodata->Affects);
|
|
node->Child = rodata->Affects;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
{
|
|
ModelRoData_SwitchRecord* rodata = &node->Data->Switch;
|
|
PROMOTE(rodata->Controls);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_BSP:
|
|
{
|
|
ModelRoData_BSPRecord* rodata = &node->Data->BSP;
|
|
PROMOTE(rodata->leftChild);
|
|
PROMOTE(rodata->rightChild);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP17:
|
|
{
|
|
ModelRoData_GroupRecord* rodata = &node->Data->Group;
|
|
PROMOTE(rodata->ChildGroup);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP11:
|
|
{
|
|
ModelRoData_Op11Record* rodata = &node->Data->Op11;
|
|
PROMOTE(rodata->unk0c[15]);
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_GUNFIRE:
|
|
{
|
|
ModelRoData_GunfireRecord* rodata = &node->Data->Gunfire;
|
|
PROMOTE(rodata->Image);
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_SHADOW:
|
|
{
|
|
ModelRoData_ShadowRecord* rodata = &node->Data->Shadow;
|
|
PROMOTE(rodata->image);
|
|
PROMOTE(rodata->Header);
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_DLPRIMARY:
|
|
{
|
|
ModelRoData_DisplayListPrimaryRecord* rodata = &node->Data->DisplayListPrimary;
|
|
PROMOTE(rodata->Vertices);
|
|
rodata->BaseAddr = (void *)fileramaddr;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (node->Child)
|
|
{
|
|
node = node->Child;
|
|
}
|
|
else
|
|
{
|
|
while (node)
|
|
{
|
|
if (node->Next)
|
|
{
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Address 7F075A90.
|
|
*/
|
|
#ifdef PORT
|
|
void sub_GAME_7F075A90(ModelFileHeader *header, s32 vma, uintptr_t addr) {
|
|
intptr_t diff = (intptr_t)(addr - (uintptr_t)(u32)vma);
|
|
#else
|
|
void sub_GAME_7F075A90(ModelFileHeader *header, s32 vma, u32 addr) {
|
|
s32 diff = addr - vma;
|
|
#endif
|
|
s32 i;
|
|
|
|
for(i = 0;i < header->numSwitches;i++)
|
|
{
|
|
PROMOTE(header->Switches[i]);
|
|
}
|
|
modelPromoteNodeOffsetsToPointers(header->RootNode, vma, addr);
|
|
}
|
|
|
|
/**
|
|
* unreferenced
|
|
* Address 7F075B08.
|
|
*/
|
|
void REMOVED_sub_GAME_7F075B08(s32 param_1,s32 param_2,s32 param_3,s32 param_4)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
s32 modelCalculateRwDataIndexes(ModelNode *basenode)
|
|
{
|
|
u16 len = 0;
|
|
ModelNode *node = basenode;
|
|
union ModelRoData *rodata;
|
|
|
|
while (node)
|
|
{
|
|
u32 type = node->Opcode & 0xff;
|
|
|
|
switch (type)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
if (1)
|
|
{
|
|
ModelRoData_HeaderRecord *rodata = &node->Data->Header;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_HeaderRecord) / 4;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_OP07:
|
|
if (1)
|
|
{
|
|
ModelRoData_Op07Record *rodata = &node->Data->Op07;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_Op07Record) / 4;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_LOD:
|
|
if (1)
|
|
{
|
|
ModelRoData_LODRecord *rodata = &node->Data->LOD;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_LODRecord) / 4;
|
|
node->Child = rodata->Affects;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
if (1)
|
|
{
|
|
ModelRoData_SwitchRecord *rodata = &node->Data->Switch;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_SwitchRecord) / 4;
|
|
node->Child = rodata->Controls;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_HEAD:
|
|
if (1)
|
|
{
|
|
ModelRoData_HeadPlaceholderRecord *rodata = &node->Data->HeadPlaceholder;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_HeadPlaceholderRecord) / 4;
|
|
node->Child = NULL;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_BSP:
|
|
if (1)
|
|
{
|
|
ModelRoData_BSPRecord *rodata = &node->Data->BSP;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_BSPRecord) / 4;
|
|
modelApplyReorderRelationsByArg(node, FALSE);
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_OP11:
|
|
if (1)
|
|
{
|
|
ModelRoData_Op11Record *rodata = &node->Data->Op11;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_Op11Record) / 4;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_GUNFIRE:
|
|
if (1)
|
|
{
|
|
ModelRoData_GunfireRecord *rodata = &node->Data->Gunfire;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_GunfireRecord) / 4;
|
|
break;
|
|
}
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
if (1)
|
|
{
|
|
ModelRoData_DisplayList_CollisionRecord *rodata = &node->Data->DisplayListCollisions;
|
|
rodata->RwDataIndex = len;
|
|
len += sizeof(struct ModelRwData_DisplayList_CollisionRecord) / 4;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (node->Child)
|
|
{
|
|
node = node->Child;
|
|
}
|
|
else
|
|
{
|
|
while (node)
|
|
{
|
|
if (node == basenode->Parent)
|
|
{
|
|
node = NULL;
|
|
break;
|
|
}
|
|
|
|
if (node->Next)
|
|
{
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
void modelCalculateRwDataLen(struct ModelFileHeader *objheader)
|
|
{
|
|
#if defined(LEFTOVERDEBUG)
|
|
objheader->isLoaded = 1;
|
|
#endif
|
|
objheader->numRecords = modelCalculateRwDataIndexes(objheader->RootNode);
|
|
}
|
|
|
|
|
|
void modelInitRwData(Model *model, ModelNode *startnode)
|
|
{
|
|
ModelNode *node = startnode;
|
|
|
|
while (node)
|
|
{
|
|
u32 type = node->Opcode & 0xFF;
|
|
|
|
switch (type)
|
|
{
|
|
case MODELNODE_OPCODE_HEADER:
|
|
if (1)
|
|
{
|
|
ModelRwData_HeaderRecord* rwdata = &modelGetNodeRwData(model, node)->Header;
|
|
|
|
rwdata->unk00 = 0;
|
|
rwdata->ground = 0;
|
|
rwdata->pos.x = 0;
|
|
rwdata->pos.y = 0;
|
|
rwdata->pos.z = 0;
|
|
rwdata->unk14 = 0;
|
|
rwdata->unk18 = 0;
|
|
rwdata->unk1c = 0;
|
|
|
|
rwdata->unk01 = 0;
|
|
rwdata->unk34.x = 0;
|
|
rwdata->unk34.y = 0;
|
|
rwdata->unk34.z = 0;
|
|
rwdata->unk30 = 0;
|
|
rwdata->unk24.x = 0;
|
|
rwdata->unk24.y = 0;
|
|
rwdata->unk24.z = 0;
|
|
rwdata->unk20 = 0;
|
|
|
|
rwdata->unk02 = 0;
|
|
rwdata->unk4c.x = 0;
|
|
rwdata->unk4c.y = 0;
|
|
rwdata->unk4c.z = 0;
|
|
rwdata->unk40.x = 0;
|
|
rwdata->unk40.y = 0;
|
|
rwdata->unk40.z = 0;
|
|
rwdata->unk5c = 0;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP07:
|
|
if (1)
|
|
{
|
|
ModelRwData_Op07Record* rwdata = &modelGetNodeRwData(model, node)->Op07;
|
|
rwdata->index = 0;
|
|
break;
|
|
}
|
|
|
|
|
|
case MODELNODE_OPCODE_LOD:
|
|
if (1)
|
|
{
|
|
ModelRoData_LODRecord* rodata = &node->Data->LOD;
|
|
ModelRwData_LODRecord* rwdata = &modelGetNodeRwData(model, node)->LOD;
|
|
rwdata->visible = FALSE;
|
|
node->Child = rodata->Affects;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
if (1)
|
|
{
|
|
ModelRoData_SwitchRecord* rodata = &node->Data->Switch;
|
|
ModelRwData_SwitchRecord* rwdata = &modelGetNodeRwData(model, node)->Switch;
|
|
rwdata->visible = TRUE;
|
|
node->Child = rodata->Controls;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_HEAD:
|
|
if (1)
|
|
{
|
|
ModelRwData_HeadPlaceholderRecord* rwdata = &modelGetNodeRwData(model, node)->HeadPlaceholder;
|
|
rwdata->ModelFileHeader = NULL;
|
|
rwdata->RwDatas = NULL;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_BSP:
|
|
if (1)
|
|
{
|
|
ModelRwData_BSPRecord* rwdata = &modelGetNodeRwData(model, node)->BSP;
|
|
rwdata->visible = FALSE;
|
|
modelApplyReorderRelations(model, node);
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_OP11:
|
|
if (1)
|
|
{
|
|
ModelRwData_Op11Record* rwdata = &modelGetNodeRwData(model, node)->Op11;
|
|
rwdata->unk00 = FALSE;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_GUNFIRE:
|
|
if (1)
|
|
{
|
|
ModelRwData_GunfireRecord* rwdata = &modelGetNodeRwData(model, node)->Gunfire;
|
|
rwdata->visible = FALSE;
|
|
break;
|
|
}
|
|
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
if (1)
|
|
{
|
|
ModelRoData_DisplayList_CollisionRecord* rodata = &node->Data->DisplayListCollisions;
|
|
ModelRwData_DisplayList_CollisionRecord* rwdata = &modelGetNodeRwData(model, node)->DisplayListCollisions;
|
|
rwdata->Vertices = rodata->Vertices;
|
|
rwdata->gdl = rodata->Primary;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (node->Child)
|
|
{
|
|
node = node->Child;
|
|
}
|
|
else
|
|
{
|
|
while (node)
|
|
{
|
|
if (node == startnode->Parent)
|
|
{
|
|
node = NULL;
|
|
break;
|
|
}
|
|
|
|
if (node->Next)
|
|
{
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void modelInit(struct Model *objinst, struct ModelFileHeader *header, u32 *data)
|
|
{
|
|
objinst->obj = header;
|
|
objinst->datas = data;
|
|
objinst->rwdatalen = -1;
|
|
objinst->attachedto = NULL;
|
|
objinst->attachedto_objinst = NULL;
|
|
objinst->scale = 1.0;
|
|
modelInitRwData(objinst, header->RootNode);
|
|
}
|
|
|
|
|
|
// PD: animInit
|
|
void animInit(struct Model *objinst, struct ModelFileHeader *header, u32 *data)
|
|
{
|
|
modelInit(objinst, header, data);
|
|
objinst->anim = NULL;
|
|
objinst->anim2 = NULL;
|
|
objinst->animlooping = 0;
|
|
objinst->animflipfunc = 0;
|
|
objinst->unk9c = 0;
|
|
objinst->unka0 = 0;
|
|
objinst->unk2c = 0.0f;
|
|
objinst->timespeed = 0.0f;
|
|
objinst->unk5c = 0.0f;
|
|
objinst->unk7c = 0.0f;
|
|
objinst->unk84 = 0.0f;
|
|
objinst->unk88 = 0.0f;
|
|
objinst->unkb0 = 0.0f;
|
|
objinst->speed = 1.0f;
|
|
objinst->speed2 = 1.0f;
|
|
objinst->playspeed = 1.0f;
|
|
objinst->anim_translation_scale = 1.0f;
|
|
objinst->endframe = -1.0f;
|
|
objinst->unk6c = -1.0f;
|
|
}
|
|
|
|
|
|
// PD: model00023108
|
|
void modelAttachPart(Model *pmodel, ModelFileHeader *pmodeldef, ModelNode *pnode, ModelFileHeader *cmodeldef)
|
|
{
|
|
ModelRwData_HeadPlaceholderRecord *rwdata = modelGetNodeRwData(pmodel, pnode);
|
|
ModelNode *node;
|
|
|
|
rwdata->ModelFileHeader = cmodeldef;
|
|
#ifdef PORT
|
|
/* D52: word-indexed pool offset — see bondtypes.h struct Model.datas. */
|
|
rwdata->RwDatas = (void *)&pmodel->datas[pmodeldef->numRecords];
|
|
#else
|
|
rwdata->RwDatas = &pmodel->datas[pmodeldef->numRecords];
|
|
#endif
|
|
|
|
pnode->Child = cmodeldef->RootNode;
|
|
|
|
node = pnode->Child;
|
|
|
|
while (node)
|
|
{
|
|
node->Parent = pnode;
|
|
node = node->Next;
|
|
}
|
|
|
|
pmodeldef->numRecords += modelCalculateRwDataIndexes(pnode->Child);
|
|
}
|
|
|
|
|
|
/**
|
|
* This function can be called repeatedly to iterate a model's display lists.
|
|
*
|
|
* On the first call, the value passed as nodeptr should point to a NULL value.
|
|
* Each time the function is called, it will update *gdlptr to point to the next
|
|
* display list, and will update *nodeptr to point to the current node. On
|
|
* subsequent calls, the same values should be passed as nodeptr and gdlptr so
|
|
* the function can continue correctly.
|
|
*
|
|
* Note that some node types support multiple display lists, so the function
|
|
* may return the same node while it iterates the display lists for that node.
|
|
*/
|
|
void modelIterateDisplayLists(ModelFileHeader *fileheader, ModelNode **nodeptr, Gfx **gdlptr)
|
|
{
|
|
ModelNode *node = *nodeptr;
|
|
union ModelRoData *rodata;
|
|
Gfx *gdl = NULL;
|
|
|
|
if (node == NULL)
|
|
{
|
|
node = fileheader->RootNode;
|
|
}
|
|
|
|
while (node)
|
|
{
|
|
u32 type = node->Opcode & 0xff;
|
|
|
|
switch (type)
|
|
{
|
|
case MODELNODE_OPCODE_DL:
|
|
rodata = node->Data;
|
|
|
|
if (node != *nodeptr)
|
|
{
|
|
gdl = rodata->DisplayList.Primary;
|
|
}
|
|
else if (rodata->DisplayList.Secondary != *gdlptr)
|
|
{
|
|
gdl = rodata->DisplayList.Secondary;
|
|
}
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
rodata = node->Data;
|
|
|
|
if (node != *nodeptr)
|
|
{
|
|
gdl = rodata->DisplayListCollisions.Primary;
|
|
}
|
|
else if (rodata->DisplayListCollisions.Secondary != *gdlptr)
|
|
{
|
|
gdl = rodata->DisplayListCollisions.Secondary;
|
|
}
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_DLPRIMARY:
|
|
rodata = node->Data;
|
|
|
|
if (node != *nodeptr)
|
|
{
|
|
gdl = rodata->DisplayListPrimary.Primary;
|
|
}
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_LOD:
|
|
rodata = node->Data;
|
|
node->Child = rodata->LOD.Affects;
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_SWITCH:
|
|
rodata = node->Data;
|
|
node->Child = rodata->Switch.Controls;
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_BSP:
|
|
modelApplyReorderRelationsByArg(node, TRUE);
|
|
break;
|
|
}
|
|
|
|
if (gdl) { break; }
|
|
|
|
if (node->Child)
|
|
{
|
|
node = node->Child;
|
|
}
|
|
else
|
|
{
|
|
while (node)
|
|
{
|
|
if (node->Next)
|
|
{
|
|
node = node->Next;
|
|
break;
|
|
}
|
|
|
|
node = node->Parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
*gdlptr = gdl;
|
|
*nodeptr = node;
|
|
}
|
|
|
|
|
|
void modelNodeReplaceGdl(u32 arg0, ModelNode *node, Gfx *find, Gfx *replacement)
|
|
{
|
|
union ModelRoData *rodata;
|
|
u32 type = node->Opcode & 0xff;
|
|
|
|
switch (type) {
|
|
case MODELNODE_OPCODE_DL:
|
|
rodata = node->Data;
|
|
|
|
if (rodata->DisplayList.Primary == find)
|
|
{
|
|
rodata->DisplayList.Primary = replacement;
|
|
return;
|
|
}
|
|
|
|
if (rodata->DisplayList.Secondary == find)
|
|
{
|
|
rodata->DisplayList.Secondary = replacement;
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_DLCOLLISION:
|
|
rodata = node->Data;
|
|
|
|
if (rodata->DisplayListCollisions.Primary == find)
|
|
{
|
|
rodata->DisplayListCollisions.Primary = replacement;
|
|
return;
|
|
}
|
|
|
|
if (rodata->DisplayListCollisions.Secondary == find)
|
|
{
|
|
rodata->DisplayListCollisions.Secondary = replacement;
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case MODELNODE_OPCODE_DLPRIMARY:
|
|
rodata = node->Data;
|
|
|
|
if (rodata->DisplayListPrimary.Primary == find)
|
|
{
|
|
rodata->DisplayListPrimary.Primary = replacement;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|