Foot IK with pelvis lowering, ground tilt, and edge smoothing#742
Merged
Foot IK with pelvis lowering, ground tilt, and edge smoothing#742
Conversation
Two-bone IK on each leg plus a pelvis-drop pass so the avatar plants both feet when standing on uneven ground (slopes, short steps). Per-leg binary engagement gate: foot disengages if the target sits more than max_step_up above the player or would need more than max_pelvis_drop of hip drop to reach. Idle-only by default — fades out with horizontal speed. Toggle in console with `/footik`. Runs in PostUpdate after PlayerUpdate and before AttachSync, with a manual transform-propagate pass so attached items see the post-IK bone positions in the same frame. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Wearable reloads despawn the old armature and rebuild it. The cached FootIkRig kept entity references that silently went dead, so IK appeared to switch off until the player toggled it twice. Each frame we now sanity-check that the seven cached entities still resolve via GlobalTransform; if any don't, we strip the component and the existing rebuild pass picks up the new bones on the next frame. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The movement scene already declares per-emote transition_seconds, and the engine tracks an idle flag on scene-driven anims (mirrored on ActiveEmote.overridable). Use these directly: ramp IK weight toward 1 while an idle pose is active, toward 0 otherwise, at rate 1/transition_seconds. Triggered emotes (dances etc.) never count as idle; engine-default idle is detected via the URN. The animation ramp is applied after the per-leg binary reach cutoff, so unreachable feet (cliff edge, tall step) stay disengaged regardless of weight, and the pelvis drop scales with the ramp. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Each per-leg ray hit also gives a surface normal; align the animated foot world rotation toward it (axis-angle clamped to max_foot_tilt_deg, default 30°), then slerp by the per-leg IK weight. Writes the foot's local rotation derived from the post-IK knee global. Disengaged legs (cliff edge, ramp-out) leave the foot in animation pose. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
When the avatar turns in place over a cliff edge, the foot's world XZ sweeps across the edge and the raycast result jumps discontinuously between the cliff top and the floor below. Even with both sides reachable (so engagement stays at 1), the leg snaps frame-to-frame. Track per-leg `last_final_y` and rate-limit the foot's actual world Y output (animated_y + (target_y - animated_y) * w) at a configurable m/s. Snap on the first engaged frame after a disengaged one so re- engagement doesn't creep from stale state. Pelvis drop is sized to the rate-limited final_y, not the back-derived IK target — the latter sits within physical reach by design (the IK math clamps l_at) and would keep the pelvis still. Engagement transition is no longer compounded with the animation weight — they clamp via min() instead, so each gates independently. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Replace the With<PrimaryUser> filter with Or<(PrimaryUser, ForeignPlayer)> on both cache and apply queries. Per-avatar state (anim_w, per-leg engagement, last_final_y) moves out of system Locals into a new FootIkRuntime component, inserted alongside FootIkRig at cache time. The apply system now loops over all avatars; each iteration scopes its raycasts via ContainingScene::get_position(avatar_pos), so the per-frame ray cost stays bounded to scenes that actually contain the iterating avatar. Pole hint, idle detection, pelvis drop, and per-leg writes all target the iterating avatar. Logs include the entity id. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
AvatarShape is the common marker on every in-world avatar — the primary user, foreign players, and scene-spawned NPCs. NPCs already have the prerequisites (AvatarDynamicState from update_npc_velocity, ActiveEmote from animate.rs, the same Mixamo skeleton from the wearable pipeline), so there's nothing else to wire up. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an idle-only inverse-kinematics pass that plants avatar feet on the actual ground beneath them — slopes, short steps, uneven terrain — so the avatar doesn't visibly hover or clip when standing on non-flat surfaces.
Toggle in-game with `/footik` (off by default). Applies to the primary user, foreign players, and scene NPCs.
How it works
Runs in `PostUpdate`, after `PlayerUpdate` and before `AttachSync`, with a manual transform-propagate pass so attached items see post-IK bone positions:
Scope
Out of scope (could follow up)
🤖 Generated with Claude Code