reze-engine 0.28.0 → 0.29.0
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/README.md +18 -1
- package/dist/animation.d.ts +14 -0
- package/dist/animation.d.ts.map +1 -1
- package/dist/animation.js +15 -0
- package/dist/engine.d.ts +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +37 -8
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/locomotion.d.ts +90 -0
- package/dist/locomotion.d.ts.map +1 -0
- package/dist/locomotion.js +179 -0
- package/dist/model.d.ts +51 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +343 -53
- package/package.json +1 -1
- package/src/animation.ts +26 -0
- package/src/engine.ts +46 -8
- package/src/index.ts +9 -0
- package/src/locomotion.ts +254 -0
- package/src/model.ts +354 -65
package/src/animation.ts
CHANGED
|
@@ -61,6 +61,17 @@ export interface AnimationProgress {
|
|
|
61
61
|
|
|
62
62
|
export const FPS = 30
|
|
63
63
|
|
|
64
|
+
/** One weighted contribution to a blended pose (see Model.setBlendPose). */
|
|
65
|
+
export interface BlendEntry {
|
|
66
|
+
/** Name of a clip previously loaded with loadVmd/loadClip. */
|
|
67
|
+
name: string
|
|
68
|
+
/** Clip-local time in seconds. The caller owns the clock — wrap or clamp before passing. */
|
|
69
|
+
time: number
|
|
70
|
+
/** Relative weight >= 0. Entries are normalized over their sum; a sum below 1 is
|
|
71
|
+
* NOT renormalized — the remainder blends toward the rest pose. */
|
|
72
|
+
weight: number
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
interface QueuedAnimationRequest {
|
|
65
76
|
name: string
|
|
66
77
|
priority: number
|
|
@@ -152,6 +163,21 @@ export class AnimationState {
|
|
|
152
163
|
return true
|
|
153
164
|
}
|
|
154
165
|
|
|
166
|
+
/** Make `name` current at frame 0 and play it, bypassing priority arbitration.
|
|
167
|
+
* Used by crossfadeTo, which owns the transition and must not be queued behind
|
|
168
|
+
* the clip it is fading away from. */
|
|
169
|
+
forcePlay(name: string, loop: boolean): boolean {
|
|
170
|
+
if (!this.animations.has(name)) return false
|
|
171
|
+
this.currentAnimationName = name
|
|
172
|
+
this.currentFrame = 0
|
|
173
|
+
this.currentPriority = 0
|
|
174
|
+
this.currentLoop = loop
|
|
175
|
+
this.isPlaying = true
|
|
176
|
+
this.isPaused = false
|
|
177
|
+
this.nextAnimation = null
|
|
178
|
+
return true
|
|
179
|
+
}
|
|
180
|
+
|
|
155
181
|
update(deltaTime: number): { ended: boolean; animationName: string | null } {
|
|
156
182
|
if (!this.isPlaying || this.isPaused || this.currentAnimationName === null) {
|
|
157
183
|
return { ended: false, animationName: this.currentAnimationName }
|
package/src/engine.ts
CHANGED
|
@@ -3628,14 +3628,40 @@ export class Engine {
|
|
|
3628
3628
|
* frustum onto an otherwise empty floor. */
|
|
3629
3629
|
private shadowMapPopulated = true
|
|
3630
3630
|
private shadowLightVPDirty = true
|
|
3631
|
+
// Last shadow-volume center, to skip recomputes while nothing moves.
|
|
3632
|
+
private readonly shadowCenter = new Vec3(0, 11, 0)
|
|
3633
|
+
|
|
3631
3634
|
private updateShadowLightVP() {
|
|
3632
|
-
|
|
3635
|
+
// The 64×64-unit volume follows the camera target so a character carried far
|
|
3636
|
+
// from the origin by code-driven root motion stays inside the lit frustum.
|
|
3637
|
+
const t = this.camera.target
|
|
3638
|
+
const moved =
|
|
3639
|
+
Math.abs(t.x - this.shadowCenter.x) > 1e-3 ||
|
|
3640
|
+
Math.abs(t.y - this.shadowCenter.y) > 1e-3 ||
|
|
3641
|
+
Math.abs(t.z - this.shadowCenter.z) > 1e-3
|
|
3642
|
+
if (!this.shadowLightVPDirty && !moved) return
|
|
3633
3643
|
this.shadowLightVPDirty = false
|
|
3644
|
+
this.shadowCenter.setXYZ(t.x, t.y, t.z)
|
|
3645
|
+
|
|
3634
3646
|
const dir = new Vec3(this.sun.direction.x, this.sun.direction.y, this.sun.direction.z)
|
|
3635
3647
|
dir.normalize()
|
|
3636
|
-
const target = new Vec3(0, 11, 0)
|
|
3637
|
-
const eye = new Vec3(target.x - dir.x * 72, target.y - dir.y * 72, target.z - dir.z * 72)
|
|
3638
3648
|
const up = Math.abs(dir.y) > 0.99 ? new Vec3(0, 0, -1) : new Vec3(0, 1, 0)
|
|
3649
|
+
|
|
3650
|
+
// Snap the center to shadow-map texels in the light's right/up plane so the
|
|
3651
|
+
// moving volume doesn't shimmer the shadow edges while running.
|
|
3652
|
+
const right = Vec3.crossInto(up, dir, new Vec3(0, 0, 0)).normalize()
|
|
3653
|
+
const upv = Vec3.crossInto(dir, right, new Vec3(0, 0, 0))
|
|
3654
|
+
const texel = 64 / Engine.SHADOW_MAP_SIZE
|
|
3655
|
+
const tr = Math.round(t.dot(right) / texel) * texel
|
|
3656
|
+
const tu = Math.round(t.dot(upv) / texel) * texel
|
|
3657
|
+
const td = t.dot(dir)
|
|
3658
|
+
const target = new Vec3(
|
|
3659
|
+
right.x * tr + upv.x * tu + dir.x * td,
|
|
3660
|
+
right.y * tr + upv.y * tu + dir.y * td,
|
|
3661
|
+
right.z * tr + upv.z * tu + dir.z * td
|
|
3662
|
+
)
|
|
3663
|
+
|
|
3664
|
+
const eye = new Vec3(target.x - dir.x * 72, target.y - dir.y * 72, target.z - dir.z * 72)
|
|
3639
3665
|
const view = Mat4.lookAt(eye, target, up)
|
|
3640
3666
|
const proj = Mat4.orthographicLh(-32, 32, -32, 32, 1, 140)
|
|
3641
3667
|
const vp = proj.multiply(view)
|
|
@@ -4589,12 +4615,24 @@ export class Engine {
|
|
|
4589
4615
|
if (hasModels) {
|
|
4590
4616
|
this.updateInstances(deltaTime)
|
|
4591
4617
|
this.updateSkinMatrices()
|
|
4592
|
-
// Update camera target from bound model
|
|
4618
|
+
// Update camera target from bound model. Bone world matrices are model-space,
|
|
4619
|
+
// so compose the scene placement (setModelTransform) — otherwise the camera
|
|
4620
|
+
// ignores a moved/rotated model (code-driven root motion). Bone not found →
|
|
4621
|
+
// follow the model root itself.
|
|
4593
4622
|
if (this.cameraTargetModel) {
|
|
4594
|
-
const
|
|
4595
|
-
const
|
|
4596
|
-
|
|
4597
|
-
|
|
4623
|
+
const m = this.cameraTargetModel
|
|
4624
|
+
const pos = m.getBoneWorldPosition(this.cameraTargetBoneName)
|
|
4625
|
+
let px = m.position.x
|
|
4626
|
+
let py = m.position.y
|
|
4627
|
+
let pz = m.position.z
|
|
4628
|
+
if (pos) {
|
|
4629
|
+
const s = m.scale
|
|
4630
|
+
pos.setXYZ(pos.x * s, pos.y * s, pos.z * s)
|
|
4631
|
+
Quat.rotateVecInto(m.rotation, pos, pos)
|
|
4632
|
+
px += pos.x
|
|
4633
|
+
py += pos.y
|
|
4634
|
+
pz += pos.z
|
|
4635
|
+
}
|
|
4598
4636
|
this.camera.target.x = px + this.cameraTargetOffset.x
|
|
4599
4637
|
this.camera.target.y = py + this.cameraTargetOffset.y
|
|
4600
4638
|
this.camera.target.z = pz + this.cameraTargetOffset.z
|
package/src/index.ts
CHANGED
|
@@ -58,12 +58,19 @@ export type {
|
|
|
58
58
|
AnimationClip,
|
|
59
59
|
AnimationPlayOptions,
|
|
60
60
|
AnimationProgress,
|
|
61
|
+
BlendEntry,
|
|
61
62
|
BoneKeyframe,
|
|
62
63
|
IkKeyframe,
|
|
63
64
|
MorphKeyframe,
|
|
64
65
|
BoneInterpolation,
|
|
65
66
|
ControlPoint,
|
|
66
67
|
} from "./animation"
|
|
68
|
+
export {
|
|
69
|
+
LocomotionController,
|
|
70
|
+
type LocomotionClips,
|
|
71
|
+
type LocomotionOptions,
|
|
72
|
+
type LocomotionPose,
|
|
73
|
+
} from "./locomotion"
|
|
67
74
|
export {
|
|
68
75
|
FPS,
|
|
69
76
|
bezierInterpolate,
|
|
@@ -71,5 +78,7 @@ export {
|
|
|
71
78
|
rawInterpolationToBoneInterpolation,
|
|
72
79
|
} from "./animation"
|
|
73
80
|
export { VMDLoader, type CameraKeyframe, type IkFrame } from "./vmd-loader"
|
|
81
|
+
export { VMDWriter } from "./vmd-writer"
|
|
82
|
+
export { PmxLoader } from "./pmx-loader"
|
|
74
83
|
export { CameraAnimation, type CameraPose } from "./camera-animation"
|
|
75
84
|
export { RezePhysics } from "./physics"
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Game-style locomotion over the blend primitive: idle ↔ run ↔ sprint mixed by a
|
|
2
|
+
// smoothed speed level, yaw eased toward the input heading, root motion integrated
|
|
3
|
+
// in code (the clips are in-place). Zero renderer coupling — the controller only
|
|
4
|
+
// talks to Model.setBlendPose and returns the root transform for the host to apply
|
|
5
|
+
// via engine.setModelTransform.
|
|
6
|
+
|
|
7
|
+
import { Model } from "./model"
|
|
8
|
+
import { FPS, type BlendEntry } from "./animation"
|
|
9
|
+
import { Quat, Vec3 } from "./math"
|
|
10
|
+
|
|
11
|
+
export interface LocomotionClips {
|
|
12
|
+
/** Clip names previously loaded on the model (loadVmd/loadClip). */
|
|
13
|
+
idle: string
|
|
14
|
+
run: string
|
|
15
|
+
sprint?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LocomotionOptions {
|
|
19
|
+
/** Ground speed in MMD units/second at full run (default 67 — measured from the Unity
|
|
20
|
+
* locomotion pack's root motion before the in-place strip; ≈5.4 m/s at MMD scale).
|
|
21
|
+
* Match this to the clips or the feet slide. */
|
|
22
|
+
runSpeed?: number
|
|
23
|
+
/** Ground speed at full sprint (default 92, measured the same way; ≈7.4 m/s). */
|
|
24
|
+
sprintSpeed?: number
|
|
25
|
+
/** Response rate of the idle↔run↔sprint blend, in speed-levels/second (default 5). */
|
|
26
|
+
speedResponse?: number
|
|
27
|
+
/** Yaw easing rate toward the input heading, 1/second (default 10). */
|
|
28
|
+
turnResponse?: number
|
|
29
|
+
/** Heading error (radians) beyond which the character pivots strictly in place —
|
|
30
|
+
* no translation until the body is back within this cone (default PI/4). */
|
|
31
|
+
turnInPlaceThreshold?: number
|
|
32
|
+
/** Tank-mode steering rate, radians/second (default 2.5 ≈ 143°/s). */
|
|
33
|
+
steerRate?: number
|
|
34
|
+
/** Backpedal speed as a fraction of run speed in tank mode (default 0.5). */
|
|
35
|
+
backpedalScale?: number
|
|
36
|
+
/** World yaw the model faces at rotation 0. MMD models rest facing -Z, so facing a
|
|
37
|
+
* heading of `yaw` needs rotationY = yaw + PI — the default. */
|
|
38
|
+
yawOffset?: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The integrated root transform for this frame. `position` and `rotation` are
|
|
42
|
+
* REUSED instances owned by the controller — apply them immediately (they feed
|
|
43
|
+
* straight into engine.setModelTransform), don't store them. */
|
|
44
|
+
export interface LocomotionPose {
|
|
45
|
+
position: Vec3
|
|
46
|
+
/** Heading in radians: 0 = +Z, increasing toward +X. */
|
|
47
|
+
yaw: number
|
|
48
|
+
/** rotationY quat including yawOffset, ready for setModelTransform. */
|
|
49
|
+
rotation: Quat
|
|
50
|
+
/** Smoothed speed level: 0 idle, 1 run, 2 sprint. */
|
|
51
|
+
speedLevel: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const TWO_PI = Math.PI * 2
|
|
55
|
+
|
|
56
|
+
function wrapAngle(a: number): number {
|
|
57
|
+
while (a > Math.PI) a -= TWO_PI
|
|
58
|
+
while (a < -Math.PI) a += TWO_PI
|
|
59
|
+
return a
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class LocomotionController {
|
|
63
|
+
private readonly model: Model
|
|
64
|
+
private readonly clips: LocomotionClips
|
|
65
|
+
private readonly runSpeed: number
|
|
66
|
+
private readonly sprintSpeed: number
|
|
67
|
+
private readonly speedResponse: number
|
|
68
|
+
private readonly turnResponse: number
|
|
69
|
+
private readonly cosTurnThreshold: number
|
|
70
|
+
private readonly yawOffset: number
|
|
71
|
+
|
|
72
|
+
private inputX = 0
|
|
73
|
+
private inputY = 0
|
|
74
|
+
private inputSprint = false
|
|
75
|
+
// Tank mode: forward/steer relative to the CURRENT facing, camera never involved.
|
|
76
|
+
private tankMode = false
|
|
77
|
+
private inputForward = 0
|
|
78
|
+
private inputSteer = 0
|
|
79
|
+
private readonly steerRate: number
|
|
80
|
+
private readonly backpedalScale: number
|
|
81
|
+
|
|
82
|
+
private speedLevel = 0
|
|
83
|
+
private yaw = 0
|
|
84
|
+
// Movement direction = the INPUT heading, not the body yaw. The body turns
|
|
85
|
+
// cosmetically toward it; translating along the (sweeping) body yaw instead
|
|
86
|
+
// would nudge the character through the forward arc on every L↔R reversal.
|
|
87
|
+
private dirX = 0
|
|
88
|
+
private dirZ = 1
|
|
89
|
+
private readonly position = new Vec3(0, 0, 0)
|
|
90
|
+
private readonly rotation = new Quat(0, 0, 0, 1)
|
|
91
|
+
private idleTime = 0
|
|
92
|
+
private gaitPhase = 0 // 0..1, shared by run and sprint so legs stay aligned across the blend
|
|
93
|
+
|
|
94
|
+
private readonly entries: BlendEntry[]
|
|
95
|
+
private readonly pose: LocomotionPose
|
|
96
|
+
|
|
97
|
+
constructor(model: Model, clips: LocomotionClips, options?: LocomotionOptions) {
|
|
98
|
+
this.model = model
|
|
99
|
+
this.clips = clips
|
|
100
|
+
this.runSpeed = options?.runSpeed ?? 67
|
|
101
|
+
this.sprintSpeed = options?.sprintSpeed ?? 92
|
|
102
|
+
this.speedResponse = options?.speedResponse ?? 5
|
|
103
|
+
this.turnResponse = options?.turnResponse ?? 10
|
|
104
|
+
this.cosTurnThreshold = Math.cos(options?.turnInPlaceThreshold ?? Math.PI / 4)
|
|
105
|
+
this.steerRate = options?.steerRate ?? 2.5
|
|
106
|
+
this.backpedalScale = options?.backpedalScale ?? 0.5
|
|
107
|
+
this.yawOffset = options?.yawOffset ?? Math.PI
|
|
108
|
+
this.entries = [
|
|
109
|
+
{ name: clips.idle, time: 0, weight: 1 },
|
|
110
|
+
{ name: clips.run, time: 0, weight: 0 },
|
|
111
|
+
{ name: clips.sprint ?? clips.run, time: 0, weight: 0 },
|
|
112
|
+
]
|
|
113
|
+
this.pose = { position: this.position, yaw: 0, rotation: this.rotation, speedLevel: 0 }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** World-vector move input: x = +right (+X), y = +forward (+Z), magnitude clamped
|
|
117
|
+
* to 1. The character turns toward the vector, then runs along it. Call whenever
|
|
118
|
+
* input changes — the value holds between calls. */
|
|
119
|
+
setMove(x: number, y: number, sprint = false): void {
|
|
120
|
+
const m = Math.hypot(x, y)
|
|
121
|
+
if (m > 1) {
|
|
122
|
+
x /= m
|
|
123
|
+
y /= m
|
|
124
|
+
}
|
|
125
|
+
this.tankMode = false
|
|
126
|
+
this.inputX = x
|
|
127
|
+
this.inputY = y
|
|
128
|
+
this.inputSprint = sprint
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Tank-style input relative to the CURRENT facing, camera-independent and fully
|
|
132
|
+
* deterministic: `steer` (−1..1, + = her right) rotates at steerRate whether
|
|
133
|
+
* standing or moving, `forward` (+1 run ahead — curving while steering — or −1
|
|
134
|
+
* backpedal at backpedalScale). Holds between calls, like setMove. */
|
|
135
|
+
setDrive(forward: number, steer: number, sprint = false): void {
|
|
136
|
+
this.tankMode = true
|
|
137
|
+
this.inputForward = Math.max(-1, Math.min(1, forward))
|
|
138
|
+
this.inputSteer = Math.max(-1, Math.min(1, steer))
|
|
139
|
+
this.inputSprint = sprint
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Place the character (initial spawn or respawn). */
|
|
143
|
+
teleport(x: number, y: number, z: number, yaw = 0): void {
|
|
144
|
+
this.position.setXYZ(x, y, z)
|
|
145
|
+
this.yaw = yaw
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getPosition(): Vec3 {
|
|
149
|
+
return this.position
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Stop driving the model's pose (the blend is cleared; the single-clip player resumes). */
|
|
153
|
+
detach(): void {
|
|
154
|
+
this.model.clearBlendPose()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private clipDuration(name: string): number {
|
|
158
|
+
const frames = this.model.getClip(name)?.frameCount ?? 0
|
|
159
|
+
return frames > 0 ? frames / FPS : 1
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Advance one frame: integrates yaw + position, updates the clip clocks, hands the
|
|
163
|
+
* weighted pose to the model, and returns the root transform to apply. */
|
|
164
|
+
update(dt: number): LocomotionPose {
|
|
165
|
+
if (dt > 0.1) dt = 0.1 // tab-switch guard: never integrate a huge step
|
|
166
|
+
|
|
167
|
+
const hasSprint = this.clips.sprint !== undefined
|
|
168
|
+
let moving: boolean
|
|
169
|
+
let align = 1
|
|
170
|
+
let speedScale = 1
|
|
171
|
+
|
|
172
|
+
if (this.tankMode) {
|
|
173
|
+
// Steering rotates the facing directly — standing or moving — and the travel
|
|
174
|
+
// direction IS the facing, so no pivot gate and no drift by construction.
|
|
175
|
+
this.yaw = wrapAngle(this.yaw + this.inputSteer * this.steerRate * dt)
|
|
176
|
+
const fwd = this.inputForward
|
|
177
|
+
moving = Math.abs(fwd) > 0.05
|
|
178
|
+
if (moving) {
|
|
179
|
+
const back = fwd < 0
|
|
180
|
+
this.dirX = Math.sin(this.yaw) * Math.sign(fwd)
|
|
181
|
+
this.dirZ = Math.cos(this.yaw) * Math.sign(fwd)
|
|
182
|
+
speedScale = Math.abs(fwd) * (back ? this.backpedalScale : 1)
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
const m = Math.hypot(this.inputX, this.inputY)
|
|
186
|
+
moving = m > 0.05
|
|
187
|
+
// Yaw eases toward the input heading only while there is one. Turns are strictly
|
|
188
|
+
// in place: zero translation outside the threshold cone, ramping smoothly to full
|
|
189
|
+
// speed as the body aligns — so direction reversals (L-R-L) cannot drift.
|
|
190
|
+
if (moving) {
|
|
191
|
+
const desired = Math.atan2(this.inputX, this.inputY)
|
|
192
|
+
const err = wrapAngle(desired - this.yaw)
|
|
193
|
+
this.yaw = wrapAngle(this.yaw + err * Math.min(1, this.turnResponse * dt))
|
|
194
|
+
align = Math.max(0, (Math.cos(err) - this.cosTurnThreshold) / (1 - this.cosTurnThreshold))
|
|
195
|
+
this.dirX = this.inputX / m
|
|
196
|
+
this.dirZ = this.inputY / m
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Speed level ramps linearly toward the target; the pose blend follows it.
|
|
201
|
+
// (No sprint while backpedaling.)
|
|
202
|
+
const sprinting = this.inputSprint && hasSprint && !(this.tankMode && this.inputForward < 0)
|
|
203
|
+
const target = moving ? (sprinting ? 2 : 1) : 0
|
|
204
|
+
const maxStep = this.speedResponse * dt
|
|
205
|
+
const d = target - this.speedLevel
|
|
206
|
+
this.speedLevel += Math.abs(d) <= maxStep ? d : Math.sign(d) * maxStep
|
|
207
|
+
|
|
208
|
+
// Root motion along the travel direction. In-place clips carry no horizontal root.
|
|
209
|
+
const speed =
|
|
210
|
+
(this.speedLevel <= 1
|
|
211
|
+
? this.runSpeed * this.speedLevel
|
|
212
|
+
: this.runSpeed + (this.sprintSpeed - this.runSpeed) * (this.speedLevel - 1)) *
|
|
213
|
+
align *
|
|
214
|
+
speedScale
|
|
215
|
+
this.position.x += this.dirX * speed * dt
|
|
216
|
+
this.position.z += this.dirZ * speed * dt
|
|
217
|
+
|
|
218
|
+
// Clocks: idle free-runs; run/sprint share one normalized gait phase so a
|
|
219
|
+
// mid-blend stride stays on the same feet.
|
|
220
|
+
const idleDur = this.clipDuration(this.clips.idle)
|
|
221
|
+
const runDur = this.clipDuration(this.clips.run)
|
|
222
|
+
const sprintDur = hasSprint ? this.clipDuration(this.clips.sprint!) : runDur
|
|
223
|
+
this.idleTime = (this.idleTime + dt) % idleDur
|
|
224
|
+
const gaitDur = this.speedLevel <= 1 ? runDur : runDur + (sprintDur - runDur) * (this.speedLevel - 1)
|
|
225
|
+
this.gaitPhase = (this.gaitPhase + dt / gaitDur) % 1
|
|
226
|
+
|
|
227
|
+
// Weights along the 1D speed axis.
|
|
228
|
+
let wIdle: number, wRun: number, wSprint: number
|
|
229
|
+
if (this.speedLevel <= 1) {
|
|
230
|
+
wIdle = 1 - this.speedLevel
|
|
231
|
+
wRun = this.speedLevel
|
|
232
|
+
wSprint = 0
|
|
233
|
+
} else {
|
|
234
|
+
wIdle = 0
|
|
235
|
+
wRun = 2 - this.speedLevel
|
|
236
|
+
wSprint = this.speedLevel - 1
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.entries[0].time = this.idleTime
|
|
240
|
+
this.entries[0].weight = wIdle
|
|
241
|
+
this.entries[1].time = this.gaitPhase * runDur
|
|
242
|
+
this.entries[1].weight = wRun
|
|
243
|
+
this.entries[2].time = this.gaitPhase * sprintDur
|
|
244
|
+
this.entries[2].weight = wSprint
|
|
245
|
+
this.model.setBlendPose(this.entries)
|
|
246
|
+
|
|
247
|
+
const ry = this.yaw + this.yawOffset
|
|
248
|
+
const half = ry * 0.5
|
|
249
|
+
this.rotation.setXYZW(0, Math.sin(half), 0, Math.cos(half))
|
|
250
|
+
this.pose.yaw = this.yaw
|
|
251
|
+
this.pose.speedLevel = this.speedLevel
|
|
252
|
+
return this.pose
|
|
253
|
+
}
|
|
254
|
+
}
|