reze-engine 0.32.1 → 0.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/locomotion.d.ts +8 -0
- package/dist/locomotion.d.ts.map +1 -1
- package/dist/locomotion.js +31 -2
- package/package.json +1 -1
- package/src/locomotion.ts +40 -2
package/dist/locomotion.d.ts
CHANGED
|
@@ -90,6 +90,10 @@ export interface LocomotionOptions {
|
|
|
90
90
|
turnTimeScale?: number;
|
|
91
91
|
/** Playback rate for stop clips (default 1.25) — same reasoning. */
|
|
92
92
|
stopTimeScale?: number;
|
|
93
|
+
/** Seconds a heading must be held before a release earns an authored stop
|
|
94
|
+
* (default 0.5). Below it, weaving direction changes blend to idle instead
|
|
95
|
+
* of skidding metres along the last-held heading. */
|
|
96
|
+
stopCommitTime?: number;
|
|
93
97
|
/** Tank-mode steering rate, radians/second (default 2.5 ≈ 143°/s). */
|
|
94
98
|
steerRate?: number;
|
|
95
99
|
/** Backpedal speed as a fraction of run speed in tank mode (default 0.5). */
|
|
@@ -123,11 +127,15 @@ export declare class LocomotionController {
|
|
|
123
127
|
private readonly turnClipMinAngle;
|
|
124
128
|
private readonly turnTimeScale;
|
|
125
129
|
private readonly stopTimeScale;
|
|
130
|
+
private readonly stopCommitTime;
|
|
126
131
|
private turning;
|
|
127
132
|
private runTurning;
|
|
128
133
|
/** An interrupted authored clip fading out OVER resumed locomotion, so breaking
|
|
129
134
|
* out of a stop is instantly responsive without a pose pop. */
|
|
130
135
|
private exitGhost;
|
|
136
|
+
private headingHold;
|
|
137
|
+
private headingDirX;
|
|
138
|
+
private headingDirY;
|
|
131
139
|
private stopping;
|
|
132
140
|
private readonly turnEntries;
|
|
133
141
|
private readonly yawOffset;
|
package/dist/locomotion.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locomotion.d.ts","sourceRoot":"","sources":["../src/locomotion.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"locomotion.d.ts","sourceRoot":"","sources":["../src/locomotion.ts"],"names":[],"mappings":"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"}
|
package/dist/locomotion.js
CHANGED
|
@@ -23,6 +23,9 @@ export class LocomotionController {
|
|
|
23
23
|
/** An interrupted authored clip fading out OVER resumed locomotion, so breaking
|
|
24
24
|
* out of a stop is instantly responsive without a pose pop. */
|
|
25
25
|
this.exitGhost = null;
|
|
26
|
+
this.headingHold = 0;
|
|
27
|
+
this.headingDirX = 0;
|
|
28
|
+
this.headingDirY = 0;
|
|
26
29
|
this.stopping = null;
|
|
27
30
|
this.inputX = 0;
|
|
28
31
|
this.inputY = 0;
|
|
@@ -59,6 +62,7 @@ export class LocomotionController {
|
|
|
59
62
|
this.turnClipMinAngle = options?.turnClipMinAngle ?? (100 * Math.PI) / 180;
|
|
60
63
|
this.turnTimeScale = options?.turnTimeScale ?? 1.4;
|
|
61
64
|
this.stopTimeScale = options?.stopTimeScale ?? 1.25;
|
|
65
|
+
this.stopCommitTime = options?.stopCommitTime ?? 0.5;
|
|
62
66
|
this.turnEntries = [
|
|
63
67
|
{ name: clips.idle, time: 0, weight: 0 },
|
|
64
68
|
{ name: clips.idle, time: 0, weight: 1 },
|
|
@@ -176,12 +180,30 @@ export class LocomotionController {
|
|
|
176
180
|
const m = Math.hypot(this.inputX, this.inputY);
|
|
177
181
|
moving = m > 0.05;
|
|
178
182
|
this.recentSpeed = Math.max(this.speedLevel, this.recentSpeed - dt * 1.5);
|
|
183
|
+
// How long the CURRENT heading has been held. An authored skid is the
|
|
184
|
+
// ending of a committed run; weaving through directions (D, then S+D,
|
|
185
|
+
// then S) rebuilds throttle in each new heading but earns no such
|
|
186
|
+
// ending — playing one there slides her metres along whichever way she
|
|
187
|
+
// happened to face last, which reads as drift.
|
|
188
|
+
if (moving) {
|
|
189
|
+
const dx = this.inputX / m;
|
|
190
|
+
const dy = this.inputY / m;
|
|
191
|
+
this.headingHold =
|
|
192
|
+
this.headingDirX * dx + this.headingDirY * dy > 0.94 ? this.headingHold + dt : 0;
|
|
193
|
+
this.headingDirX = dx;
|
|
194
|
+
this.headingDirY = dy;
|
|
195
|
+
}
|
|
179
196
|
// Release at speed: play the authored stop instead of blending to idle.
|
|
180
197
|
const stops = this.clips.stop;
|
|
181
198
|
// Gate on the peak-hold speed: the release decay (throttle/analog) drains the
|
|
182
199
|
// instantaneous level below any threshold before `moving` goes false. The
|
|
183
200
|
// small floor on the actual level keeps standstill taps from faking a skid.
|
|
184
|
-
if (!moving &&
|
|
201
|
+
if (!moving &&
|
|
202
|
+
stops &&
|
|
203
|
+
stops.length > 0 &&
|
|
204
|
+
this.recentSpeed >= 0.7 &&
|
|
205
|
+
this.speedLevel >= 0.35 &&
|
|
206
|
+
this.headingHold >= this.stopCommitTime) {
|
|
185
207
|
const gear = this.recentSpeed > 1.4 ? "sprint" : "run";
|
|
186
208
|
const foot = this.gaitPhase < 0.5 ? "L" : "R";
|
|
187
209
|
// Gear must match exactly: supplying stops for only one gear means the
|
|
@@ -207,6 +229,13 @@ export class LocomotionController {
|
|
|
207
229
|
dirX: this.dirX,
|
|
208
230
|
dirZ: this.dirZ,
|
|
209
231
|
startLevel: Math.min(this.recentSpeed, 2),
|
|
232
|
+
// How much of the authored skid this stop has earned. Each profile
|
|
233
|
+
// was captured at ITS gear's full speed (run = level 1, sprint =
|
|
234
|
+
// level 2), so compare like with like. Entering slower — or moments
|
|
235
|
+
// after a direction change, when the peak-hold still remembers the
|
|
236
|
+
// PREVIOUS heading's sprint — must not replay the full slide.
|
|
237
|
+
// Floored so a stop always still reads as a stop.
|
|
238
|
+
scale: Math.max(0.3, Math.min(1, this.speedLevel / (best.gear === "sprint" ? 2 : 1))),
|
|
210
239
|
fromIdleW: Math.max(0, 1 - this.speedLevel),
|
|
211
240
|
fromClip,
|
|
212
241
|
fromTime: this.gaitPhase * this.clipDuration(fromClip),
|
|
@@ -512,7 +541,7 @@ export class LocomotionController {
|
|
|
512
541
|
}
|
|
513
542
|
t.time += dt * this.stopTimeScale;
|
|
514
543
|
const clipT = Math.min(t.time, entry.exitTime);
|
|
515
|
-
const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime);
|
|
544
|
+
const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime) * t.scale;
|
|
516
545
|
this.position.x = t.startX + t.dirX * fwd;
|
|
517
546
|
this.position.z = t.startZ + t.dirZ * fwd;
|
|
518
547
|
this.speedLevel = t.startLevel * Math.max(0, 1 - t.time / entry.exitTime);
|
package/package.json
CHANGED
package/src/locomotion.ts
CHANGED
|
@@ -102,6 +102,10 @@ export interface LocomotionOptions {
|
|
|
102
102
|
turnTimeScale?: number
|
|
103
103
|
/** Playback rate for stop clips (default 1.25) — same reasoning. */
|
|
104
104
|
stopTimeScale?: number
|
|
105
|
+
/** Seconds a heading must be held before a release earns an authored stop
|
|
106
|
+
* (default 0.5). Below it, weaving direction changes blend to idle instead
|
|
107
|
+
* of skidding metres along the last-held heading. */
|
|
108
|
+
stopCommitTime?: number
|
|
105
109
|
/** Tank-mode steering rate, radians/second (default 2.5 ≈ 143°/s). */
|
|
106
110
|
steerRate?: number
|
|
107
111
|
/** Backpedal speed as a fraction of run speed in tank mode (default 0.5). */
|
|
@@ -148,6 +152,7 @@ export class LocomotionController {
|
|
|
148
152
|
private readonly turnClipMinAngle: number
|
|
149
153
|
private readonly turnTimeScale: number
|
|
150
154
|
private readonly stopTimeScale: number
|
|
155
|
+
private readonly stopCommitTime: number
|
|
151
156
|
private turning: { entry: TurnClipEntry; time: number } | null = null
|
|
152
157
|
private runTurning: {
|
|
153
158
|
entry: RunTurnClipEntry
|
|
@@ -160,6 +165,9 @@ export class LocomotionController {
|
|
|
160
165
|
/** An interrupted authored clip fading out OVER resumed locomotion, so breaking
|
|
161
166
|
* out of a stop is instantly responsive without a pose pop. */
|
|
162
167
|
private exitGhost: { clip: string; clipTime: number; elapsed: number } | null = null
|
|
168
|
+
private headingHold = 0
|
|
169
|
+
private headingDirX = 0
|
|
170
|
+
private headingDirY = 0
|
|
163
171
|
private stopping: {
|
|
164
172
|
entry: StopClipEntry
|
|
165
173
|
time: number
|
|
@@ -168,6 +176,8 @@ export class LocomotionController {
|
|
|
168
176
|
dirX: number
|
|
169
177
|
dirZ: number
|
|
170
178
|
startLevel: number
|
|
179
|
+
/** Fraction of the authored deceleration distance this stop travels. */
|
|
180
|
+
scale: number
|
|
171
181
|
/** Idle's share of the blend at release (level < 1 shows part idle) — the
|
|
172
182
|
* crossfade source keeps this exact mix or the first stop frame snaps. */
|
|
173
183
|
fromIdleW: number
|
|
@@ -226,6 +236,7 @@ export class LocomotionController {
|
|
|
226
236
|
this.turnClipMinAngle = options?.turnClipMinAngle ?? (100 * Math.PI) / 180
|
|
227
237
|
this.turnTimeScale = options?.turnTimeScale ?? 1.4
|
|
228
238
|
this.stopTimeScale = options?.stopTimeScale ?? 1.25
|
|
239
|
+
this.stopCommitTime = options?.stopCommitTime ?? 0.5
|
|
229
240
|
this.turnEntries = [
|
|
230
241
|
{ name: clips.idle, time: 0, weight: 0 },
|
|
231
242
|
{ name: clips.idle, time: 0, weight: 1 },
|
|
@@ -353,12 +364,32 @@ export class LocomotionController {
|
|
|
353
364
|
const m = Math.hypot(this.inputX, this.inputY)
|
|
354
365
|
moving = m > 0.05
|
|
355
366
|
this.recentSpeed = Math.max(this.speedLevel, this.recentSpeed - dt * 1.5)
|
|
367
|
+
// How long the CURRENT heading has been held. An authored skid is the
|
|
368
|
+
// ending of a committed run; weaving through directions (D, then S+D,
|
|
369
|
+
// then S) rebuilds throttle in each new heading but earns no such
|
|
370
|
+
// ending — playing one there slides her metres along whichever way she
|
|
371
|
+
// happened to face last, which reads as drift.
|
|
372
|
+
if (moving) {
|
|
373
|
+
const dx = this.inputX / m
|
|
374
|
+
const dy = this.inputY / m
|
|
375
|
+
this.headingHold =
|
|
376
|
+
this.headingDirX * dx + this.headingDirY * dy > 0.94 ? this.headingHold + dt : 0
|
|
377
|
+
this.headingDirX = dx
|
|
378
|
+
this.headingDirY = dy
|
|
379
|
+
}
|
|
356
380
|
// Release at speed: play the authored stop instead of blending to idle.
|
|
357
381
|
const stops = this.clips.stop
|
|
358
382
|
// Gate on the peak-hold speed: the release decay (throttle/analog) drains the
|
|
359
383
|
// instantaneous level below any threshold before `moving` goes false. The
|
|
360
384
|
// small floor on the actual level keeps standstill taps from faking a skid.
|
|
361
|
-
if (
|
|
385
|
+
if (
|
|
386
|
+
!moving &&
|
|
387
|
+
stops &&
|
|
388
|
+
stops.length > 0 &&
|
|
389
|
+
this.recentSpeed >= 0.7 &&
|
|
390
|
+
this.speedLevel >= 0.35 &&
|
|
391
|
+
this.headingHold >= this.stopCommitTime
|
|
392
|
+
) {
|
|
362
393
|
const gear = this.recentSpeed > 1.4 ? "sprint" : "run"
|
|
363
394
|
const foot = this.gaitPhase < 0.5 ? "L" : "R"
|
|
364
395
|
// Gear must match exactly: supplying stops for only one gear means the
|
|
@@ -383,6 +414,13 @@ export class LocomotionController {
|
|
|
383
414
|
dirX: this.dirX,
|
|
384
415
|
dirZ: this.dirZ,
|
|
385
416
|
startLevel: Math.min(this.recentSpeed, 2),
|
|
417
|
+
// How much of the authored skid this stop has earned. Each profile
|
|
418
|
+
// was captured at ITS gear's full speed (run = level 1, sprint =
|
|
419
|
+
// level 2), so compare like with like. Entering slower — or moments
|
|
420
|
+
// after a direction change, when the peak-hold still remembers the
|
|
421
|
+
// PREVIOUS heading's sprint — must not replay the full slide.
|
|
422
|
+
// Floored so a stop always still reads as a stop.
|
|
423
|
+
scale: Math.max(0.3, Math.min(1, this.speedLevel / (best.gear === "sprint" ? 2 : 1))),
|
|
386
424
|
fromIdleW: Math.max(0, 1 - this.speedLevel),
|
|
387
425
|
fromClip,
|
|
388
426
|
fromTime: this.gaitPhase * this.clipDuration(fromClip),
|
|
@@ -707,7 +745,7 @@ export class LocomotionController {
|
|
|
707
745
|
|
|
708
746
|
t.time += dt * this.stopTimeScale
|
|
709
747
|
const clipT = Math.min(t.time, entry.exitTime)
|
|
710
|
-
const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime)
|
|
748
|
+
const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime) * t.scale
|
|
711
749
|
this.position.x = t.startX + t.dirX * fwd
|
|
712
750
|
this.position.z = t.startZ + t.dirZ * fwd
|
|
713
751
|
|