mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-10-13 15:33:53 -04:00
Interpolate skinned limb vertices (horse body and legs) (#7282)
Matrix interpolation cannot cover SKIN_LIMB_TYPE_ANIMATED limbs: their vertices are deformed on the CPU and referenced through gSPSegment(0x08, ...) without any recorded matrix, so recorded matrix interpolation never moves them. Record each skinned limb's pose (double-buffered, keyed by the vertex entry) and blend the previous and current poses for every displayed frame, right before the graphics commands are run. Ties the blend to a logical-frame counter so a limb that was skipped for a frame falls back to its exact pose instead of blending against a stale one.
This commit is contained in:
@@ -1785,6 +1785,8 @@ void RunCommands(Gfx* Commands, int time, int step, int denom, int count) {
|
||||
(time == denom) ? std::unordered_map<Mtx*, MtxF>() : FrameInterpolation_Interpolate(interpolationStep);
|
||||
// Ribbon vertices are shared by every frame of a tick: re-blend them before each run.
|
||||
FrameInterpolation_UpdateRibbonHeads(interpolationStep);
|
||||
// Skinned vertices are shared by every displayed frame, so blend them before each run
|
||||
FrameInterpolation_UpdateSkinnedVertices(interpolationStep);
|
||||
intp->mInterpolationT = interpolationStep;
|
||||
wnd->DrawAndRunGraphicsCommands(Commands, mtx_replacements);
|
||||
intp->mInterpolationIndex++;
|
||||
|
||||
@@ -234,6 +234,18 @@ struct RibbonHead {
|
||||
};
|
||||
|
||||
unordered_map<const void*, RibbonHead> ribbon_heads;
|
||||
|
||||
// Skinned limbs (horse body and legs) have their vertices rewritten on the CPU each
|
||||
// gameplay frame, so matrix interpolation can't move them. Keep the last two poses and
|
||||
// write a blend of them into the game's buffer before each displayed frame.
|
||||
struct SkinnedLimb {
|
||||
Vtx* dest = nullptr;
|
||||
vector<Vtx> prev;
|
||||
vector<Vtx> cur;
|
||||
uint32_t frame = 0;
|
||||
};
|
||||
|
||||
unordered_map<const void*, SkinnedLimb> skinned_limbs;
|
||||
uint32_t record_frame;
|
||||
|
||||
Data& append(Op op) {
|
||||
@@ -505,6 +517,7 @@ void FrameInterpolation_StartRecord(void) {
|
||||
is_recording = true;
|
||||
} else {
|
||||
ribbon_heads.clear();
|
||||
skinned_limbs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,6 +730,53 @@ void FrameInterpolation_UpdateRibbonHeads(float step) {
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInterpolation_RecordSkinnedLimb(const void* key, void* dest, u32 vtxCount) {
|
||||
if (!is_recording) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkinnedLimb& limb = skinned_limbs[key];
|
||||
// Only blend against a pose from the frame right before this one
|
||||
if (limb.frame + 1 == record_frame && limb.cur.size() == vtxCount) {
|
||||
limb.prev.swap(limb.cur);
|
||||
} else {
|
||||
limb.prev.clear();
|
||||
}
|
||||
limb.dest = (Vtx*)dest;
|
||||
limb.cur.assign(limb.dest, limb.dest + vtxCount);
|
||||
limb.frame = record_frame;
|
||||
}
|
||||
|
||||
void FrameInterpolation_UpdateSkinnedVertices(float step) {
|
||||
for (auto it = skinned_limbs.begin(); it != skinned_limbs.end();) {
|
||||
SkinnedLimb& limb = it->second;
|
||||
|
||||
// Not drawn this frame, its buffer may be freed
|
||||
if (limb.frame != record_frame) {
|
||||
it = skinned_limbs.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (limb.prev.empty() || step >= 1.0f) {
|
||||
std::copy(limb.cur.begin(), limb.cur.end(), limb.dest);
|
||||
} else {
|
||||
float w = 1.0f - step;
|
||||
for (size_t i = 0; i < limb.cur.size(); i++) {
|
||||
const Vtx& p = limb.prev[i];
|
||||
const Vtx& c = limb.cur[i];
|
||||
Vtx& d = limb.dest[i];
|
||||
d = c;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
d.n.ob[j] = (s16)(w * p.n.ob[j] + step * c.n.ob[j]);
|
||||
d.n.n[j] = (s8)(w * p.n.n[j] + step * c.n.n[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix
|
||||
static bool invert_matrix(const float m[16], float invOut[16]) {
|
||||
float inv[16], det;
|
||||
|
||||
@@ -63,6 +63,10 @@ void FrameInterpolation_RecordRibbonHead(const void* key, void* dest, u32 vtxCou
|
||||
|
||||
void FrameInterpolation_UpdateRibbonHeads(float step);
|
||||
|
||||
void FrameInterpolation_RecordSkinnedLimb(const void* key, void* dest, u32 vtxCount);
|
||||
|
||||
void FrameInterpolation_UpdateSkinnedVertices(float step);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "global.h"
|
||||
#include "soh/frame_interpolation.h"
|
||||
|
||||
MtxF gSkinLimbMatrices[60]; // holds matrices for each limb of the skeleton currently being drawn
|
||||
|
||||
@@ -126,6 +127,8 @@ void Skin_ApplyLimbModifications(GraphicsContext* gfxCtx, Skin* skin, s32 limbIn
|
||||
vtxBuf, &vtxPoint);
|
||||
}
|
||||
|
||||
FrameInterpolation_RecordSkinnedLimb(vtxEntry, vtxBuf, data->totalVtxCount);
|
||||
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, vtxEntry->buf[vtxEntry->index]);
|
||||
|
||||
vtxEntry->index = (vtxEntry->index == 0) ? 1 : 0;
|
||||
|
||||
Reference in New Issue
Block a user