reze-engine 0.21.1 → 0.22.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/src/math.ts CHANGED
@@ -535,6 +535,23 @@ export class Mat4 {
535
535
  out[14] = pz
536
536
  }
537
537
 
538
+ // Position + rotation + UNIFORM scale. Scales the rotation basis (columns 0–2) by s;
539
+ // uniform scale keeps normals correct after the shader's normalize() (no inverse-transpose).
540
+ static fromPositionRotationScaleInto(
541
+ px: number, py: number, pz: number,
542
+ qx: number, qy: number, qz: number, qw: number,
543
+ s: number,
544
+ out: Float32Array
545
+ ): void {
546
+ Mat4.fromQuatInto(qx, qy, qz, qw, out, 0)
547
+ out[0] *= s; out[1] *= s; out[2] *= s
548
+ out[4] *= s; out[5] *= s; out[6] *= s
549
+ out[8] *= s; out[9] *= s; out[10] *= s
550
+ out[12] = px
551
+ out[13] = py
552
+ out[14] = pz
553
+ }
554
+
538
555
  // In-place 4x4 inverse into out array. Returns true on success, false if singular (out untouched).
539
556
  static inverseInto(m: Float32Array, out: Float32Array): boolean {
540
557
  const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3]
package/src/model.ts CHANGED
@@ -199,6 +199,17 @@ export class Model {
199
199
  return this._rotation
200
200
  }
201
201
 
202
+ /** Uniform scale (default 1). Used to fit a stage.pmx to the character. */
203
+ get scale(): number {
204
+ return this._scale
205
+ }
206
+
207
+ /** Whether this model renders. Hidden models skip the main, shadow, and pick passes;
208
+ * physics keeps running so they resume consistently. */
209
+ get visible(): boolean {
210
+ return this._visible
211
+ }
212
+
202
213
  setPosition(position: Vec3): void {
203
214
  this._position.set(position)
204
215
  this.rootMatrixDirty = true
@@ -209,6 +220,15 @@ export class Model {
209
220
  this.rootMatrixDirty = true
210
221
  }
211
222
 
223
+ setScale(scale: number): void {
224
+ this._scale = scale
225
+ this.rootMatrixDirty = true
226
+ }
227
+
228
+ setVisible(visible: boolean): void {
229
+ this._visible = visible
230
+ }
231
+
212
232
  private vertexData: Float32Array<ArrayBuffer>
213
233
  private baseVertexData: Float32Array<ArrayBuffer> // Original vertex data before morphing
214
234
  private vertexCount: number
@@ -254,6 +274,8 @@ export class Model {
254
274
  // Skip-when-identity flag avoids the extra mat mul per bone when unused.
255
275
  private _position: Vec3 = Vec3.zeros()
256
276
  private _rotation: Quat = Quat.identity()
277
+ private _scale: number = 1
278
+ private _visible: boolean = true
257
279
  private rootMatrixValues: Float32Array = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])
258
280
  private rootMatrixDirty: boolean = false
259
281
  private rootIsIdentity: boolean = true
@@ -851,11 +873,11 @@ export class Model {
851
873
 
852
874
  // Rebuild root matrix + cache identity-shortcut flag only when pos/rot changed.
853
875
  if (this.rootMatrixDirty) {
854
- const p = this._position, r = this._rotation
855
- Mat4.fromPositionRotationInto(p.x, p.y, p.z, r.x, r.y, r.z, r.w, this.rootMatrixValues)
876
+ const p = this._position, r = this._rotation, s = this._scale
877
+ Mat4.fromPositionRotationScaleInto(p.x, p.y, p.z, r.x, r.y, r.z, r.w, s, this.rootMatrixValues)
856
878
  this.rootIsIdentity =
857
879
  p.x === 0 && p.y === 0 && p.z === 0 &&
858
- r.x === 0 && r.y === 0 && r.z === 0 && r.w === 1
880
+ r.x === 0 && r.y === 0 && r.z === 0 && r.w === 1 && s === 1
859
881
  this.rootMatrixDirty = false
860
882
  }
861
883
 
package/src/vmd-loader.ts CHANGED
@@ -20,6 +20,18 @@ export interface VMDKeyFrame {
20
20
  morphFrames: MorphFrame[]
21
21
  }
22
22
 
23
+ /** One MMD camera keyframe. The camera looks at `target` from `distance` away, oriented by
24
+ * `rotation` (euler radians), with `fov` degrees. `interpolation` is 24 bytes: 6 channels
25
+ * (posX, posY, posZ, rotation, distance, fov) × 4 bezier bytes (x1, x2, y1, y2). */
26
+ export interface CameraKeyframe {
27
+ frame: number
28
+ distance: number
29
+ target: Vec3
30
+ rotation: Vec3 // euler radians
31
+ fov: number // degrees
32
+ interpolation: Uint8Array // 24 bytes
33
+ }
34
+
23
35
  export class VMDLoader {
24
36
  private view: DataView
25
37
  private offset = 0
@@ -46,6 +58,48 @@ export class VMDLoader {
46
58
  return loader.parse()
47
59
  }
48
60
 
61
+ /** Parse only the camera track (a dedicated camera VMD, or the camera block of any VMD).
62
+ * Returns [] if the file has no camera block (motion-only VMDs end after the morph block). */
63
+ static async loadCamera(url: string): Promise<CameraKeyframe[]> {
64
+ const loader = new VMDLoader(await fetch(url).then((r) => r.arrayBuffer()))
65
+ return loader.parseCamera()
66
+ }
67
+
68
+ static loadCameraFromBuffer(buffer: ArrayBuffer): CameraKeyframe[] {
69
+ return new VMDLoader(buffer).parseCamera()
70
+ }
71
+
72
+ // Seek past the bone + morph blocks (fixed record sizes) to the camera block, then read it.
73
+ // bone frame = 111 B (15 name + 4 frame + 12 pos + 16 rot + 64 interp); morph = 23 B
74
+ // (15 name + 4 frame + 4 weight); camera = 61 B.
75
+ private parseCamera(): CameraKeyframe[] {
76
+ this.offset = 0
77
+ const header = this.getString(30)
78
+ if (!header.startsWith("Vocaloid Motion Data")) throw new Error("Invalid VMD file header")
79
+ this.skip(20)
80
+ const boneCount = this.getUint32()
81
+ this.skip(boneCount * 111)
82
+ const morphCount = this.getUint32()
83
+ this.skip(morphCount * 23)
84
+ if (this.offset + 4 > this.view.buffer.byteLength) return [] // no camera block
85
+ const cameraCount = this.getUint32()
86
+
87
+ const frames: CameraKeyframe[] = []
88
+ for (let i = 0; i < cameraCount; i++) {
89
+ const frame = this.getUint32()
90
+ const distance = this.getFloat32()
91
+ const target = new Vec3(this.getFloat32(), this.getFloat32(), this.getFloat32())
92
+ const rotation = new Vec3(this.getFloat32(), this.getFloat32(), this.getFloat32())
93
+ const interpolation = new Uint8Array(24)
94
+ for (let j = 0; j < 24; j++) interpolation[j] = this.getUint8()
95
+ const fov = this.getUint32()
96
+ this.skip(1) // perspective flag (0 = perspective) — unused
97
+ frames.push({ frame, distance, target, rotation, fov, interpolation })
98
+ }
99
+ frames.sort((a, b) => a.frame - b.frame)
100
+ return frames
101
+ }
102
+
49
103
  private parse(): VMDKeyFrame[] {
50
104
  // Read header (30 bytes)
51
105
  const header = this.getString(30)