SubS Time Paths (Finale) (#823)

* Merge in sub_s_models

* Update subs DL names

* Unused Weight pathing

* Function headers for weightpathing

* TimePathing WIP

* Timepathing, still unsure about unk184

* Move subs functions from functions.h to z64subs.h

* Add fake comment

* Some cleanup and renames

* Renames/cleanup of actors that use timepath

* Cleanup

* More cleanup

* Rename unk stuff

* Merge in upstream/master

* TimeElapsed -> elapsedTime

* Fix

* Final cleanup

* Fix waypoint comments

* Review pt. 1

* Add clarifying comment to SubS_TimePathing_FillWeightArray

* format.sh

* Fix order comments

* weightArray -> knots

* Review pt 1

* Review pt 2

* Update src/code/z_sub_s.c

Co-authored-by: EllipticEllipsis <elliptic.ellipsis@gmail.com>

* Update src/code/z_sub_s.c

Co-authored-by: EllipticEllipsis <elliptic.ellipsis@gmail.com>

Co-authored-by: Maide <34639600+Kelebek1@users.noreply.github.com>
Co-authored-by: Tom Overton <tom-overton@users.noreply.github.com>
Co-authored-by: EllipticEllipsis <elliptic.ellipsis@gmail.com>
This commit is contained in:
Derek Hensley
2022-06-19 16:49:20 -07:00
committed by GitHub
parent a039a2f34a
commit d318b1f285
21 changed files with 1004 additions and 528 deletions
+451 -19
View File
@@ -178,8 +178,7 @@ s32 SubS_InCsMode(GlobalContext* globalCtx) {
* @param[in] stepRot boolean, step towards newRot instead of setting directly
* @param[in] overrideRot boolean, override newRot with the specified input.
*
* Note:
* If overrideRot is true, the rotation will automatically step instead of setting directly
* @note if overrideRot is true, the rotation will automatically step instead of setting directly
*/
s32 SubS_UpdateLimb(s16 newRotZ, s16 newRotY, Vec3f* pos, Vec3s* rot, s32 stepRot, s32 overrideRot) {
Vec3f newPos;
@@ -214,17 +213,273 @@ void SubS_UpdateFlags(u16* flags, u16 setBits, u16 unsetBits) {
*flags = (*flags & ~unsetBits) | setBits;
}
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013AF00.s")
/**
* Fills the knot array to be used with time paths
*
* The default knot array just pads with `order` duplicate knots of the first knot at the front and of the last knot
* at the end.
*
* @param[out] knots an array of values that are used to compute the progress and the individual weights
* @param[in] order the order of the interpolation i.e. the number of points in the interpolation
* @param[in] numPoints the number of points to fill, generally the path count + order
*
* @note Same note as SubS_TimePathing_Update()
*/
void SubS_TimePathing_FillKnots(f32 knots[], s32 order, s32 numPoints) {
s32 i;
f32 val = 0.0f;
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013B010.s")
for (i = 0; i < numPoints; i++) {
if ((i >= order) && (i < (numPoints - order + 1))) {
val += 1.0f;
}
knots[i] = val;
}
}
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013B0C8.s")
typedef enum {
/* 0 */ SUBS_TIME_PATHING_PROGRESS_STATUS_ERROR,
/* 1 */ SUBS_TIME_PATHING_PROGRESS_STATUS_STILL_ON_PATH,
/* 2 */ SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END
} SUBS_TIME_PATHING_PROGRESS_STATUS;
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013B350.s")
/**
* Computes the progress to be used with time paths
*
* @param[out] progress the progress along the path, used to compute the weights
* @param[in] elapsedTime how much time has passed
* @param[in] waypointTime how much time per each waypoint
* @param[in] totalTime how much time the path should take to travel
* @param[in] pathCount the path count
* @param[in] order the order of the interpolation i.e. the number of points in the interpolation
* @param[in] knots see SubS_TimePathing_FillKnots()
*
* @return see SUBS_TIME_PATHING_PROGRESS_STATUS
*/
s32 SubS_TimePathing_ComputeProgress(f32* progress, s32 elapsedTime, s32 waypointTime, s32 totalTime, s32 pathCount,
s32 order, f32 knots[]) {
s32 i;
s32 j;
s32 k;
f32 waypointTimeInv; // The fraction of a waypoint a single unit of time contains
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013B6B0.s")
*progress = 0.0f;
if ((waypointTime <= 0) || (elapsedTime < 0)) {
return SUBS_TIME_PATHING_PROGRESS_STATUS_ERROR;
}
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013B878.s")
// When using the knots from SubS_TimePathing_FillKnots() these nested loops seem to simplify to
// *progress = (f32)elapsedTime / (f32)waypointTime;
waypointTimeInv = 1.0f / waypointTime;
k = 0;
for (i = order - 1; i < pathCount; i++) {
for (j = 0; j < waypointTime; j++) {
if (k == elapsedTime) {
break;
}
*progress += (knots[i + 1] - knots[i]) * waypointTimeInv;
k++;
}
}
return (elapsedTime == totalTime) ? SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END
: SUBS_TIME_PATHING_PROGRESS_STATUS_STILL_ON_PATH;
}
/**
* Computes the interpolation weights to be used with time paths
*
* Seems to use some kind of B-Spline interpolation algorithm
*
* @param[in] order the order of the interpolation i.e. the number of points in the interpolation, max is 10
* @param[in] progress see SubS_TimePathing_ComputeProgress()
* @param[in] waypoint the current waypoint
* @param[in] knots see SubS_TimePathing_FillKnots()
* @param[out] weights how much to weight each point considered
*/
void SubS_TimePathing_ComputeWeights(s32 order, f32 progress, s32 waypoint, f32 knots[], f32 weights[]) {
f32 weightsTemp[10][11];
s32 i;
s32 j;
s32 k;
for (i = 0; i < order; i++) {
for (j = 0; j < order + 1; j++) {
weightsTemp[i][j] = 0.0f;
}
}
weightsTemp[0][order - 1] = 1.0f;
for (i = 1; i < order; i++) {
for (j = waypoint - i, k = (order - 1) - i; j <= waypoint; j++, k++) {
if (knots[j + i] != knots[j]) {
weightsTemp[i][k] = ((progress - knots[j]) / (knots[j + i] - knots[j])) * weightsTemp[i - 1][k];
} else {
weightsTemp[i][k] = 0.0f;
}
if (knots[j + i + 1] != knots[j + 1]) {
weightsTemp[i][k] +=
((knots[j + i + 1] - progress) / (knots[j + i + 1] - knots[j + 1])) * weightsTemp[i - 1][k + 1];
}
}
}
for (j = 0; j < order; j++) {
weights[j] = weightsTemp[order - 1][j];
}
}
/**
* Computes the X and Z component of the position to move to in time based paths
*
* @param[out] x computed x position
* @param[out] z computed z position
* @param[in] progress see SubS_TimePathing_ComputeProgress()
* @param[in] order the order of the interpolation i.e. the number of points in the interpolation, max is 10
* @param[in] waypoint the current waypoint
* @param[in] points the path's points
* @param[in] knots see SubS_TimePathing_FillKnots()
*/
void SubS_TimePathing_ComputeTargetPosXZ(f32* x, f32* z, f32 progress, s32 order, s32 waypoint, Vec3s points[],
f32 knots[]) {
f32 xPos;
f32 zPos;
f32 weights[11];
f32 weightedX;
f32 weightedZ;
f32 weightedTotal;
s32 i;
SubS_TimePathing_ComputeWeights(order, progress, waypoint, knots, weights);
weightedTotal = 0.0f;
weightedZ = 0.0f;
weightedX = 0.0f;
for (i = 0; i < order; i++) {
xPos = points[waypoint - order + i + 1].x;
zPos = points[waypoint - order + i + 1].z;
weightedX += weights[i] * xPos;
weightedZ += weights[i] * zPos;
weightedTotal += weights[i];
}
*x = weightedX / weightedTotal;
*z = weightedZ / weightedTotal;
}
/**
* Updates a time based path that an actor follows by:
* - Computing the X and Z components of the next point to move to
* - Updating the waypoint
* - Updating the time
*
* @param[in] path
* @param[out] progress see SubS_TimePathing_ComputeProgress()
* @param[in,out] elapsedTime how much time has passed
* @param[in] waypointTime how much time per each waypoint
* @param[in] totalTime how much time the path should take to travel
* @param[in,out] waypoint the current waypoint, this and the previous two points will be used to compute the targetPos
* @param[in] knots see SubS_TimePathing_FillKnots()
* @param[out] targetPos the computed position to move to
* @param[in] timeSpeed how fast time moves
*
* @return s32 returns true when the end has been reached.
*
* @note This system/function makes a couple of assumptions about the order used:
* 1. the order is assumed to be 3, see SUBS_TIME_PATHING_ORDER
* 2. even if SUBS_TIME_PATHING_ORDER is updated, the order can only be a max of 10
*/
s32 SubS_TimePathing_Update(Path* path, f32* progress, s32* elapsedTime, s32 waypointTime, s32 totalTime, s32* waypoint,
f32 knots[], Vec3f* targetPos, s32 timeSpeed) {
Vec3s* points = Lib_SegmentedToVirtual(path->points);
s32 state;
f32 endX;
f32 endZ;
s32 reachedEnd = false;
if (*waypoint >= path->count) {
state = SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END;
} else {
state = SubS_TimePathing_ComputeProgress(progress, *elapsedTime, waypointTime, totalTime, path->count,
SUBS_TIME_PATHING_ORDER, knots);
}
switch (state) {
case SUBS_TIME_PATHING_PROGRESS_STATUS_STILL_ON_PATH:
reachedEnd = false;
SubS_TimePathing_ComputeTargetPosXZ(&targetPos->x, &targetPos->z, *progress, SUBS_TIME_PATHING_ORDER,
*waypoint, points, knots);
break;
case SUBS_TIME_PATHING_PROGRESS_STATUS_SHOULD_REACH_END:
endX = points[path->count - 1].x;
endZ = points[path->count - 1].z;
targetPos->x = endX * 1;
targetPos->z = endZ * 1;
reachedEnd = true;
break;
}
*elapsedTime += timeSpeed;
if (*elapsedTime >= totalTime) {
*elapsedTime = totalTime;
} else if (*elapsedTime < 0) {
*elapsedTime = 0;
}
*waypoint = (*elapsedTime / waypointTime) + (SUBS_TIME_PATHING_ORDER - 1);
return reachedEnd;
}
/**
* Computes the initial Y component of a time based path
*
* @param[in] globalCtx
* @param[in] path
* @param[in] waypoint the current waypoint, this and the previous two points will be used to compute the target pos
* @param[out] targetPos the computed position to move to, only the Y component has meaning
*
* @note Same note as SubS_TimePathing_Update()
*/
void SubS_TimePathing_ComputeInitialY(GlobalContext* globalCtx, Path* path, s32 waypoint, Vec3f* targetPos) {
Vec3s* points = Lib_SegmentedToVirtual(path->points);
Vec3f posA;
Vec3f posB;
Vec3f posResult;
s32 i = waypoint - (SUBS_TIME_PATHING_ORDER - 1);
s16 max;
s16 min;
s32 isSetup;
CollisionPoly* outPoly = NULL;
s32 bgId = 0;
max = 0;
min = 0;
isSetup = false;
for (; i <= waypoint; i++) {
if (isSetup) {
if (max < points[i].y) {
max = points[i].y;
}
if (points[i].y < min) {
min = points[i].y;
}
} else {
max = min = points[i].y;
}
isSetup = true;
}
max += 30;
min -= 30;
posA = *targetPos;
posB = *targetPos;
posA.y = max;
posB.y = min;
if (BgCheck_EntityLineTest1(&globalCtx->colCtx, &posA, &posB, &posResult, &outPoly, true, true, true, true,
&bgId)) {
targetPos->y = posResult.y;
}
}
Path* SubS_GetAdditionalPath(GlobalContext* globalCtx, u8 pathIndex, s32 max) {
Path* path;
@@ -362,9 +617,189 @@ Path* SubS_GetDayDependentPath(GlobalContext* globalCtx, u8 pathIndex, u8 max, s
return path;
}
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013C068.s")
/**
* Computes the point to move toward using a weight based algorithm that considers 4 points along the path
*
* @param path
* @param waypoint the current waypoint, this and the previous three points will be used to compute the point
* @param point the point computed
* @param progress the main weight value used to compute the weights for the points considered
* @param direction the direciton along the path to move, 1 for forwards, anything else for backwards
*
* @note only computes X and Z components of the point
*/
s32 SubS_WeightPathing_ComputePoint(Path* path, s32 waypoint, Vec3f* point, f32 progress, s32 direction) {
s32 i;
f32 weight0;
f32 weight1;
f32 weight2;
f32 weight3;
s32 lastPoint;
s32 secondLastPoint;
s32 secondPoint;
s32 firstPoint;
f32 xPoints[4];
f32 zPoints[4];
f32 oneMinusProgress;
f32 squared;
f32 cubed;
Vec3s* points;
s32 count = path->count;
s32 pointIndex;
s32 tmp;
#pragma GLOBAL_ASM("asm/non_matchings/code/z_sub_s/func_8013C624.s")
if (path == NULL) {
return false;
}
if (direction == 1) {
if (waypoint <= 2) {
pointIndex = 2;
} else {
pointIndex = (waypoint == 3) ? 3 : waypoint;
}
for (i = 0; i < 4; i++, pointIndex--) {
if (pointIndex <= 0) {
pointIndex = 0;
}
points = Lib_SegmentedToVirtual(path->points);
points = &points[pointIndex];
xPoints[i] = points->x;
zPoints[i] = points->z;
}
lastPoint = count - 1;
secondLastPoint = count - 2;
secondPoint = 3;
firstPoint = 2;
} else {
if (waypoint >= count - 3) {
pointIndex = count - 3;
} else {
tmp = waypoint + 4;
pointIndex = (count == tmp) ? count - 4 : waypoint;
}
for (i = 0; i < 4; i++, pointIndex++) {
if (pointIndex >= path->count) {
pointIndex = path->count - 1;
}
points = Lib_SegmentedToVirtual(path->points);
points = &points[pointIndex];
xPoints[i] = points->x;
zPoints[i] = points->z;
}
lastPoint = 0;
secondLastPoint = 1;
secondPoint = count - 4;
firstPoint = count - 3;
}
if (waypoint == lastPoint) {
oneMinusProgress = 1.0f - progress;
squared = progress * progress;
cubed = progress * squared;
weight0 = oneMinusProgress * oneMinusProgress * oneMinusProgress;
weight1 = (1.75f * cubed) - (4.5f * squared) + (3.0f * progress);
weight2 = ((-11.0f / 12.0f) * cubed) + (1.5f * squared);
weight3 = (1.0f / 6.0f) * cubed;
} else if (waypoint == secondLastPoint) {
oneMinusProgress = 1.0f - progress;
squared = progress * progress;
cubed = progress * squared;
weight0 = oneMinusProgress * oneMinusProgress * oneMinusProgress * ((void)0, 0.25f); //! FAKE:
weight1 = ((7.0f / 12.0f) * cubed) - (1.25f * squared) + (0.25f * progress) + (7.0f / 12.0f);
weight2 = (-0.5f * cubed) + (0.5f * squared) + (progress * 0.5f) + (1.0f / 6.0f);
weight3 = cubed * (1.0f / 6.0f);
} else if (waypoint == secondPoint) {
oneMinusProgress = 1.0f - progress;
squared = oneMinusProgress * oneMinusProgress;
cubed = oneMinusProgress * squared;
weight0 = (1.0f / 6.0f) * cubed;
weight1 = (-0.5f * cubed) + (0.5f * squared) + (0.5f * oneMinusProgress) + (1.0f / 6.0f);
weight2 = ((7.0f / 12.0f) * cubed) - (1.25f * squared) + (0.25f * oneMinusProgress) + (7.0f / 12.0f);
weight3 = progress * progress * progress * 0.25f;
} else if (((direction == 1) && (firstPoint >= waypoint)) || ((direction != 1) && (waypoint >= firstPoint))) {
oneMinusProgress = 1.0f - progress;
squared = oneMinusProgress * oneMinusProgress;
cubed = oneMinusProgress * squared;
weight0 = (1.0f / 6.0f) * cubed;
weight1 = ((-11.0f / 12.0f) * cubed) + (1.5f * squared);
weight2 = (1.75f * cubed) - (4.5f * squared) + (3.0f * oneMinusProgress);
weight3 = progress * progress * progress;
} else {
oneMinusProgress = 1.0f - progress;
squared = progress * progress;
cubed = squared * progress;
weight0 = oneMinusProgress * oneMinusProgress;
weight0 = oneMinusProgress * weight0 / 6.0f;
weight1 = (cubed * 0.5f) - squared + (2.0f / 3.0f);
weight2 = (cubed / -2.0f) + (squared * 0.5f) + (progress * 0.5f) + (1.0f / 6.0f);
weight3 = cubed / 6.0f;
}
point->x = (weight0 * xPoints[0]) + (weight1 * xPoints[1]) + (weight2 * xPoints[2]) + (weight3 * xPoints[3]);
point->z = (weight0 * zPoints[0]) + (weight1 * zPoints[1]) + (weight2 * zPoints[2]) + (weight3 * zPoints[3]);
return true;
}
// WeightPathing System is completely unused
/**
* Moves an actor based on a weight based algorithm that takes into account 4 points along the path
*
* @param actor
* @param path
* @param waypoint the current waypoint, this and the previous three points will be used to move forward
* @param progress the progress towards a given waypoint, used to compute the weights
* @param direction the direction along the path to move, 1 for forwards, anything else for backwards
* @param returnStart boolean, true if the actor should wrap back to start when reaching the end
*
* @return s32 true if actor reached the end of the path in this iteration, false otherwise
*/
s32 SubS_WeightPathing_Move(Actor* actor, Path* path, s32* waypoint, f32* progress, s32 direction, s32 returnStart) {
Vec3f worldPos = actor->world.pos;
Vec3f velocity = actor->velocity;
Vec3f point;
f32 dist;
if (((direction != 1) && (*waypoint >= (path->count - 2))) || ((direction == 1) && (*waypoint < 2))) {
return false;
}
while (true) {
if (!SubS_WeightPathing_ComputePoint(path, *waypoint, &point, *progress, direction) ||
((s32)(actor->speedXZ * 10000.0f) == 0)) {
return false;
}
dist = Math_Vec3f_DistXZ(&actor->world.pos, &point);
actor->world.rot.y = Math_Vec3f_Yaw(&actor->world.pos, &point);
Actor_MoveWithGravity(actor);
if (Math_Vec3f_DistXZ(&actor->world.pos, &point) < dist) {
break;
}
*progress += 0.1f;
if (*progress >= 1.1f) {
if (direction != 1) {
(*waypoint)++;
if (*waypoint >= (path->count - 2)) {
if (returnStart) {
*waypoint = 0;
} else {
return true;
}
}
} else {
(*waypoint)--;
if (*waypoint < 2) {
if (returnStart) {
*waypoint = path->count - 2;
} else {
return true;
}
}
}
*progress = 0.0f;
}
actor->world.pos = worldPos;
actor->velocity = velocity;
}
return false;
}
s32 SubS_CopyPointFromPathCheckBounds(Path* path, s32 pointIndex, Vec3f* dst) {
Vec3s* point;
@@ -557,10 +992,10 @@ void SubS_DrawShadowTex(Actor* actor, GameState* gameState, u8* tex) {
Matrix_Translate(actor->world.pos.x, 0.0f, actor->world.pos.z, MTXMODE_NEW);
Matrix_Scale(0.6f, 1.0f, 0.6f, MTXMODE_APPLY);
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_OPA_DISP++, gShadowDL);
gSPDisplayList(POLY_OPA_DISP++, gShadowMaterialDL);
gDPLoadTextureBlock(POLY_OPA_DISP++, tex, G_IM_FMT_I, G_IM_SIZ_8b, SUBS_SHADOW_TEX_WIDTH, SUBS_SHADOW_TEX_HEIGHT, 0,
G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, 6, 6, G_TX_NOLOD, G_TX_NOLOD);
gSPDisplayList(POLY_OPA_DISP++, gShadowVtxDL);
gSPDisplayList(POLY_OPA_DISP++, gShadowModelDL);
CLOSE_DISPS(gfxCtx);
}
@@ -617,9 +1052,7 @@ s16 SubS_ComputeTurnToPointRot(s16* rot, s16 rotMax, s16 target, f32 slowness, f
* @param[in,out] turnTarget the intermediate target step that headRot and torsoRot step towards
* @param[in,out] headRot the computed head rotation
* @param[in,out] torsoRot the computed torso rotation
* @param[in] options various options to adjust how the actor turns, see `SubS_ComputeTurnToPointRot and
* TurnOptions/TurnOptionsSet`
*
* @param[in] options various options to adjust how the actor turns, see SubS_ComputeTurnToPointRot()
*/
s32 SubS_TurnToPoint(Vec3f* point, Vec3f* focusPos, Vec3s* shapeRot, Vec3s* turnTarget, Vec3s* headRot, Vec3s* torsoRot,
TurnOptionsSet* options) {
@@ -781,8 +1214,8 @@ s32 SubS_CopyPointFromPathList(Path* paths, s32 pathIndex, s32 pointIndex, Vec3f
return false;
}
u8 SubS_GetPathCount(Path* paths, s32 index) {
Path* path = &paths[index];
u8 SubS_GetPathCountFromPathList(Path* paths, s32 pathIndex) {
Path* path = &paths[pathIndex];
return path->count;
}
@@ -1004,8 +1437,7 @@ s32 SubS_FillCutscenesList(Actor* actor, s16 cutscenes[], s16 numCutscenes) {
* @param[in] rot the angles to rotate with, uses just the x and y components
* @param[out] plane the computed plane
*
* Notes:
* The unit input vector is expected to already be normalized (only uses are with the z unit vector)
* @note the unit input vector is expected to already be normalized (only uses are with the z unit vector)
*
*/
void SubS_ConstructPlane(Vec3f* point, Vec3f* unitVec, Vec3s* rot, Plane* plane) {