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.
- package/README.md +67 -66
- package/dist/bezier-interpolate.d.ts +15 -0
- package/dist/bezier-interpolate.d.ts.map +1 -0
- package/dist/bezier-interpolate.js +40 -0
- package/dist/engine.d.ts +12 -13
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +107 -175
- package/dist/ik-solver.d.ts +26 -0
- package/dist/ik-solver.d.ts.map +1 -0
- package/dist/ik-solver.js +372 -0
- package/dist/math.d.ts +1 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +8 -0
- package/dist/model.d.ts +46 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +201 -3
- package/dist/player.d.ts +100 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +409 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +57 -36
- package/dist/vmd-loader.d.ts +11 -1
- package/dist/vmd-loader.d.ts.map +1 -1
- package/dist/vmd-loader.js +91 -15
- package/package.json +1 -1
- package/src/bezier-interpolate.ts +47 -0
- package/src/camera.ts +358 -358
- package/src/engine.ts +123 -194
- package/src/ik-solver.ts +488 -0
- package/src/math.ts +555 -546
- package/src/model.ts +284 -3
- package/src/physics.ts +752 -752
- package/src/player.ts +490 -0
- package/src/pmx-loader.ts +1173 -1145
- package/src/vmd-loader.ts +276 -179
package/dist/vmd-loader.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
99
|
-
this.
|
|
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
|
-
//
|
|
107
|
-
|
|
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
|
@@ -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
|
+
}
|