reze-engine 0.2.19 → 0.3.1

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.
@@ -1,4 +1,4 @@
1
- import { Quat } from "./math";
1
+ import { Quat, Vec3 } from "./math";
2
2
  export class VMDLoader {
3
3
  constructor(buffer) {
4
4
  this.offset = 0;
@@ -34,39 +34,67 @@ export class VMDLoader {
34
34
  const allBoneFrames = [];
35
35
  for (let i = 0; i < boneFrameCount; i++) {
36
36
  const boneFrame = this.readBoneFrame();
37
- // Convert frame number to time (assuming 30 FPS like the Rust code)
37
+ // Convert frame number to time (30 FPS)
38
38
  const FRAME_RATE = 30.0;
39
39
  const time = boneFrame.frame / FRAME_RATE;
40
40
  allBoneFrames.push({ time, boneFrame });
41
41
  }
42
+ // Read morph frame count (4 bytes, u32 little endian)
43
+ const morphFrameCount = this.getUint32();
44
+ // Read all morph frames
45
+ const allMorphFrames = [];
46
+ for (let i = 0; i < morphFrameCount; i++) {
47
+ const morphFrame = this.readMorphFrame();
48
+ // Convert frame number to time (30 FPS)
49
+ const FRAME_RATE = 30.0;
50
+ const time = morphFrame.frame / FRAME_RATE;
51
+ allMorphFrames.push({ time, morphFrame });
52
+ }
53
+ // Combine all frames and group by time
54
+ const allFrames = [];
55
+ for (const { time, boneFrame } of allBoneFrames) {
56
+ allFrames.push({ time, boneFrame });
57
+ }
58
+ for (const { time, morphFrame } of allMorphFrames) {
59
+ allFrames.push({ time, morphFrame });
60
+ }
61
+ // Sort by time
62
+ allFrames.sort((a, b) => a.time - b.time);
42
63
  // Group by time and convert to VMDKeyFrame format
43
- // Sort by time first
44
- allBoneFrames.sort((a, b) => a.time - b.time);
45
64
  const keyFrames = [];
46
65
  let currentTime = -1.0;
47
66
  let currentBoneFrames = [];
48
- for (const { time, boneFrame } of allBoneFrames) {
49
- if (Math.abs(time - currentTime) > 0.001) {
67
+ let currentMorphFrames = [];
68
+ for (const frame of allFrames) {
69
+ if (Math.abs(frame.time - currentTime) > 0.001) {
50
70
  // New time frame
51
- if (currentBoneFrames.length > 0) {
71
+ if (currentBoneFrames.length > 0 || currentMorphFrames.length > 0) {
52
72
  keyFrames.push({
53
73
  time: currentTime,
54
74
  boneFrames: currentBoneFrames,
75
+ morphFrames: currentMorphFrames,
55
76
  });
56
77
  }
57
- currentTime = time;
58
- currentBoneFrames = [boneFrame];
78
+ currentTime = frame.time;
79
+ currentBoneFrames = frame.boneFrame ? [frame.boneFrame] : [];
80
+ currentMorphFrames = frame.morphFrame ? [frame.morphFrame] : [];
59
81
  }
60
82
  else {
61
83
  // Same time frame
62
- currentBoneFrames.push(boneFrame);
84
+ if (frame.boneFrame) {
85
+ currentBoneFrames.push(frame.boneFrame);
86
+ }
87
+ if (frame.morphFrame) {
88
+ currentMorphFrames.push(frame.morphFrame);
89
+ }
63
90
  }
64
91
  }
65
92
  // Add the last frame
66
- if (currentBoneFrames.length > 0) {
93
+ if (currentBoneFrames.length > 0 || currentMorphFrames.length > 0) {
67
94
  keyFrames.push({
68
95
  time: currentTime,
69
96
  boneFrames: currentBoneFrames,
97
+ morphFrames: currentMorphFrames,
70
98
  });
71
99
  }
72
100
  return keyFrames;
@@ -95,22 +123,70 @@ export class VMDLoader {
95
123
  }
96
124
  // Read frame number (4 bytes, little endian)
97
125
  const frame = this.getUint32();
98
- // Skip position (12 bytes: 3 x f32, little endian)
99
- this.skip(12);
126
+ // Read position/translation (12 bytes: 3 x f32, little endian)
127
+ const posX = this.getFloat32();
128
+ const posY = this.getFloat32();
129
+ const posZ = this.getFloat32();
130
+ const translation = new Vec3(posX, posY, posZ);
100
131
  // Read rotation quaternion (16 bytes: 4 x f32, little endian)
101
132
  const rotX = this.getFloat32();
102
133
  const rotY = this.getFloat32();
103
134
  const rotZ = this.getFloat32();
104
135
  const rotW = this.getFloat32();
105
136
  const rotation = new Quat(rotX, rotY, rotZ, rotW);
106
- // Skip interpolation parameters (64 bytes)
107
- this.skip(64);
137
+ // Read interpolation parameters (64 bytes)
138
+ const interpolation = new Uint8Array(64);
139
+ for (let i = 0; i < 64; i++) {
140
+ interpolation[i] = this.getUint8();
141
+ }
108
142
  return {
109
143
  boneName,
110
144
  frame,
111
145
  rotation,
146
+ translation,
147
+ interpolation,
112
148
  };
113
149
  }
150
+ readMorphFrame() {
151
+ // Read morph name (15 bytes)
152
+ const nameBuffer = new Uint8Array(this.view.buffer, this.offset, 15);
153
+ this.offset += 15;
154
+ // Find the actual length of the morph name (stop at first null byte)
155
+ let nameLength = 15;
156
+ for (let i = 0; i < 15; i++) {
157
+ if (nameBuffer[i] === 0) {
158
+ nameLength = i;
159
+ break;
160
+ }
161
+ }
162
+ // Decode Shift-JIS morph name
163
+ let morphName;
164
+ try {
165
+ const nameSlice = nameBuffer.slice(0, nameLength);
166
+ morphName = this.decoder.decode(nameSlice);
167
+ }
168
+ catch {
169
+ // Fallback to lossy decoding if there were encoding errors
170
+ morphName = String.fromCharCode(...nameBuffer.slice(0, nameLength));
171
+ }
172
+ // Read frame number (4 bytes, little endian)
173
+ const frame = this.getUint32();
174
+ // Read weight (4 bytes, f32, little endian)
175
+ const weight = this.getFloat32();
176
+ return {
177
+ morphName,
178
+ frame,
179
+ weight,
180
+ };
181
+ }
182
+ getUint8() {
183
+ if (this.offset + 1 > this.view.buffer.byteLength) {
184
+ throw new RangeError(`Offset ${this.offset} + 1 exceeds buffer bounds ${this.view.buffer.byteLength}`);
185
+ }
186
+ const v = this.view.getUint8(this.offset);
187
+ this.offset += 1;
188
+ return v;
189
+ }
114
190
  getUint32() {
115
191
  if (this.offset + 4 > this.view.buffer.byteLength) {
116
192
  throw new RangeError(`Offset ${this.offset} + 4 exceeds buffer bounds ${this.view.buffer.byteLength}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.2.19",
3
+ "version": "0.3.1",
4
4
  "description": "A WebGPU-based MMD model renderer",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Bezier interpolation for VMD animations
3
+ * Based on the reference implementation from babylon-mmd
4
+ */
5
+
6
+ /**
7
+ * Bezier interpolation function
8
+ * @param x1 First control point X (0-127, normalized to 0-1)
9
+ * @param x2 Second control point X (0-127, normalized to 0-1)
10
+ * @param y1 First control point Y (0-127, normalized to 0-1)
11
+ * @param y2 Second control point Y (0-127, normalized to 0-1)
12
+ * @param t Interpolation parameter (0-1)
13
+ * @returns Interpolated value (0-1)
14
+ */
15
+ export function bezierInterpolate(x1: number, x2: number, y1: number, y2: number, t: number): number {
16
+ // Clamp t to [0, 1]
17
+ t = Math.max(0, Math.min(1, t))
18
+
19
+ // Binary search for the t value that gives us the desired x
20
+ // We're solving for t in the Bezier curve: x(t) = 3*(1-t)^2*t*x1 + 3*(1-t)*t^2*x2 + t^3
21
+ let start = 0
22
+ let end = 1
23
+ let mid = 0.5
24
+
25
+ // Iterate until we find the t value that gives us the desired x
26
+ for (let i = 0; i < 15; i++) {
27
+ // Evaluate Bezier curve at mid point
28
+ const x = 3 * (1 - mid) * (1 - mid) * mid * x1 + 3 * (1 - mid) * mid * mid * x2 + mid * mid * mid
29
+
30
+ if (Math.abs(x - t) < 0.0001) {
31
+ break
32
+ }
33
+
34
+ if (x < t) {
35
+ start = mid
36
+ } else {
37
+ end = mid
38
+ }
39
+
40
+ mid = (start + end) / 2
41
+ }
42
+
43
+ // Now evaluate the y value at this t
44
+ const y = 3 * (1 - mid) * (1 - mid) * mid * y1 + 3 * (1 - mid) * mid * mid * y2 + mid * mid * mid
45
+
46
+ return y
47
+ }