Manual vertex velocity computation. (#46)

* Update RT64 for vertex velocity segment support.

* Switch to using extended velocity data for CPU skinning.

* Integrate actor tracking into vertex velocity calculation

* Add vertex velocity calculation to overlay draws (e.g. Clanker)

---------

Co-authored-by: Mr-Wiseguy <mrwiseguyromhacking@gmail.com>
This commit is contained in:
Darío
2026-01-06 22:21:15 -03:00
committed by GitHub
parent ab9e6440cd
commit 768de1bda4
6 changed files with 133 additions and 36 deletions
+9
View File
@@ -14,6 +14,9 @@ extern struct {
void (*unk14)(s32, s32);
} sOverlay;
void recomp_setup_overlay_skinning(int overlay_id);
void recomp_clear_overlay_skinning();
// @recomp: Patched to assign an ID to Clanker.
RECOMP_PATCH void func_80322E64(Gfx **gfx, Mtx **mtx, Vtx **vtx){
// @recomp If the current level is Clanker's Cavern, set up the model ID for Clanker.
@@ -22,8 +25,14 @@ RECOMP_PATCH void func_80322E64(Gfx **gfx, Mtx **mtx, Vtx **vtx){
cur_drawn_model_transform_id = CLANKER_TRANSFORM_ID_START;
}
// @recomp Setup the overlay skinning data.
recomp_setup_overlay_skinning(overlayManagergetLoadedId());
if(sOverlay.draw)
sOverlay.draw(gfx, mtx, vtx);
// @recomp Clear the overlay skinning setup.
recomp_clear_overlay_skinning();
// @recomp Reset the current transform ID.
cur_drawn_model_transform_id = prev_transform_id;
+4
View File
@@ -11,6 +11,7 @@ void animMtxList_setBoned(AnimMtxList **this_ptr, BKAnimationList *anim_list, Bo
extern s32 cur_model_would_have_been_culled_in_demo;
bool recomp_in_demo_playback_game_mode();
void recomp_setup_marker_skinning(ActorMarker *marker);
int frustum_checks_enabled = TRUE;
void set_frustum_checks_enabled(int enabled) {
@@ -188,6 +189,9 @@ RECOMP_PATCH void actor_predrawMethod(Actor *this){
}
if(this->unkF4_30){
// @recomp Set up skinning data for this actor.
recomp_setup_marker_skinning(this->marker);
sp40 = func_80330C74(this);
if(this->unk138_29){
sp34[0] = this->pitch;
+5
View File
@@ -10,12 +10,17 @@
RECOMP_DECLARE_EVENT(recomp_on_init());
void recomp_init_vertex_skinning(void);
// @recomp Patched to perform some initialization after core2 has been loaded.
RECOMP_PATCH void dummy_func_8025AFB0(void) {
// @recomp Initialize note saving data before the init event is run.
// This will allow mods to override it as necessary.
init_note_saving();
// @recomp Perform the necessary initialization for vertex skinning.
recomp_init_vertex_skinning();
// @recomp Register actor extension data and call the init event.
recomp_on_init();
+3
View File
@@ -74,6 +74,9 @@ void osWriteBackDCacheAll(void);
#define gEXMatrixGroupDecomposedNormal(cmd, id, push, proj, edit) \
gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_AUTO)
#define gEXMatrixGroupDecomposedNormalTcs(cmd, id, push, proj, edit) \
gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_AUTO)
#define gEXMatrixGroupDecomposedSkipRot(cmd, id, push, proj, edit) \
gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_AUTO)
+111 -35
View File
@@ -35,6 +35,7 @@ s32 cur_drawn_model_skip_interpolation = FALSE;
s32 cur_model_transform_id_offset = 0;
s32 cur_model_uses_bones = FALSE;
s32 cur_model_would_have_been_culled_in_demo = FALSE;
s32 cur_model_uses_ex_vertex = FALSE;
Mtx identity_fixed_mtx = {{
{
@@ -211,24 +212,18 @@ bool set_model_matrix_group(Gfx **gfx, void *geo_list, bool skip_rotation) {
// Skip interpolation if all interpolation is currently skipped or the transform was specified to be skipped.
gEXMatrixGroupSkipAll((*gfx)++, group_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
else if (cur_model_uses_bones) {
// Tag the matrix with simple matrix interpolation if the model uses bones.
if (skip_rotation) {
gEXMatrixGroupSimpleVertsSkipRot((*gfx)++, group_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
else {
gEXMatrixGroupSimpleVerts((*gfx)++, group_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
}
else {
// Tag the matrix with decomposed matrix interpolation on any other model.
if (skip_rotation) {
gEXMatrixGroupDecomposedVertsSkipRot((*gfx)++, group_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
else {
gEXMatrixGroupDecomposedVerts((*gfx)++, group_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
// Tag the matrix with simple matrix interpolation if the model uses bones.
// Use decomposed matrix interpolation on any other model.
u8 interpolation_mode = cur_model_uses_bones ? G_EX_INTERPOLATE_SIMPLE : G_EX_INTERPOLATE_DECOMPOSE;
u8 rotation_mode = skip_rotation ? G_EX_COMPONENT_SKIP : G_EX_COMPONENT_INTERPOLATE;
u8 vertex_interpolation_mode = cur_drawn_model_is_map ? G_EX_COMPONENT_INTERPOLATE : G_EX_COMPONENT_SKIP;
u8 texcoord_interpolation_mode = cur_drawn_model_is_map ? G_EX_COMPONENT_INTERPOLATE : G_EX_COMPONENT_SKIP;
gEXMatrixGroup((*gfx)++, group_id, interpolation_mode, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_COMPONENT_INTERPOLATE, rotation_mode,
G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, vertex_interpolation_mode, G_EX_COMPONENT_INTERPOLATE,
G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, G_EX_ASPECT_AUTO, texcoord_interpolation_mode, G_EX_COMPONENT_AUTO);
}
return TRUE;
}
else if (skip_all_interpolation) {
@@ -451,31 +446,79 @@ RECOMP_PATCH void func_80339124(Gfx ** gfx, Mtx ** mtx, BKGeoList *geo_list){
}while(1);
}
typedef struct {
u32 frameCount;
u32 modelId;
u32 floatStart;
} ModelSkinningData;
// @recomp Applies CPU skinning and saves the result to a higher precision vertex buffer.
#define SKINNING_POSITIONS_MAX 65536
f32 sSkinningFloats[SKINNING_POSITIONS_MAX] __attribute__((aligned(8)));
f32 sSkinningPosFloats[2][SKINNING_POSITIONS_MAX] __attribute__((aligned(8)));
f32 sSkinningVelFloats[SKINNING_POSITIONS_MAX] __attribute__((aligned(8)));
u32 sSkinningFloatCount;
u32 sSkinningFloatFrame;
MarkerExtensionId sMarkerSkinningExtensionId;
ModelSkinningData *sCurModelSkinningData;
u32 sCurModelId;
ModelSkinningData sOverlaySkinningData;
void recomp_setup_overlay_skinning(int overlay_id) {
sCurModelSkinningData = &sOverlaySkinningData;
sCurModelId = overlay_id; // Using the overlay ID as the model ID since there's no easy way to get the model ID for overlay draws.
}
void recomp_clear_overlay_skinning() {
sCurModelSkinningData = NULL;
sCurModelId = 0;
}
void recomp_reset_skinning_stack() {
sSkinningFloatCount = 0;
sSkinningFloatFrame++;
}
float *recomp_apply_cpu_skinning(BKModelUnk28List *arg0, BKVertexList *arg1, AnimMtxList *mtx_list) {
void recomp_init_vertex_skinning() {
sMarkerSkinningExtensionId = bkrecomp_extend_marker_all(sizeof(ModelSkinningData));
}
void recomp_setup_marker_skinning(ActorMarker *marker) {
sCurModelSkinningData = bkrecomp_get_extended_marker_data(marker, sMarkerSkinningExtensionId);
sCurModelId = marker->modelId;
}
bool recomp_apply_cpu_skinning(BKModelUnk28List *arg0, BKVertexList *arg1, AnimMtxList *mtx_list, float **pos, float **vel) {
*pos = NULL;
*vel = NULL;
ModelSkinningData prev_skinning_data = {0};
u32 cur_model_id = sCurModelId;
if (sCurModelSkinningData != NULL) {
prev_skinning_data = *sCurModelSkinningData;
sCurModelSkinningData->frameCount = sSkinningFloatFrame;
sCurModelSkinningData->modelId = cur_model_id;
sCurModelSkinningData->floatStart = sSkinningFloatCount;
}
sCurModelSkinningData = NULL;
sCurModelId = 0;
if (sSkinningFloatCount + (arg1->count * 3) > SKINNING_POSITIONS_MAX) {
return NULL;
return FALSE;
}
// Copy unmodified positions.
float *dst_pos = &sSkinningFloats[sSkinningFloatCount];
s32 i, j;
float *dst_pos = &sSkinningPosFloats[sSkinningFloatFrame & 0x1][sSkinningFloatCount];
s32 i, j = 0;
for (i = 0; i < arg1->count; i++) {
sSkinningFloats[sSkinningFloatCount++] = arg1->vtx_18[i].v.ob[0];
sSkinningFloats[sSkinningFloatCount++] = arg1->vtx_18[i].v.ob[1];
sSkinningFloats[sSkinningFloatCount++] = arg1->vtx_18[i].v.ob[2];
dst_pos[j++] = arg1->vtx_18[i].v.ob[0];
dst_pos[j++] = arg1->vtx_18[i].v.ob[1];
dst_pos[j++] = arg1->vtx_18[i].v.ob[2];
}
// Always align to multiples of 2.
// Increase the current float count. Always align to multiples of 2.
sSkinningFloatCount += arg1->count * 3;
if (sSkinningFloatCount & 0x1) {
sSkinningFloatCount++;
}
@@ -507,7 +550,25 @@ float *recomp_apply_cpu_skinning(BKModelUnk28List *arg0, BKVertexList *arg1, Ani
i_ptr = (BKModelUnk28 *)((s16 *)(i_ptr + 1) + (i_ptr->vtx_count - 1));
}
return dst_pos;
// Compute velocities if applicable.
// To apply, the frame index stored on the extended marker data must be exactly the previous frame and the model ID must match.
if (prev_skinning_data.frameCount == sSkinningFloatFrame - 1 && prev_skinning_data.modelId == cur_model_id) {
const float *prev_pos = &sSkinningPosFloats[(sSkinningFloatFrame & 0x1) ^ 1][prev_skinning_data.floatStart];
float *dst_vel = &sSkinningVelFloats[sSkinningFloatCount];
j = 0;
for (i = 0; i < arg1->count; i++) {
for (s32 k = 0; k < 3; k++) {
dst_vel[j] = dst_pos[j] - prev_pos[j];
j++;
}
}
*vel = dst_vel;
}
// Return the vertex float segments that should be used.
*pos = dst_pos;
return TRUE;
}
@@ -765,15 +826,22 @@ RECOMP_PATCH BKModelBin *modelRender_draw(Gfx **gfx, Mtx **mtx, f32 position[3],
}
// @recomp Use higher precision vertex buffer when the model requires CPU skinning.
bool ex_vertex_components_used = FALSE;
cur_model_uses_ex_vertex = FALSE;
if(model_bin->unk28 != NULL && D_8038371C != NULL){
func_802E6BD0((u8*)modelRenderModelBin + modelRenderModelBin->unk28, modelRendervertexList, D_8038371C);
// @recomp Do the skinning again on a high precision version of the vertex buffer. Force its usage for any subsequent display lists.
f32 *skinned_pos = recomp_apply_cpu_skinning((u8 *)modelRenderModelBin + modelRenderModelBin->unk28, modelRendervertexList, D_8038371C);
if (skinned_pos != NULL) {
f32 *skinned_pos, *skinned_vel;
if (recomp_apply_cpu_skinning((u8 *)modelRenderModelBin + modelRenderModelBin->unk28, modelRendervertexList, D_8038371C, &skinned_pos, &skinned_vel)) {
gEXSetVertexSegment((*gfx)++, G_EX_VERTEX_POSITION, G_EX_ENABLED, skinned_pos, &modelRendervertexList->vtx_18);
ex_vertex_components_used = TRUE;
if (skinned_vel != NULL) {
gEXSetVertexSegment((*gfx)++, G_EX_VERTEX_VELOCITY, G_EX_ENABLED, skinned_vel, &modelRendervertexList->vtx_18);
}
else {
gEXSetVertexSegment((*gfx)++, G_EX_VERTEX_VELOCITY, G_EX_DISABLED, 0, 0);
}
cur_model_uses_ex_vertex = TRUE;
}
}
@@ -800,14 +868,21 @@ RECOMP_PATCH BKModelBin *modelRender_draw(Gfx **gfx, Mtx **mtx, f32 position[3],
gSPMatrix((*gfx)++, (*mtx)++, G_MTX_PUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
// @recomp Create a matrix group if a transform id is set.
bool pushed_matrix_group = FALSE;
if (skip_all_interpolation || cur_drawn_model_skip_interpolation) {
// @recomp Skip interpolation if all interpolation is currently skipped or the transform was specified to be skipped.
gEXMatrixGroupSkipAll((*gfx)++, cur_drawn_model_transform_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
pushed_matrix_group = TRUE;
}
else if (cur_drawn_model_is_map) {
gEXMatrixGroupDecomposedVerts((*gfx)++, cur_drawn_model_transform_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
pushed_matrix_group = TRUE;
}
else if (cur_drawn_model_transform_id != 0) {
gEXMatrixGroupDecomposedVerts((*gfx)++, cur_drawn_model_transform_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
gEXMatrixGroupDecomposedNormalTcs((*gfx)++, cur_drawn_model_transform_id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
pushed_matrix_group = TRUE;
}
modelRenderScale = scale;
if(rotation){
modelRenderRotation[0] = rotation[0];
@@ -833,13 +908,14 @@ RECOMP_PATCH BKModelBin *modelRender_draw(Gfx **gfx, Mtx **mtx, f32 position[3],
gSPPopMatrix((*gfx)++, G_MTX_MODELVIEW);
// @recomp Pop the matrix group if a transform id is set.
if (cur_drawn_model_transform_id != 0) {
if (pushed_matrix_group) {
gEXPopMatrixGroup((*gfx)++, G_MTX_MODELVIEW);
}
// @recomp Clear use of high precision vertex buffers.
if (ex_vertex_components_used) {
if (cur_model_uses_ex_vertex) {
gEXSetVertexSegment((*gfx)++, G_EX_VERTEX_POSITION, G_EX_DISABLED, 0, 0);
gEXSetVertexSegment((*gfx)++, G_EX_VERTEX_VELOCITY, G_EX_DISABLED, 0, 0);
}
if(modelRenderCallback.post_method != NULL){