reze-engine 0.2.18 → 0.3.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 +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 +10 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +284 -144
- 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 +82 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +357 -4
- package/dist/pmx-loader.d.ts +3 -1
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +218 -130
- 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 +308 -165
- package/src/ik-solver.ts +488 -0
- package/src/math.ts +555 -546
- package/src/model.ts +930 -421
- package/src/physics.ts +752 -752
- package/src/pmx-loader.ts +1173 -1054
- package/src/vmd-loader.ts +276 -179
package/src/vmd-loader.ts
CHANGED
|
@@ -1,179 +1,276 @@
|
|
|
1
|
-
import { Quat } from "./math"
|
|
2
|
-
|
|
3
|
-
export interface BoneFrame {
|
|
4
|
-
boneName: string
|
|
5
|
-
frame: number
|
|
6
|
-
rotation: Quat
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Read
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
for (
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
1
|
+
import { Quat, Vec3 } from "./math"
|
|
2
|
+
|
|
3
|
+
export interface BoneFrame {
|
|
4
|
+
boneName: string
|
|
5
|
+
frame: number
|
|
6
|
+
rotation: Quat
|
|
7
|
+
translation: Vec3
|
|
8
|
+
interpolation: Uint8Array // 64 bytes of interpolation parameters
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface MorphFrame {
|
|
12
|
+
morphName: string
|
|
13
|
+
frame: number
|
|
14
|
+
weight: number // 0.0 to 1.0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface VMDKeyFrame {
|
|
18
|
+
time: number // in seconds
|
|
19
|
+
boneFrames: BoneFrame[]
|
|
20
|
+
morphFrames: MorphFrame[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class VMDLoader {
|
|
24
|
+
private view: DataView
|
|
25
|
+
private offset = 0
|
|
26
|
+
private decoder: TextDecoder
|
|
27
|
+
|
|
28
|
+
private constructor(buffer: ArrayBuffer) {
|
|
29
|
+
this.view = new DataView(buffer)
|
|
30
|
+
// Try to use Shift-JIS decoder, fallback to UTF-8 if not available
|
|
31
|
+
try {
|
|
32
|
+
this.decoder = new TextDecoder("shift-jis")
|
|
33
|
+
} catch {
|
|
34
|
+
// Fallback to UTF-8 if Shift-JIS is not supported
|
|
35
|
+
this.decoder = new TextDecoder("utf-8")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static async load(url: string): Promise<VMDKeyFrame[]> {
|
|
40
|
+
const loader = new VMDLoader(await fetch(url).then((r) => r.arrayBuffer()))
|
|
41
|
+
return loader.parse()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static loadFromBuffer(buffer: ArrayBuffer): VMDKeyFrame[] {
|
|
45
|
+
const loader = new VMDLoader(buffer)
|
|
46
|
+
return loader.parse()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private parse(): VMDKeyFrame[] {
|
|
50
|
+
// Read header (30 bytes)
|
|
51
|
+
const header = this.getString(30)
|
|
52
|
+
if (!header.startsWith("Vocaloid Motion Data")) {
|
|
53
|
+
throw new Error("Invalid VMD file header")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Skip model name (20 bytes)
|
|
57
|
+
this.skip(20)
|
|
58
|
+
|
|
59
|
+
// Read bone frame count (4 bytes, u32 little endian)
|
|
60
|
+
const boneFrameCount = this.getUint32()
|
|
61
|
+
|
|
62
|
+
// Read all bone frames
|
|
63
|
+
const allBoneFrames: Array<{ time: number; boneFrame: BoneFrame }> = []
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < boneFrameCount; i++) {
|
|
66
|
+
const boneFrame = this.readBoneFrame()
|
|
67
|
+
|
|
68
|
+
// Convert frame number to time (30 FPS)
|
|
69
|
+
const FRAME_RATE = 30.0
|
|
70
|
+
const time = boneFrame.frame / FRAME_RATE
|
|
71
|
+
|
|
72
|
+
allBoneFrames.push({ time, boneFrame })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Read morph frame count (4 bytes, u32 little endian)
|
|
76
|
+
const morphFrameCount = this.getUint32()
|
|
77
|
+
|
|
78
|
+
// Read all morph frames
|
|
79
|
+
const allMorphFrames: Array<{ time: number; morphFrame: MorphFrame }> = []
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < morphFrameCount; i++) {
|
|
82
|
+
const morphFrame = this.readMorphFrame()
|
|
83
|
+
|
|
84
|
+
// Convert frame number to time (30 FPS)
|
|
85
|
+
const FRAME_RATE = 30.0
|
|
86
|
+
const time = morphFrame.frame / FRAME_RATE
|
|
87
|
+
|
|
88
|
+
allMorphFrames.push({ time, morphFrame })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Combine all frames and group by time
|
|
92
|
+
const allFrames: Array<{ time: number; boneFrame?: BoneFrame; morphFrame?: MorphFrame }> = []
|
|
93
|
+
for (const { time, boneFrame } of allBoneFrames) {
|
|
94
|
+
allFrames.push({ time, boneFrame })
|
|
95
|
+
}
|
|
96
|
+
for (const { time, morphFrame } of allMorphFrames) {
|
|
97
|
+
allFrames.push({ time, morphFrame })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Sort by time
|
|
101
|
+
allFrames.sort((a, b) => a.time - b.time)
|
|
102
|
+
|
|
103
|
+
// Group by time and convert to VMDKeyFrame format
|
|
104
|
+
const keyFrames: VMDKeyFrame[] = []
|
|
105
|
+
let currentTime = -1.0
|
|
106
|
+
let currentBoneFrames: BoneFrame[] = []
|
|
107
|
+
let currentMorphFrames: MorphFrame[] = []
|
|
108
|
+
|
|
109
|
+
for (const frame of allFrames) {
|
|
110
|
+
if (Math.abs(frame.time - currentTime) > 0.001) {
|
|
111
|
+
// New time frame
|
|
112
|
+
if (currentBoneFrames.length > 0 || currentMorphFrames.length > 0) {
|
|
113
|
+
keyFrames.push({
|
|
114
|
+
time: currentTime,
|
|
115
|
+
boneFrames: currentBoneFrames,
|
|
116
|
+
morphFrames: currentMorphFrames,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
currentTime = frame.time
|
|
120
|
+
currentBoneFrames = frame.boneFrame ? [frame.boneFrame] : []
|
|
121
|
+
currentMorphFrames = frame.morphFrame ? [frame.morphFrame] : []
|
|
122
|
+
} else {
|
|
123
|
+
// Same time frame
|
|
124
|
+
if (frame.boneFrame) {
|
|
125
|
+
currentBoneFrames.push(frame.boneFrame)
|
|
126
|
+
}
|
|
127
|
+
if (frame.morphFrame) {
|
|
128
|
+
currentMorphFrames.push(frame.morphFrame)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Add the last frame
|
|
134
|
+
if (currentBoneFrames.length > 0 || currentMorphFrames.length > 0) {
|
|
135
|
+
keyFrames.push({
|
|
136
|
+
time: currentTime,
|
|
137
|
+
boneFrames: currentBoneFrames,
|
|
138
|
+
morphFrames: currentMorphFrames,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return keyFrames
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private readBoneFrame(): BoneFrame {
|
|
146
|
+
// Read bone name (15 bytes)
|
|
147
|
+
const nameBuffer = new Uint8Array(this.view.buffer, this.offset, 15)
|
|
148
|
+
this.offset += 15
|
|
149
|
+
|
|
150
|
+
// Find the actual length of the bone name (stop at first null byte)
|
|
151
|
+
let nameLength = 15
|
|
152
|
+
for (let i = 0; i < 15; i++) {
|
|
153
|
+
if (nameBuffer[i] === 0) {
|
|
154
|
+
nameLength = i
|
|
155
|
+
break
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Decode Shift-JIS bone name
|
|
160
|
+
let boneName: string
|
|
161
|
+
try {
|
|
162
|
+
const nameSlice = nameBuffer.slice(0, nameLength)
|
|
163
|
+
boneName = this.decoder.decode(nameSlice)
|
|
164
|
+
} catch {
|
|
165
|
+
// Fallback to lossy decoding if there were encoding errors
|
|
166
|
+
boneName = String.fromCharCode(...nameBuffer.slice(0, nameLength))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Read frame number (4 bytes, little endian)
|
|
170
|
+
const frame = this.getUint32()
|
|
171
|
+
|
|
172
|
+
// Read position/translation (12 bytes: 3 x f32, little endian)
|
|
173
|
+
const posX = this.getFloat32()
|
|
174
|
+
const posY = this.getFloat32()
|
|
175
|
+
const posZ = this.getFloat32()
|
|
176
|
+
const translation = new Vec3(posX, posY, posZ)
|
|
177
|
+
|
|
178
|
+
// Read rotation quaternion (16 bytes: 4 x f32, little endian)
|
|
179
|
+
const rotX = this.getFloat32()
|
|
180
|
+
const rotY = this.getFloat32()
|
|
181
|
+
const rotZ = this.getFloat32()
|
|
182
|
+
const rotW = this.getFloat32()
|
|
183
|
+
const rotation = new Quat(rotX, rotY, rotZ, rotW)
|
|
184
|
+
|
|
185
|
+
// Read interpolation parameters (64 bytes)
|
|
186
|
+
const interpolation = new Uint8Array(64)
|
|
187
|
+
for (let i = 0; i < 64; i++) {
|
|
188
|
+
interpolation[i] = this.getUint8()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
boneName,
|
|
193
|
+
frame,
|
|
194
|
+
rotation,
|
|
195
|
+
translation,
|
|
196
|
+
interpolation,
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private readMorphFrame(): MorphFrame {
|
|
201
|
+
// Read morph name (15 bytes)
|
|
202
|
+
const nameBuffer = new Uint8Array(this.view.buffer, this.offset, 15)
|
|
203
|
+
this.offset += 15
|
|
204
|
+
|
|
205
|
+
// Find the actual length of the morph name (stop at first null byte)
|
|
206
|
+
let nameLength = 15
|
|
207
|
+
for (let i = 0; i < 15; i++) {
|
|
208
|
+
if (nameBuffer[i] === 0) {
|
|
209
|
+
nameLength = i
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Decode Shift-JIS morph name
|
|
215
|
+
let morphName: string
|
|
216
|
+
try {
|
|
217
|
+
const nameSlice = nameBuffer.slice(0, nameLength)
|
|
218
|
+
morphName = this.decoder.decode(nameSlice)
|
|
219
|
+
} catch {
|
|
220
|
+
// Fallback to lossy decoding if there were encoding errors
|
|
221
|
+
morphName = String.fromCharCode(...nameBuffer.slice(0, nameLength))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Read frame number (4 bytes, little endian)
|
|
225
|
+
const frame = this.getUint32()
|
|
226
|
+
|
|
227
|
+
// Read weight (4 bytes, f32, little endian)
|
|
228
|
+
const weight = this.getFloat32()
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
morphName,
|
|
232
|
+
frame,
|
|
233
|
+
weight,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private getUint8(): number {
|
|
238
|
+
if (this.offset + 1 > this.view.buffer.byteLength) {
|
|
239
|
+
throw new RangeError(`Offset ${this.offset} + 1 exceeds buffer bounds ${this.view.buffer.byteLength}`)
|
|
240
|
+
}
|
|
241
|
+
const v = this.view.getUint8(this.offset)
|
|
242
|
+
this.offset += 1
|
|
243
|
+
return v
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private getUint32(): number {
|
|
247
|
+
if (this.offset + 4 > this.view.buffer.byteLength) {
|
|
248
|
+
throw new RangeError(`Offset ${this.offset} + 4 exceeds buffer bounds ${this.view.buffer.byteLength}`)
|
|
249
|
+
}
|
|
250
|
+
const v = this.view.getUint32(this.offset, true) // true = little endian
|
|
251
|
+
this.offset += 4
|
|
252
|
+
return v
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private getFloat32(): number {
|
|
256
|
+
if (this.offset + 4 > this.view.buffer.byteLength) {
|
|
257
|
+
throw new RangeError(`Offset ${this.offset} + 4 exceeds buffer bounds ${this.view.buffer.byteLength}`)
|
|
258
|
+
}
|
|
259
|
+
const v = this.view.getFloat32(this.offset, true) // true = little endian
|
|
260
|
+
this.offset += 4
|
|
261
|
+
return v
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private getString(len: number): string {
|
|
265
|
+
const bytes = new Uint8Array(this.view.buffer, this.offset, len)
|
|
266
|
+
this.offset += len
|
|
267
|
+
return String.fromCharCode(...bytes)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private skip(bytes: number): void {
|
|
271
|
+
if (this.offset + bytes > this.view.buffer.byteLength) {
|
|
272
|
+
throw new RangeError(`Offset ${this.offset} + ${bytes} exceeds buffer bounds ${this.view.buffer.byteLength}`)
|
|
273
|
+
}
|
|
274
|
+
this.offset += bytes
|
|
275
|
+
}
|
|
276
|
+
}
|