Repository navigation
[Feature] Reach IK: a target per chain, in world, model, shoulder or ground space - #1283
Conversation
…pace Chains shared one world target, which suits a character grabbing at something and not hands that follow tracking. setReachIKChainTargets gives every chain its own; a shoulder-space target is an offset from the chain's shoulder, so the hand keeps its place on the body without a frame of lag. The easing of the targets is a parameter.
…ot where it stands A target in the new modelGround space is a spot whose height is ignored: the chain's end goes over it at the height the pose gives it.
untoldengine
left a comment
There was a problem hiding this comment.
Nice work on this — the per-chain target design (and the fallback-to-shared-target / fallback-to-pose semantics) is clean, and the test coverage across the four spaces is thorough.
One thing worth a doc note (not a bug, just a sharp edge): setReachIKTarget and setReachIKChainTargets write to the same underlying ReachIKState fields — weight, targetWeight, halflife, reach, and now targetHalflife too. If a caller drives some chains via setReachIKChainTargets (e.g. hands from tracking) and relies on the shared-target fallback via setReachIKTarget for other chains (e.g. a grab target) in the same session, whichever call runs last on a given frame silently overwrites the other's halflife/reach/targetHalflife. Given the fallback behavior explicitly invites mixing the two APIs, it'd be worth a line in UsingPoseLayers.md (or a code comment on the two functions) calling out that they share one blend/easing configuration per entity, so callers need to pick consistent values across both call sites rather than tuning them independently.
Everything else checks out — no correctness issues found in the easing state machine, the .modelGround/.shoulder space math, or the cleanup-on-release path.
… the shared influence is documented setReachIKChainTargets set the same target easing the shared target uses, so following tracked hands exactly made a shared grab target jump. The chains' targets have their own halflife now. Weight, halflife and reach stay one influence per entity, set by whichever of the two calls runs last: said on both functions and in the guide.
|
Good catch, addressed in 3991416. Weight, halflife and reach are one influence per entity, and that is now stated on both functions and in The target easing was the one that could actually bite: following tracked hands exactly ( Two tests cover the mix: exact per-chain following leaves the shared target eased, and the call made last sets the reach limit for every chain. |
What
Reach IK bent every chain toward one shared world target, which suits a character grabbing at something and not limbs that each follow something of their own. This adds a target per chain.
setReachIKChainTargets(entityId:targets:weight:halflife:reach:targetHalflife:): oneReachIKChainTargetper chain, index-aligned with the chains. A chain whose entry is nil reaches for the shared target ofsetReachIKTarget, or keeps its pose when there is none, so existing callers are untouched..world: a world position..model: a position in the entity's model space..shoulder: an offset from the chain's own shoulder along the model axes. The hand keeps its place relative to the body however the body moves that frame, with no frame of lag (hands driven by tracking)..modelGround: a spot in model space whose height is ignored. The chain's end goes over it at the height the pose gives it, which is what a leg chain (hip, knee, ankle) needs to hold a foot where it stands while the pose lifts and lowers it.targetHalflife; 0 follows a source that is already filtered).The chains are any two-bone chain, so the same solver holds legs as well as arms; nothing else in the pose pipeline changes.
Why
The CoolMirror demo (iPhone body capture plus the headset's own head and hands) drives both hands and both planted feet through it: the hands as shoulder-space targets, the feet as ground spots. The demo PR for UntoldArcade follows once this is in.
Tests
AnimationReachChainTargetTests: ten cases on a two-arm skeleton (each chain reaches its own target; the four spaces; a chain without a target keeps its pose or takes the shared one; target easing and its zero halflife; ease-out on release; full reach straightens the chain). The existing pose layer and reach tests are unchanged and pass.Verified locally: 1524 engine unit tests (the external render-extension fixture excepted, a toolchain issue on my machine), a strict-concurrency build with no warning sites, and lint.
Docs
docs/API/UsingPoseLayers.mdgains the per-chain targets.