reze-engine 0.54.16 → 0.55.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/dist/engine.d.ts +210 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1063 -11
- package/dist/graph/presets/pool_floor.d.ts +3 -0
- package/dist/graph/presets/pool_floor.d.ts.map +1 -0
- package/dist/graph/presets/pool_floor.js +42 -0
- package/dist/graph/presets/water.d.ts +3 -0
- package/dist/graph/presets/water.d.ts.map +1 -0
- package/dist/graph/presets/water.js +31 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -0
- package/dist/overlay.d.ts +200 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +806 -0
- package/dist/physics/autofit.d.ts +147 -0
- package/dist/physics/autofit.d.ts.map +1 -0
- package/dist/physics/autofit.js +501 -0
- package/dist/pmx-document.d.ts +218 -0
- package/dist/pmx-document.d.ts.map +1 -0
- package/dist/pmx-document.js +685 -0
- package/dist/shaders/passes/field-blit.d.ts +26 -0
- package/dist/shaders/passes/field-blit.d.ts.map +1 -0
- package/dist/shaders/passes/field-blit.js +65 -0
- package/dist/shaders/passes/ground-noise.d.ts +7 -0
- package/dist/shaders/passes/ground-noise.d.ts.map +1 -0
- package/dist/shaders/passes/ground-noise.js +88 -0
- package/dist/shaders/passes/overlay.d.ts +3 -0
- package/dist/shaders/passes/overlay.d.ts.map +1 -0
- package/dist/shaders/passes/overlay.js +164 -0
- package/dist/shaders/passes/sim.d.ts +34 -0
- package/dist/shaders/passes/sim.d.ts.map +1 -0
- package/dist/shaders/passes/sim.js +169 -0
- package/dist/shaders/passes/wireframe.d.ts +2 -0
- package/dist/shaders/passes/wireframe.d.ts.map +1 -0
- package/dist/shaders/passes/wireframe.js +107 -0
- package/dist/shaders/score-api.d.ts +10 -0
- package/dist/shaders/score-api.d.ts.map +1 -0
- package/dist/shaders/score-api.js +114 -0
- package/package.json +1 -1
- package/src/engine.ts +1147 -13
- package/src/index.ts +50 -0
- package/src/model.ts +9 -0
- package/src/overlay.ts +1000 -0
- package/src/pmx-document.ts +882 -0
- package/src/shaders/passes/overlay.ts +166 -0
- package/src/shaders/passes/wireframe.ts +108 -0
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
// The PMX file, as data, losslessly.
|
|
2
|
+
//
|
|
3
|
+
// SEPARATE from Model on purpose. Model is what the renderer needs and drops
|
|
4
|
+
// everything else — English names, memos, display frames, additional UVs, SDEF
|
|
5
|
+
// and QDEF weights, per-vertex edge scale. That is correct for drawing: making
|
|
6
|
+
// Model lossless would make every viewer pay per-vertex costs for a scene it
|
|
7
|
+
// never edits. It is fatal for editing. A writer fed from Model would turn
|
|
8
|
+
// "rename a bone and save" into "rename a bone and lose every morph's panel
|
|
9
|
+
// assignment", and the user would rightly call that a broken editor.
|
|
10
|
+
//
|
|
11
|
+
// So this reads the file into a structure that mirrors it one for one, and
|
|
12
|
+
// writes that structure back. Read and write live in ONE module and each section
|
|
13
|
+
// keeps its reader and writer adjacent, because the only real failure mode here
|
|
14
|
+
// is the two drifting apart in field order — which the format cannot detect and
|
|
15
|
+
// which produces a file that loads as garbage.
|
|
16
|
+
//
|
|
17
|
+
// Round-tripping is checked byte-for-byte against real models in
|
|
18
|
+
// tests/pmx-roundtrip.test.mjs. That test is the product promise in executable
|
|
19
|
+
// form, so it is worth more than any of the code below.
|
|
20
|
+
|
|
21
|
+
/** 0 = UTF-16LE, 1 = UTF-8. Preserved: writing back in the other one is a
|
|
22
|
+
* different file, even where it decodes the same. */
|
|
23
|
+
export type PmxEncoding = 0 | 1
|
|
24
|
+
|
|
25
|
+
export interface PmxGlobals {
|
|
26
|
+
encoding: PmxEncoding
|
|
27
|
+
additionalUvCount: number
|
|
28
|
+
/** Vertex indices are UNSIGNED; every other index is signed, with -1 for none. */
|
|
29
|
+
vertexIndexSize: 1 | 2 | 4
|
|
30
|
+
textureIndexSize: 1 | 2 | 4
|
|
31
|
+
materialIndexSize: 1 | 2 | 4
|
|
32
|
+
boneIndexSize: 1 | 2 | 4
|
|
33
|
+
morphIndexSize: 1 | 2 | 4
|
|
34
|
+
rigidbodyIndexSize: 1 | 2 | 4
|
|
35
|
+
/** Anything past the eight PMX 2.0 knows about, kept so the header's own
|
|
36
|
+
* length survives a round trip. */
|
|
37
|
+
extra: number[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type PmxWeightType = 0 | 1 | 2 | 3 | 4 // BDEF1, BDEF2, BDEF4, SDEF, QDEF
|
|
41
|
+
|
|
42
|
+
export interface PmxVertex {
|
|
43
|
+
position: [number, number, number]
|
|
44
|
+
normal: [number, number, number]
|
|
45
|
+
uv: [number, number]
|
|
46
|
+
/** additionalUvCount entries, each a vec4. */
|
|
47
|
+
additionalUv: [number, number, number, number][]
|
|
48
|
+
weightType: PmxWeightType
|
|
49
|
+
/** 1, 2 or 4 entries depending on weightType. */
|
|
50
|
+
bones: number[]
|
|
51
|
+
/** BDEF2 keeps one, BDEF4 and QDEF keep four, BDEF1 keeps none. */
|
|
52
|
+
weights: number[]
|
|
53
|
+
/** SDEF only: C, R0, R1. */
|
|
54
|
+
sdef?: { c: [number, number, number]; r0: [number, number, number]; r1: [number, number, number] }
|
|
55
|
+
edgeScale: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PmxMaterial {
|
|
59
|
+
name: string
|
|
60
|
+
nameEn: string
|
|
61
|
+
diffuse: [number, number, number, number]
|
|
62
|
+
specular: [number, number, number]
|
|
63
|
+
specularPower: number
|
|
64
|
+
ambient: [number, number, number]
|
|
65
|
+
/** Bit 0 double-sided, 1 ground shadow, 2 to self-shadow map, 3 from
|
|
66
|
+
* self-shadow, 4 edge, 5 vertex colour, 6 point draw, 7 line draw. */
|
|
67
|
+
drawFlags: number
|
|
68
|
+
edgeColor: [number, number, number, number]
|
|
69
|
+
edgeSize: number
|
|
70
|
+
textureIndex: number
|
|
71
|
+
sphereIndex: number
|
|
72
|
+
/** 0 none, 1 multiply, 2 add, 3 subtexture. */
|
|
73
|
+
sphereMode: number
|
|
74
|
+
/** 0 = toonIndex is a texture index, 1 = it is a shared toon 0-9. */
|
|
75
|
+
toonShared: number
|
|
76
|
+
toonIndex: number
|
|
77
|
+
memo: string
|
|
78
|
+
/** How many INDICES this material owns, not faces. Materials own a contiguous
|
|
79
|
+
* run of the index buffer, which is why face order is document identity. */
|
|
80
|
+
indexCount: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface PmxIkLink {
|
|
84
|
+
boneIndex: number
|
|
85
|
+
hasLimit: boolean
|
|
86
|
+
limitMin?: [number, number, number]
|
|
87
|
+
limitMax?: [number, number, number]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PmxBone {
|
|
91
|
+
name: string
|
|
92
|
+
nameEn: string
|
|
93
|
+
position: [number, number, number]
|
|
94
|
+
parentIndex: number
|
|
95
|
+
layer: number
|
|
96
|
+
/** 0x0001 tail is a bone · 0x0002 rotatable · 0x0004 movable · 0x0008 visible
|
|
97
|
+
* 0x0010 operable · 0x0020 IK · 0x0080 local append · 0x0100 append rotate
|
|
98
|
+
* 0x0200 append move · 0x0400 fixed axis · 0x0800 local axes
|
|
99
|
+
* 0x1000 after physics · 0x2000 external parent */
|
|
100
|
+
flags: number
|
|
101
|
+
tailBoneIndex?: number
|
|
102
|
+
tailPosition?: [number, number, number]
|
|
103
|
+
appendParentIndex?: number
|
|
104
|
+
appendRatio?: number
|
|
105
|
+
fixedAxis?: [number, number, number]
|
|
106
|
+
localAxisX?: [number, number, number]
|
|
107
|
+
localAxisZ?: [number, number, number]
|
|
108
|
+
externalKey?: number
|
|
109
|
+
ik?: {
|
|
110
|
+
targetIndex: number
|
|
111
|
+
loopCount: number
|
|
112
|
+
limitAngle: number
|
|
113
|
+
links: PmxIkLink[]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type PmxMorphOffset =
|
|
118
|
+
| { kind: "group"; index: number; influence: number }
|
|
119
|
+
| { kind: "vertex"; index: number; offset: [number, number, number] }
|
|
120
|
+
| { kind: "bone"; index: number; translation: [number, number, number]; rotation: [number, number, number, number] }
|
|
121
|
+
| { kind: "uv"; index: number; offset: [number, number, number, number] }
|
|
122
|
+
| {
|
|
123
|
+
kind: "material"
|
|
124
|
+
index: number
|
|
125
|
+
/** 0 multiply, 1 add. */
|
|
126
|
+
operation: number
|
|
127
|
+
diffuse: [number, number, number, number]
|
|
128
|
+
specular: [number, number, number]
|
|
129
|
+
specularPower: number
|
|
130
|
+
ambient: [number, number, number]
|
|
131
|
+
edgeColor: [number, number, number, number]
|
|
132
|
+
edgeSize: number
|
|
133
|
+
textureTint: [number, number, number, number]
|
|
134
|
+
sphereTint: [number, number, number, number]
|
|
135
|
+
toonTint: [number, number, number, number]
|
|
136
|
+
}
|
|
137
|
+
| { kind: "flip"; index: number; influence: number }
|
|
138
|
+
| {
|
|
139
|
+
kind: "impulse"
|
|
140
|
+
index: number
|
|
141
|
+
local: number
|
|
142
|
+
velocity: [number, number, number]
|
|
143
|
+
torque: [number, number, number]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface PmxMorph {
|
|
147
|
+
name: string
|
|
148
|
+
nameEn: string
|
|
149
|
+
/** 0 reserved, 1 eyebrow, 2 eye, 3 mouth, 4 other. What MMD groups sliders by. */
|
|
150
|
+
panel: number
|
|
151
|
+
/** 0 group, 1 vertex, 2 bone, 3-7 UV, 8 material, 9 flip, 10 impulse. */
|
|
152
|
+
type: number
|
|
153
|
+
offsets: PmxMorphOffset[]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface PmxDisplayFrame {
|
|
157
|
+
name: string
|
|
158
|
+
nameEn: string
|
|
159
|
+
/** 1 for the two frames MMD reserves (Root and 表情). */
|
|
160
|
+
special: number
|
|
161
|
+
/** type 0 = bone index, 1 = morph index. */
|
|
162
|
+
elements: { type: number; index: number }[]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface PmxRigidbody {
|
|
166
|
+
name: string
|
|
167
|
+
nameEn: string
|
|
168
|
+
boneIndex: number
|
|
169
|
+
group: number
|
|
170
|
+
nonCollideMask: number
|
|
171
|
+
/** 0 sphere, 1 box, 2 capsule. */
|
|
172
|
+
shape: number
|
|
173
|
+
size: [number, number, number]
|
|
174
|
+
position: [number, number, number]
|
|
175
|
+
rotation: [number, number, number]
|
|
176
|
+
mass: number
|
|
177
|
+
linearDamping: number
|
|
178
|
+
angularDamping: number
|
|
179
|
+
restitution: number
|
|
180
|
+
friction: number
|
|
181
|
+
/** 0 follows its bone, 1 physics, 2 physics then aligned to its bone. */
|
|
182
|
+
physicsMode: number
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface PmxJoint {
|
|
186
|
+
name: string
|
|
187
|
+
nameEn: string
|
|
188
|
+
/** 0 is 6DOF-with-spring, the only one PMX 2.0 has. 2.1 adds 1-5. */
|
|
189
|
+
type: number
|
|
190
|
+
rigidbodyA: number
|
|
191
|
+
rigidbodyB: number
|
|
192
|
+
position: [number, number, number]
|
|
193
|
+
rotation: [number, number, number]
|
|
194
|
+
positionMin: [number, number, number]
|
|
195
|
+
positionMax: [number, number, number]
|
|
196
|
+
rotationMin: [number, number, number]
|
|
197
|
+
rotationMax: [number, number, number]
|
|
198
|
+
springPosition: [number, number, number]
|
|
199
|
+
springRotation: [number, number, number]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface PmxDocument {
|
|
203
|
+
version: number
|
|
204
|
+
globals: PmxGlobals
|
|
205
|
+
name: string
|
|
206
|
+
nameEn: string
|
|
207
|
+
comment: string
|
|
208
|
+
commentEn: string
|
|
209
|
+
vertices: PmxVertex[]
|
|
210
|
+
/** Flat triples. Materials own contiguous runs of this, so its ORDER is
|
|
211
|
+
* identity: reordering it silently re-assigns geometry between materials. */
|
|
212
|
+
indices: Uint32Array
|
|
213
|
+
textures: string[]
|
|
214
|
+
materials: PmxMaterial[]
|
|
215
|
+
bones: PmxBone[]
|
|
216
|
+
morphs: PmxMorph[]
|
|
217
|
+
displayFrames: PmxDisplayFrame[]
|
|
218
|
+
rigidbodies: PmxRigidbody[]
|
|
219
|
+
joints: PmxJoint[]
|
|
220
|
+
/** PMX 2.1 soft bodies, kept as the bytes they arrived as. They are vanishingly
|
|
221
|
+
* rare and nothing here edits them — but a model that has them must still save
|
|
222
|
+
* as the same file, and preserving the tail costs nothing. */
|
|
223
|
+
trailing: Uint8Array | null
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ──────────────────────────────────────────────────────────────────
|
|
227
|
+
// Bytes
|
|
228
|
+
|
|
229
|
+
class Cursor {
|
|
230
|
+
readonly view: DataView
|
|
231
|
+
readonly bytes: Uint8Array
|
|
232
|
+
at = 0
|
|
233
|
+
constructor(buffer: ArrayBuffer) {
|
|
234
|
+
this.view = new DataView(buffer)
|
|
235
|
+
this.bytes = new Uint8Array(buffer)
|
|
236
|
+
}
|
|
237
|
+
u8() {
|
|
238
|
+
return this.view.getUint8(this.at++)
|
|
239
|
+
}
|
|
240
|
+
u16() {
|
|
241
|
+
const v = this.view.getUint16(this.at, true)
|
|
242
|
+
this.at += 2
|
|
243
|
+
return v
|
|
244
|
+
}
|
|
245
|
+
i32() {
|
|
246
|
+
const v = this.view.getInt32(this.at, true)
|
|
247
|
+
this.at += 4
|
|
248
|
+
return v
|
|
249
|
+
}
|
|
250
|
+
f32() {
|
|
251
|
+
const v = this.view.getFloat32(this.at, true)
|
|
252
|
+
this.at += 4
|
|
253
|
+
return v
|
|
254
|
+
}
|
|
255
|
+
vec2(): [number, number] {
|
|
256
|
+
return [this.f32(), this.f32()]
|
|
257
|
+
}
|
|
258
|
+
vec3(): [number, number, number] {
|
|
259
|
+
return [this.f32(), this.f32(), this.f32()]
|
|
260
|
+
}
|
|
261
|
+
vec4(): [number, number, number, number] {
|
|
262
|
+
return [this.f32(), this.f32(), this.f32(), this.f32()]
|
|
263
|
+
}
|
|
264
|
+
/** Unsigned — vertex indices only. */
|
|
265
|
+
uindex(size: number): number {
|
|
266
|
+
if (size === 1) return this.u8()
|
|
267
|
+
if (size === 2) return this.u16()
|
|
268
|
+
return this.view.getUint32((this.at += 4) - 4, true)
|
|
269
|
+
}
|
|
270
|
+
/** Signed, with the all-ones pattern meaning "none". Every index but vertex. */
|
|
271
|
+
index(size: number): number {
|
|
272
|
+
if (size === 1) return this.view.getInt8(this.at++)
|
|
273
|
+
if (size === 2) {
|
|
274
|
+
const v = this.view.getInt16(this.at, true)
|
|
275
|
+
this.at += 2
|
|
276
|
+
return v
|
|
277
|
+
}
|
|
278
|
+
return this.i32()
|
|
279
|
+
}
|
|
280
|
+
text(encoding: PmxEncoding): string {
|
|
281
|
+
const length = this.i32()
|
|
282
|
+
const slice = this.bytes.subarray(this.at, this.at + length)
|
|
283
|
+
this.at += length
|
|
284
|
+
if (encoding === 1) return new TextDecoder("utf-8").decode(slice)
|
|
285
|
+
return new TextDecoder("utf-16le").decode(slice)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
class Sink {
|
|
290
|
+
private chunks: Uint8Array[] = []
|
|
291
|
+
private buf = new Uint8Array(65536)
|
|
292
|
+
private view = new DataView(this.buf.buffer)
|
|
293
|
+
private at = 0
|
|
294
|
+
private need(n: number) {
|
|
295
|
+
if (this.at + n <= this.buf.length) return
|
|
296
|
+
this.chunks.push(this.buf.subarray(0, this.at))
|
|
297
|
+
this.buf = new Uint8Array(Math.max(65536, n))
|
|
298
|
+
this.view = new DataView(this.buf.buffer)
|
|
299
|
+
this.at = 0
|
|
300
|
+
}
|
|
301
|
+
u8(v: number) {
|
|
302
|
+
this.need(1)
|
|
303
|
+
this.view.setUint8(this.at++, v)
|
|
304
|
+
}
|
|
305
|
+
u16(v: number) {
|
|
306
|
+
this.need(2)
|
|
307
|
+
this.view.setUint16(this.at, v, true)
|
|
308
|
+
this.at += 2
|
|
309
|
+
}
|
|
310
|
+
i32(v: number) {
|
|
311
|
+
this.need(4)
|
|
312
|
+
this.view.setInt32(this.at, v, true)
|
|
313
|
+
this.at += 4
|
|
314
|
+
}
|
|
315
|
+
f32(v: number) {
|
|
316
|
+
this.need(4)
|
|
317
|
+
this.view.setFloat32(this.at, v, true)
|
|
318
|
+
this.at += 4
|
|
319
|
+
}
|
|
320
|
+
vec(v: readonly number[]) {
|
|
321
|
+
for (const n of v) this.f32(n)
|
|
322
|
+
}
|
|
323
|
+
uindex(size: number, v: number) {
|
|
324
|
+
if (size === 1) this.u8(v)
|
|
325
|
+
else if (size === 2) this.u16(v)
|
|
326
|
+
else this.i32(v | 0)
|
|
327
|
+
}
|
|
328
|
+
index(size: number, v: number) {
|
|
329
|
+
if (size === 1) this.u8(v & 0xff)
|
|
330
|
+
else if (size === 2) this.u16(v & 0xffff)
|
|
331
|
+
else this.i32(v)
|
|
332
|
+
}
|
|
333
|
+
raw(v: Uint8Array) {
|
|
334
|
+
this.need(v.length)
|
|
335
|
+
this.buf.set(v, this.at)
|
|
336
|
+
this.at += v.length
|
|
337
|
+
}
|
|
338
|
+
text(encoding: PmxEncoding, v: string) {
|
|
339
|
+
const bytes = encoding === 1 ? new TextEncoder().encode(v) : utf16le(v)
|
|
340
|
+
this.i32(bytes.length)
|
|
341
|
+
this.raw(bytes)
|
|
342
|
+
}
|
|
343
|
+
finish(): ArrayBuffer {
|
|
344
|
+
this.chunks.push(this.buf.subarray(0, this.at))
|
|
345
|
+
let total = 0
|
|
346
|
+
for (const c of this.chunks) total += c.length
|
|
347
|
+
const out = new Uint8Array(total)
|
|
348
|
+
let at = 0
|
|
349
|
+
for (const c of this.chunks) {
|
|
350
|
+
out.set(c, at)
|
|
351
|
+
at += c.length
|
|
352
|
+
}
|
|
353
|
+
return out.buffer
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** TextEncoder has no utf-16le, so the surrogate pairs are written by hand. */
|
|
358
|
+
function utf16le(s: string): Uint8Array {
|
|
359
|
+
const out = new Uint8Array(s.length * 2)
|
|
360
|
+
const view = new DataView(out.buffer)
|
|
361
|
+
for (let i = 0; i < s.length; i++) view.setUint16(i * 2, s.charCodeAt(i), true)
|
|
362
|
+
return out
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ──────────────────────────────────────────────────────────────────
|
|
366
|
+
// Read and write, one section at a time, each pair adjacent
|
|
367
|
+
|
|
368
|
+
/** Parses a PMX file into a document that writes back byte for byte. */
|
|
369
|
+
export function readPmxDocument(buffer: ArrayBuffer): PmxDocument {
|
|
370
|
+
const c = new Cursor(buffer)
|
|
371
|
+
|
|
372
|
+
// Signature. "Pmx " appears in the wild alongside "PMX ", and PMD is the older
|
|
373
|
+
// format entirely — say which it was rather than "not a PMX file".
|
|
374
|
+
const sig = String.fromCharCode(c.u8(), c.u8(), c.u8(), c.u8())
|
|
375
|
+
if (sig.slice(0, 3).toUpperCase() !== "PMX") {
|
|
376
|
+
if (sig.slice(0, 3).toUpperCase() === "PMD") {
|
|
377
|
+
throw new Error("This is a PMD file (MMD's older format). Convert it to PMX in PMXEditor first.")
|
|
378
|
+
}
|
|
379
|
+
throw new Error(`Not a PMX file (signature "${sig.replace(/[^\x20-\x7e]/g, "?")}")`)
|
|
380
|
+
}
|
|
381
|
+
const version = c.f32()
|
|
382
|
+
const globalsCount = c.u8()
|
|
383
|
+
if (globalsCount < 8) throw new Error(`PMX header declares ${globalsCount} globals, expected at least 8`)
|
|
384
|
+
const globals: PmxGlobals = {
|
|
385
|
+
encoding: c.u8() as PmxEncoding,
|
|
386
|
+
additionalUvCount: c.u8(),
|
|
387
|
+
vertexIndexSize: c.u8() as 1 | 2 | 4,
|
|
388
|
+
textureIndexSize: c.u8() as 1 | 2 | 4,
|
|
389
|
+
materialIndexSize: c.u8() as 1 | 2 | 4,
|
|
390
|
+
boneIndexSize: c.u8() as 1 | 2 | 4,
|
|
391
|
+
morphIndexSize: c.u8() as 1 | 2 | 4,
|
|
392
|
+
rigidbodyIndexSize: c.u8() as 1 | 2 | 4,
|
|
393
|
+
extra: [],
|
|
394
|
+
}
|
|
395
|
+
for (let i = 8; i < globalsCount; i++) globals.extra.push(c.u8())
|
|
396
|
+
const e = globals.encoding
|
|
397
|
+
|
|
398
|
+
const doc: PmxDocument = {
|
|
399
|
+
version,
|
|
400
|
+
globals,
|
|
401
|
+
name: c.text(e),
|
|
402
|
+
nameEn: c.text(e),
|
|
403
|
+
comment: c.text(e),
|
|
404
|
+
commentEn: c.text(e),
|
|
405
|
+
vertices: [],
|
|
406
|
+
indices: new Uint32Array(0),
|
|
407
|
+
textures: [],
|
|
408
|
+
materials: [],
|
|
409
|
+
bones: [],
|
|
410
|
+
morphs: [],
|
|
411
|
+
displayFrames: [],
|
|
412
|
+
rigidbodies: [],
|
|
413
|
+
joints: [],
|
|
414
|
+
trailing: null,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ── Vertices ──
|
|
418
|
+
const vertexCount = c.i32()
|
|
419
|
+
doc.vertices.length = vertexCount
|
|
420
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
421
|
+
const v: PmxVertex = {
|
|
422
|
+
position: c.vec3(),
|
|
423
|
+
normal: c.vec3(),
|
|
424
|
+
uv: c.vec2(),
|
|
425
|
+
additionalUv: [],
|
|
426
|
+
weightType: 0,
|
|
427
|
+
bones: [],
|
|
428
|
+
weights: [],
|
|
429
|
+
edgeScale: 0,
|
|
430
|
+
}
|
|
431
|
+
for (let k = 0; k < globals.additionalUvCount; k++) v.additionalUv.push(c.vec4())
|
|
432
|
+
v.weightType = c.u8() as PmxWeightType
|
|
433
|
+
const bi = globals.boneIndexSize
|
|
434
|
+
switch (v.weightType) {
|
|
435
|
+
case 0:
|
|
436
|
+
v.bones = [c.index(bi)]
|
|
437
|
+
break
|
|
438
|
+
case 1:
|
|
439
|
+
v.bones = [c.index(bi), c.index(bi)]
|
|
440
|
+
v.weights = [c.f32()]
|
|
441
|
+
break
|
|
442
|
+
case 2:
|
|
443
|
+
case 4:
|
|
444
|
+
v.bones = [c.index(bi), c.index(bi), c.index(bi), c.index(bi)]
|
|
445
|
+
v.weights = [c.f32(), c.f32(), c.f32(), c.f32()]
|
|
446
|
+
break
|
|
447
|
+
case 3:
|
|
448
|
+
v.bones = [c.index(bi), c.index(bi)]
|
|
449
|
+
v.weights = [c.f32()]
|
|
450
|
+
v.sdef = { c: c.vec3(), r0: c.vec3(), r1: c.vec3() }
|
|
451
|
+
break
|
|
452
|
+
}
|
|
453
|
+
v.edgeScale = c.f32()
|
|
454
|
+
doc.vertices[i] = v
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ── Faces ──
|
|
458
|
+
const indexCount = c.i32()
|
|
459
|
+
const indices = new Uint32Array(indexCount)
|
|
460
|
+
for (let i = 0; i < indexCount; i++) indices[i] = c.uindex(globals.vertexIndexSize)
|
|
461
|
+
doc.indices = indices
|
|
462
|
+
|
|
463
|
+
// ── Textures ──
|
|
464
|
+
const textureCount = c.i32()
|
|
465
|
+
for (let i = 0; i < textureCount; i++) doc.textures.push(c.text(e))
|
|
466
|
+
|
|
467
|
+
// ── Materials ──
|
|
468
|
+
const materialCount = c.i32()
|
|
469
|
+
for (let i = 0; i < materialCount; i++) {
|
|
470
|
+
const m: PmxMaterial = {
|
|
471
|
+
name: c.text(e),
|
|
472
|
+
nameEn: c.text(e),
|
|
473
|
+
diffuse: c.vec4(),
|
|
474
|
+
specular: c.vec3(),
|
|
475
|
+
specularPower: c.f32(),
|
|
476
|
+
ambient: c.vec3(),
|
|
477
|
+
drawFlags: c.u8(),
|
|
478
|
+
edgeColor: c.vec4(),
|
|
479
|
+
edgeSize: c.f32(),
|
|
480
|
+
textureIndex: c.index(globals.textureIndexSize),
|
|
481
|
+
sphereIndex: c.index(globals.textureIndexSize),
|
|
482
|
+
sphereMode: c.u8(),
|
|
483
|
+
toonShared: c.u8(),
|
|
484
|
+
toonIndex: 0,
|
|
485
|
+
memo: "",
|
|
486
|
+
indexCount: 0,
|
|
487
|
+
}
|
|
488
|
+
m.toonIndex = m.toonShared === 1 ? c.u8() : c.index(globals.textureIndexSize)
|
|
489
|
+
m.memo = c.text(e)
|
|
490
|
+
m.indexCount = c.i32()
|
|
491
|
+
doc.materials.push(m)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ── Bones ──
|
|
495
|
+
const boneCount = c.i32()
|
|
496
|
+
const bi = globals.boneIndexSize
|
|
497
|
+
for (let i = 0; i < boneCount; i++) {
|
|
498
|
+
const b: PmxBone = {
|
|
499
|
+
name: c.text(e),
|
|
500
|
+
nameEn: c.text(e),
|
|
501
|
+
position: c.vec3(),
|
|
502
|
+
parentIndex: c.index(bi),
|
|
503
|
+
layer: c.i32(),
|
|
504
|
+
flags: c.u16(),
|
|
505
|
+
}
|
|
506
|
+
if (b.flags & 0x0001) b.tailBoneIndex = c.index(bi)
|
|
507
|
+
else b.tailPosition = c.vec3()
|
|
508
|
+
if (b.flags & 0x0300) {
|
|
509
|
+
b.appendParentIndex = c.index(bi)
|
|
510
|
+
b.appendRatio = c.f32()
|
|
511
|
+
}
|
|
512
|
+
if (b.flags & 0x0400) b.fixedAxis = c.vec3()
|
|
513
|
+
if (b.flags & 0x0800) {
|
|
514
|
+
b.localAxisX = c.vec3()
|
|
515
|
+
b.localAxisZ = c.vec3()
|
|
516
|
+
}
|
|
517
|
+
if (b.flags & 0x2000) b.externalKey = c.i32()
|
|
518
|
+
if (b.flags & 0x0020) {
|
|
519
|
+
const targetIndex = c.index(bi)
|
|
520
|
+
const loopCount = c.i32()
|
|
521
|
+
const limitAngle = c.f32()
|
|
522
|
+
const linkCount = c.i32()
|
|
523
|
+
const links: PmxIkLink[] = []
|
|
524
|
+
for (let k = 0; k < linkCount; k++) {
|
|
525
|
+
const boneIndex = c.index(bi)
|
|
526
|
+
const hasLimit = c.u8() === 1
|
|
527
|
+
links.push(hasLimit ? { boneIndex, hasLimit, limitMin: c.vec3(), limitMax: c.vec3() } : { boneIndex, hasLimit })
|
|
528
|
+
}
|
|
529
|
+
b.ik = { targetIndex, loopCount, limitAngle, links }
|
|
530
|
+
}
|
|
531
|
+
doc.bones.push(b)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ── Morphs ──
|
|
535
|
+
const morphCount = c.i32()
|
|
536
|
+
for (let i = 0; i < morphCount; i++) {
|
|
537
|
+
const name = c.text(e)
|
|
538
|
+
const nameEn = c.text(e)
|
|
539
|
+
const panel = c.u8()
|
|
540
|
+
const type = c.u8()
|
|
541
|
+
const offsetCount = c.i32()
|
|
542
|
+
const offsets: PmxMorphOffset[] = []
|
|
543
|
+
for (let k = 0; k < offsetCount; k++) {
|
|
544
|
+
switch (type) {
|
|
545
|
+
case 0:
|
|
546
|
+
offsets.push({ kind: "group", index: c.index(globals.morphIndexSize), influence: c.f32() })
|
|
547
|
+
break
|
|
548
|
+
case 1:
|
|
549
|
+
offsets.push({ kind: "vertex", index: c.uindex(globals.vertexIndexSize), offset: c.vec3() })
|
|
550
|
+
break
|
|
551
|
+
case 2:
|
|
552
|
+
offsets.push({ kind: "bone", index: c.index(bi), translation: c.vec3(), rotation: c.vec4() })
|
|
553
|
+
break
|
|
554
|
+
case 3:
|
|
555
|
+
case 4:
|
|
556
|
+
case 5:
|
|
557
|
+
case 6:
|
|
558
|
+
case 7:
|
|
559
|
+
offsets.push({ kind: "uv", index: c.uindex(globals.vertexIndexSize), offset: c.vec4() })
|
|
560
|
+
break
|
|
561
|
+
case 8:
|
|
562
|
+
offsets.push({
|
|
563
|
+
kind: "material",
|
|
564
|
+
index: c.index(globals.materialIndexSize),
|
|
565
|
+
operation: c.u8(),
|
|
566
|
+
diffuse: c.vec4(),
|
|
567
|
+
specular: c.vec3(),
|
|
568
|
+
specularPower: c.f32(),
|
|
569
|
+
ambient: c.vec3(),
|
|
570
|
+
edgeColor: c.vec4(),
|
|
571
|
+
edgeSize: c.f32(),
|
|
572
|
+
textureTint: c.vec4(),
|
|
573
|
+
sphereTint: c.vec4(),
|
|
574
|
+
toonTint: c.vec4(),
|
|
575
|
+
})
|
|
576
|
+
break
|
|
577
|
+
case 9:
|
|
578
|
+
offsets.push({ kind: "flip", index: c.index(globals.morphIndexSize), influence: c.f32() })
|
|
579
|
+
break
|
|
580
|
+
case 10:
|
|
581
|
+
offsets.push({
|
|
582
|
+
kind: "impulse",
|
|
583
|
+
index: c.index(globals.rigidbodyIndexSize),
|
|
584
|
+
local: c.u8(),
|
|
585
|
+
velocity: c.vec3(),
|
|
586
|
+
torque: c.vec3(),
|
|
587
|
+
})
|
|
588
|
+
break
|
|
589
|
+
default:
|
|
590
|
+
throw new Error(`Morph "${name}" has unknown type ${type}`)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
doc.morphs.push({ name, nameEn, panel, type, offsets })
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ── Display frames ──
|
|
597
|
+
const frameCount = c.i32()
|
|
598
|
+
for (let i = 0; i < frameCount; i++) {
|
|
599
|
+
const name = c.text(e)
|
|
600
|
+
const nameEn = c.text(e)
|
|
601
|
+
const special = c.u8()
|
|
602
|
+
const elementCount = c.i32()
|
|
603
|
+
const elements: { type: number; index: number }[] = []
|
|
604
|
+
for (let k = 0; k < elementCount; k++) {
|
|
605
|
+
const type = c.u8()
|
|
606
|
+
elements.push({ type, index: c.index(type === 1 ? globals.morphIndexSize : bi) })
|
|
607
|
+
}
|
|
608
|
+
doc.displayFrames.push({ name, nameEn, special, elements })
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// ── Rigidbodies ──
|
|
612
|
+
const bodyCount = c.i32()
|
|
613
|
+
for (let i = 0; i < bodyCount; i++) {
|
|
614
|
+
doc.rigidbodies.push({
|
|
615
|
+
name: c.text(e),
|
|
616
|
+
nameEn: c.text(e),
|
|
617
|
+
boneIndex: c.index(bi),
|
|
618
|
+
group: c.u8(),
|
|
619
|
+
nonCollideMask: c.u16(),
|
|
620
|
+
shape: c.u8(),
|
|
621
|
+
size: c.vec3(),
|
|
622
|
+
position: c.vec3(),
|
|
623
|
+
rotation: c.vec3(),
|
|
624
|
+
mass: c.f32(),
|
|
625
|
+
linearDamping: c.f32(),
|
|
626
|
+
angularDamping: c.f32(),
|
|
627
|
+
restitution: c.f32(),
|
|
628
|
+
friction: c.f32(),
|
|
629
|
+
physicsMode: c.u8(),
|
|
630
|
+
})
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ── Joints ──
|
|
634
|
+
const jointCount = c.i32()
|
|
635
|
+
const ri = globals.rigidbodyIndexSize
|
|
636
|
+
for (let i = 0; i < jointCount; i++) {
|
|
637
|
+
doc.joints.push({
|
|
638
|
+
name: c.text(e),
|
|
639
|
+
nameEn: c.text(e),
|
|
640
|
+
type: c.u8(),
|
|
641
|
+
rigidbodyA: c.index(ri),
|
|
642
|
+
rigidbodyB: c.index(ri),
|
|
643
|
+
position: c.vec3(),
|
|
644
|
+
rotation: c.vec3(),
|
|
645
|
+
positionMin: c.vec3(),
|
|
646
|
+
positionMax: c.vec3(),
|
|
647
|
+
rotationMin: c.vec3(),
|
|
648
|
+
rotationMax: c.vec3(),
|
|
649
|
+
springPosition: c.vec3(),
|
|
650
|
+
springRotation: c.vec3(),
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (c.at < c.bytes.length) doc.trailing = c.bytes.slice(c.at)
|
|
655
|
+
return doc
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Writes a document back to PMX bytes. */
|
|
659
|
+
export function writePmxDocument(doc: PmxDocument): ArrayBuffer {
|
|
660
|
+
const w = new Sink()
|
|
661
|
+
const g = doc.globals
|
|
662
|
+
const e = g.encoding
|
|
663
|
+
const bi = g.boneIndexSize
|
|
664
|
+
|
|
665
|
+
for (const ch of "PMX ") w.u8(ch.charCodeAt(0))
|
|
666
|
+
w.f32(doc.version)
|
|
667
|
+
w.u8(8 + g.extra.length)
|
|
668
|
+
w.u8(g.encoding)
|
|
669
|
+
w.u8(g.additionalUvCount)
|
|
670
|
+
w.u8(g.vertexIndexSize)
|
|
671
|
+
w.u8(g.textureIndexSize)
|
|
672
|
+
w.u8(g.materialIndexSize)
|
|
673
|
+
w.u8(g.boneIndexSize)
|
|
674
|
+
w.u8(g.morphIndexSize)
|
|
675
|
+
w.u8(g.rigidbodyIndexSize)
|
|
676
|
+
for (const x of g.extra) w.u8(x)
|
|
677
|
+
w.text(e, doc.name)
|
|
678
|
+
w.text(e, doc.nameEn)
|
|
679
|
+
w.text(e, doc.comment)
|
|
680
|
+
w.text(e, doc.commentEn)
|
|
681
|
+
|
|
682
|
+
w.i32(doc.vertices.length)
|
|
683
|
+
for (const v of doc.vertices) {
|
|
684
|
+
w.vec(v.position)
|
|
685
|
+
w.vec(v.normal)
|
|
686
|
+
w.vec(v.uv)
|
|
687
|
+
for (let k = 0; k < g.additionalUvCount; k++) w.vec(v.additionalUv[k] ?? [0, 0, 0, 0])
|
|
688
|
+
w.u8(v.weightType)
|
|
689
|
+
switch (v.weightType) {
|
|
690
|
+
case 0:
|
|
691
|
+
w.index(bi, v.bones[0])
|
|
692
|
+
break
|
|
693
|
+
case 1:
|
|
694
|
+
w.index(bi, v.bones[0])
|
|
695
|
+
w.index(bi, v.bones[1])
|
|
696
|
+
w.f32(v.weights[0])
|
|
697
|
+
break
|
|
698
|
+
case 2:
|
|
699
|
+
case 4:
|
|
700
|
+
for (let k = 0; k < 4; k++) w.index(bi, v.bones[k])
|
|
701
|
+
for (let k = 0; k < 4; k++) w.f32(v.weights[k])
|
|
702
|
+
break
|
|
703
|
+
case 3:
|
|
704
|
+
w.index(bi, v.bones[0])
|
|
705
|
+
w.index(bi, v.bones[1])
|
|
706
|
+
w.f32(v.weights[0])
|
|
707
|
+
w.vec(v.sdef!.c)
|
|
708
|
+
w.vec(v.sdef!.r0)
|
|
709
|
+
w.vec(v.sdef!.r1)
|
|
710
|
+
break
|
|
711
|
+
}
|
|
712
|
+
w.f32(v.edgeScale)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
w.i32(doc.indices.length)
|
|
716
|
+
for (let i = 0; i < doc.indices.length; i++) w.uindex(g.vertexIndexSize, doc.indices[i])
|
|
717
|
+
|
|
718
|
+
w.i32(doc.textures.length)
|
|
719
|
+
for (const t of doc.textures) w.text(e, t)
|
|
720
|
+
|
|
721
|
+
w.i32(doc.materials.length)
|
|
722
|
+
for (const m of doc.materials) {
|
|
723
|
+
w.text(e, m.name)
|
|
724
|
+
w.text(e, m.nameEn)
|
|
725
|
+
w.vec(m.diffuse)
|
|
726
|
+
w.vec(m.specular)
|
|
727
|
+
w.f32(m.specularPower)
|
|
728
|
+
w.vec(m.ambient)
|
|
729
|
+
w.u8(m.drawFlags)
|
|
730
|
+
w.vec(m.edgeColor)
|
|
731
|
+
w.f32(m.edgeSize)
|
|
732
|
+
w.index(g.textureIndexSize, m.textureIndex)
|
|
733
|
+
w.index(g.textureIndexSize, m.sphereIndex)
|
|
734
|
+
w.u8(m.sphereMode)
|
|
735
|
+
w.u8(m.toonShared)
|
|
736
|
+
if (m.toonShared === 1) w.u8(m.toonIndex)
|
|
737
|
+
else w.index(g.textureIndexSize, m.toonIndex)
|
|
738
|
+
w.text(e, m.memo)
|
|
739
|
+
w.i32(m.indexCount)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
w.i32(doc.bones.length)
|
|
743
|
+
for (const b of doc.bones) {
|
|
744
|
+
w.text(e, b.name)
|
|
745
|
+
w.text(e, b.nameEn)
|
|
746
|
+
w.vec(b.position)
|
|
747
|
+
w.index(bi, b.parentIndex)
|
|
748
|
+
w.i32(b.layer)
|
|
749
|
+
w.u16(b.flags)
|
|
750
|
+
if (b.flags & 0x0001) w.index(bi, b.tailBoneIndex!)
|
|
751
|
+
else w.vec(b.tailPosition!)
|
|
752
|
+
if (b.flags & 0x0300) {
|
|
753
|
+
w.index(bi, b.appendParentIndex!)
|
|
754
|
+
w.f32(b.appendRatio!)
|
|
755
|
+
}
|
|
756
|
+
if (b.flags & 0x0400) w.vec(b.fixedAxis!)
|
|
757
|
+
if (b.flags & 0x0800) {
|
|
758
|
+
w.vec(b.localAxisX!)
|
|
759
|
+
w.vec(b.localAxisZ!)
|
|
760
|
+
}
|
|
761
|
+
if (b.flags & 0x2000) w.i32(b.externalKey!)
|
|
762
|
+
if (b.flags & 0x0020) {
|
|
763
|
+
const ik = b.ik!
|
|
764
|
+
w.index(bi, ik.targetIndex)
|
|
765
|
+
w.i32(ik.loopCount)
|
|
766
|
+
w.f32(ik.limitAngle)
|
|
767
|
+
w.i32(ik.links.length)
|
|
768
|
+
for (const l of ik.links) {
|
|
769
|
+
w.index(bi, l.boneIndex)
|
|
770
|
+
w.u8(l.hasLimit ? 1 : 0)
|
|
771
|
+
if (l.hasLimit) {
|
|
772
|
+
w.vec(l.limitMin!)
|
|
773
|
+
w.vec(l.limitMax!)
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
w.i32(doc.morphs.length)
|
|
780
|
+
for (const m of doc.morphs) {
|
|
781
|
+
w.text(e, m.name)
|
|
782
|
+
w.text(e, m.nameEn)
|
|
783
|
+
w.u8(m.panel)
|
|
784
|
+
w.u8(m.type)
|
|
785
|
+
w.i32(m.offsets.length)
|
|
786
|
+
for (const o of m.offsets) {
|
|
787
|
+
switch (o.kind) {
|
|
788
|
+
case "group":
|
|
789
|
+
w.index(g.morphIndexSize, o.index)
|
|
790
|
+
w.f32(o.influence)
|
|
791
|
+
break
|
|
792
|
+
case "vertex":
|
|
793
|
+
w.uindex(g.vertexIndexSize, o.index)
|
|
794
|
+
w.vec(o.offset)
|
|
795
|
+
break
|
|
796
|
+
case "bone":
|
|
797
|
+
w.index(bi, o.index)
|
|
798
|
+
w.vec(o.translation)
|
|
799
|
+
w.vec(o.rotation)
|
|
800
|
+
break
|
|
801
|
+
case "uv":
|
|
802
|
+
w.uindex(g.vertexIndexSize, o.index)
|
|
803
|
+
w.vec(o.offset)
|
|
804
|
+
break
|
|
805
|
+
case "material":
|
|
806
|
+
w.index(g.materialIndexSize, o.index)
|
|
807
|
+
w.u8(o.operation)
|
|
808
|
+
w.vec(o.diffuse)
|
|
809
|
+
w.vec(o.specular)
|
|
810
|
+
w.f32(o.specularPower)
|
|
811
|
+
w.vec(o.ambient)
|
|
812
|
+
w.vec(o.edgeColor)
|
|
813
|
+
w.f32(o.edgeSize)
|
|
814
|
+
w.vec(o.textureTint)
|
|
815
|
+
w.vec(o.sphereTint)
|
|
816
|
+
w.vec(o.toonTint)
|
|
817
|
+
break
|
|
818
|
+
case "flip":
|
|
819
|
+
w.index(g.morphIndexSize, o.index)
|
|
820
|
+
w.f32(o.influence)
|
|
821
|
+
break
|
|
822
|
+
case "impulse":
|
|
823
|
+
w.index(g.rigidbodyIndexSize, o.index)
|
|
824
|
+
w.u8(o.local)
|
|
825
|
+
w.vec(o.velocity)
|
|
826
|
+
w.vec(o.torque)
|
|
827
|
+
break
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
w.i32(doc.displayFrames.length)
|
|
833
|
+
for (const f of doc.displayFrames) {
|
|
834
|
+
w.text(e, f.name)
|
|
835
|
+
w.text(e, f.nameEn)
|
|
836
|
+
w.u8(f.special)
|
|
837
|
+
w.i32(f.elements.length)
|
|
838
|
+
for (const el of f.elements) {
|
|
839
|
+
w.u8(el.type)
|
|
840
|
+
w.index(el.type === 1 ? g.morphIndexSize : bi, el.index)
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
w.i32(doc.rigidbodies.length)
|
|
845
|
+
for (const b of doc.rigidbodies) {
|
|
846
|
+
w.text(e, b.name)
|
|
847
|
+
w.text(e, b.nameEn)
|
|
848
|
+
w.index(bi, b.boneIndex)
|
|
849
|
+
w.u8(b.group)
|
|
850
|
+
w.u16(b.nonCollideMask)
|
|
851
|
+
w.u8(b.shape)
|
|
852
|
+
w.vec(b.size)
|
|
853
|
+
w.vec(b.position)
|
|
854
|
+
w.vec(b.rotation)
|
|
855
|
+
w.f32(b.mass)
|
|
856
|
+
w.f32(b.linearDamping)
|
|
857
|
+
w.f32(b.angularDamping)
|
|
858
|
+
w.f32(b.restitution)
|
|
859
|
+
w.f32(b.friction)
|
|
860
|
+
w.u8(b.physicsMode)
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
w.i32(doc.joints.length)
|
|
864
|
+
for (const j of doc.joints) {
|
|
865
|
+
w.text(e, j.name)
|
|
866
|
+
w.text(e, j.nameEn)
|
|
867
|
+
w.u8(j.type)
|
|
868
|
+
w.index(g.rigidbodyIndexSize, j.rigidbodyA)
|
|
869
|
+
w.index(g.rigidbodyIndexSize, j.rigidbodyB)
|
|
870
|
+
w.vec(j.position)
|
|
871
|
+
w.vec(j.rotation)
|
|
872
|
+
w.vec(j.positionMin)
|
|
873
|
+
w.vec(j.positionMax)
|
|
874
|
+
w.vec(j.rotationMin)
|
|
875
|
+
w.vec(j.rotationMax)
|
|
876
|
+
w.vec(j.springPosition)
|
|
877
|
+
w.vec(j.springRotation)
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (doc.trailing) w.raw(doc.trailing)
|
|
881
|
+
return w.finish()
|
|
882
|
+
}
|