@webxr-jp/avatar-optimizer 0.1.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/index.js ADDED
@@ -0,0 +1,2293 @@
1
+ import { MToonMaterial, VRMLoaderPlugin } from '@pixiv/three-vrm';
2
+ import { ResultAsync, safeTry, err, ok, okAsync, fromSafePromise, Result } from 'neverthrow';
3
+ import { Mesh, SkinnedMesh, Bone, Vector3, Matrix4, Quaternion, BufferAttribute, Scene, BufferGeometry, WebGLRenderer, Color, OrthographicCamera, WebGLRenderTarget, DataTexture, RGBAFormat, UnsignedByteType, LinearFilter, RepeatWrapping, SRGBColorSpace, MeshBasicMaterial, InterleavedBufferAttribute, LinearSRGBColorSpace, NoColorSpace, PlaneGeometry, NoBlending, DoubleSide, Vector2, FloatType, NearestFilter, Skeleton, Float32BufferAttribute, Vector4 } from 'three';
4
+ import { Packer, MaxRectsBssf } from 'rectpack-ts';
5
+ import { SORT_AREA } from 'rectpack-ts/dist/src/sorting.js';
6
+ import { MeshoptSimplifier } from 'meshoptimizer';
7
+ import { MToonAtlasExporterPlugin, MToonAtlasLoaderPlugin, MToonAtlasMaterial } from '@webxr-jp/mtoon-atlas';
8
+ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
9
+ import { initBasisEncoder } from '@webxr-jp/texture-compression';
10
+ export { UastcQuality } from '@webxr-jp/texture-compression';
11
+ import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
12
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
13
+ import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
14
+
15
+ // src/avatar-optimizer.ts
16
+ var MTOON_TEXTURE_SLOTS = [
17
+ "map",
18
+ "normalMap",
19
+ "emissiveMap",
20
+ "shadeMultiplyTexture",
21
+ "shadingShiftTexture",
22
+ "matcapTexture",
23
+ "rimMultiplyTexture",
24
+ "outlineWidthMultiplyTexture",
25
+ "uvAnimationMaskTexture"
26
+ ];
27
+ var MTOON_TEXTURE_SLOT_COLOR_SPACES = {
28
+ map: SRGBColorSpace,
29
+ normalMap: NoColorSpace,
30
+ emissiveMap: SRGBColorSpace,
31
+ shadeMultiplyTexture: SRGBColorSpace,
32
+ shadingShiftTexture: NoColorSpace,
33
+ matcapTexture: SRGBColorSpace,
34
+ rimMultiplyTexture: SRGBColorSpace,
35
+ outlineWidthMultiplyTexture: NoColorSpace,
36
+ uvAnimationMaskTexture: NoColorSpace
37
+ };
38
+ var PARAMETER_LAYOUT = [
39
+ { id: "baseColor", texel: 0, channels: ["r", "g", "b"] },
40
+ { id: "opacity", texel: 0, channels: ["a"] },
41
+ { id: "shadeColor", texel: 1, channels: ["r", "g", "b"] },
42
+ { id: "shadingShiftTextureScale", texel: 1, channels: ["a"] },
43
+ { id: "emissiveColor", texel: 2, channels: ["r", "g", "b"] },
44
+ { id: "emissiveIntensity", texel: 2, channels: ["a"] },
45
+ { id: "matcapColor", texel: 3, channels: ["r", "g", "b"] },
46
+ { id: "outlineWidth", texel: 3, channels: ["a"] },
47
+ { id: "outlineColor", texel: 4, channels: ["r", "g", "b"] },
48
+ { id: "outlineLightingMix", texel: 4, channels: ["a"] },
49
+ { id: "parametricRimColor", texel: 5, channels: ["r", "g", "b"] },
50
+ { id: "parametricRimLift", texel: 5, channels: ["a"] },
51
+ { id: "parametricRimFresnelPower", texel: 6, channels: ["r"] },
52
+ { id: "shadingToony", texel: 6, channels: ["g"] },
53
+ { id: "rimLightingMix", texel: 6, channels: ["b"] },
54
+ { id: "uvAnimationRotation", texel: 6, channels: ["a"] },
55
+ { id: "normalScale", texel: 7, channels: ["r", "g"] },
56
+ { id: "uvAnimationScrollX", texel: 7, channels: ["b"] },
57
+ { id: "uvAnimationScrollY", texel: 7, channels: ["a"] },
58
+ { id: "shadingShift", texel: 8, channels: ["r"] }
59
+ ];
60
+ function composeImagesToAtlas(layers, options) {
61
+ const { width, height, colorSpace = SRGBColorSpace } = options;
62
+ if (width <= 0 || height <= 0) {
63
+ return err({
64
+ type: "INVALID_PARAMETER",
65
+ message: "width, height\u306F\u6B63\u306E\u5024\u306B\u3057\u3066\u304F\u3060\u3055\u3044"
66
+ });
67
+ }
68
+ const renderer = createRenderer();
69
+ const scene = new Scene();
70
+ scene.background = new Color(0, 0, 0);
71
+ const camera = new OrthographicCamera(0, 1, 1, 0, 0.1, 1e3);
72
+ camera.position.z = 10;
73
+ const renderTarget = new WebGLRenderTarget(width, height);
74
+ for (const layer of layers) {
75
+ const mesh = createLayerMesh(layer);
76
+ scene.add(mesh);
77
+ }
78
+ renderer.setRenderTarget(renderTarget);
79
+ renderer.setClearColor(0, 0);
80
+ renderer.clear();
81
+ renderer.render(scene, camera);
82
+ const pixels = new Uint8Array(width * height * 4);
83
+ renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, pixels);
84
+ const tex = new DataTexture(
85
+ pixels,
86
+ width,
87
+ height,
88
+ RGBAFormat,
89
+ UnsignedByteType
90
+ );
91
+ tex.needsUpdate = true;
92
+ tex.magFilter = LinearFilter;
93
+ tex.minFilter = LinearFilter;
94
+ tex.wrapS = RepeatWrapping;
95
+ tex.wrapT = RepeatWrapping;
96
+ tex.colorSpace = colorSpace;
97
+ scene.traverse((obj) => {
98
+ if (obj instanceof Mesh) {
99
+ obj.geometry.dispose();
100
+ if (obj.material instanceof MeshBasicMaterial) {
101
+ obj.material.dispose();
102
+ }
103
+ }
104
+ });
105
+ renderer.dispose();
106
+ renderTarget.dispose();
107
+ layers.forEach((layer) => layer.image.dispose());
108
+ return ok(tex);
109
+ }
110
+ function createRenderer() {
111
+ const canvas = new OffscreenCanvas(1, 1);
112
+ const renderer = new WebGLRenderer({
113
+ canvas,
114
+ antialias: false,
115
+ alpha: true,
116
+ // premultipliedAlpha を無効化してアルファ値をそのまま保持
117
+ premultipliedAlpha: false
118
+ });
119
+ renderer.outputColorSpace = LinearSRGBColorSpace;
120
+ return renderer;
121
+ }
122
+ function createLayerMesh(layer) {
123
+ const texture = layer.image;
124
+ const uvTransform = layer.uvTransform;
125
+ texture.colorSpace = NoColorSpace;
126
+ const geometry = new PlaneGeometry();
127
+ const material = new MeshBasicMaterial({
128
+ map: texture,
129
+ side: DoubleSide,
130
+ blending: NoBlending
131
+ });
132
+ const mesh = new Mesh(geometry, material);
133
+ mesh.position.x = uvTransform.offset.x + uvTransform.scale.x * 0.5;
134
+ mesh.position.y = uvTransform.offset.y + uvTransform.scale.y * 0.5;
135
+ mesh.position.z = 0;
136
+ mesh.scale.x = uvTransform.scale.x;
137
+ mesh.scale.y = uvTransform.scale.y;
138
+ mesh.scale.z = 1;
139
+ return mesh;
140
+ }
141
+
142
+ // src/process/gen-atlas.ts
143
+ var DEFAULT_ATLAS_RESOLUTION = 2048;
144
+ function getSlotResolution(slot, options) {
145
+ const defaultRes = options?.defaultResolution ?? DEFAULT_ATLAS_RESOLUTION;
146
+ return options?.slotResolutions?.[slot] ?? defaultRes;
147
+ }
148
+ function generateAtlasImagesFromPatterns(materials, patternMappings, patternPlacements, options) {
149
+ return safeTry(function* () {
150
+ if (patternMappings.length !== patternPlacements.length) {
151
+ return err({
152
+ type: "INVALID_OPERATION",
153
+ message: "Pattern mappings and placements length mismatch"
154
+ });
155
+ }
156
+ const atlasMap = {};
157
+ for (const slot of MTOON_TEXTURE_SLOTS) {
158
+ const layers = [];
159
+ for (let i = 0; i < patternMappings.length; i++) {
160
+ const mapping = patternMappings[i];
161
+ const placement = patternPlacements[i];
162
+ const representativeMaterialIndex = mapping.materialIndices[0];
163
+ const material = materials[representativeMaterialIndex];
164
+ const texture = material[slot];
165
+ if (texture) {
166
+ layers.push({
167
+ image: texture,
168
+ uvTransform: placement
169
+ });
170
+ }
171
+ }
172
+ const resolution = getSlotResolution(slot, options);
173
+ const atlas = yield* composeImagesToAtlas(layers, {
174
+ width: resolution,
175
+ height: resolution,
176
+ colorSpace: MTOON_TEXTURE_SLOT_COLOR_SPACES[slot]
177
+ });
178
+ atlasMap[slot] = atlas;
179
+ }
180
+ return ok(atlasMap);
181
+ });
182
+ }
183
+ function fillNullTexturesWithAverageDimensions(textures) {
184
+ const validTextures = textures.filter(
185
+ (t) => t !== null
186
+ );
187
+ if (validTextures.length === 0) {
188
+ return textures.map((t) => t ?? { width: 512, height: 512 });
189
+ }
190
+ const avgWidth = validTextures.reduce((sum, t) => sum + t.width, 0) / validTextures.length;
191
+ const avgHeight = validTextures.reduce((sum, t) => sum + t.height, 0) / validTextures.length;
192
+ const roundedWidth = roundToNearestPowerOfTwo(avgWidth);
193
+ const roundedHeight = roundToNearestPowerOfTwo(avgHeight);
194
+ return textures.map(
195
+ (t) => t ?? { width: roundedWidth, height: roundedHeight }
196
+ );
197
+ }
198
+ function roundToNearestPowerOfTwo(value) {
199
+ if (value <= 0) return 512;
200
+ const lower = Math.pow(2, Math.floor(Math.log2(value)));
201
+ const upper = lower * 2;
202
+ return value - lower < upper - value ? lower : upper;
203
+ }
204
+ function createParameterTexture(materials, texelsPerSlot = 9) {
205
+ if (materials.length === 0) {
206
+ return err({
207
+ type: "PARAMETER_TEXTURE_FAILED",
208
+ message: "No materials to pack"
209
+ });
210
+ }
211
+ const slotCount = materials.length;
212
+ const width = texelsPerSlot;
213
+ const height = slotCount;
214
+ const data = new Float32Array(width * height * 4);
215
+ for (let slotIndex = 0; slotIndex < slotCount; slotIndex++) {
216
+ const material = materials[slotIndex];
217
+ for (const layout of PARAMETER_LAYOUT) {
218
+ const value = extractParameterValue(material, layout.id);
219
+ packParameterValue(data, slotIndex, layout, value, texelsPerSlot);
220
+ }
221
+ }
222
+ const texture = new DataTexture(data, width, height, RGBAFormat, FloatType);
223
+ texture.minFilter = NearestFilter;
224
+ texture.magFilter = NearestFilter;
225
+ texture.needsUpdate = true;
226
+ return ok(texture);
227
+ }
228
+ function packParameterValue(data, slotIndex, layout, value, texelsPerSlot) {
229
+ const texelIndex = layout.texel;
230
+ const pixelIndex = slotIndex * texelsPerSlot + texelIndex;
231
+ const baseOffset = pixelIndex * 4;
232
+ let values;
233
+ if (typeof value === "number") {
234
+ values = [value];
235
+ } else if ("w" in value) {
236
+ values = [value.x, value.y, value.z, value.w];
237
+ } else {
238
+ values = [value.x, value.y, value.z];
239
+ }
240
+ for (let i = 0; i < layout.channels.length; i++) {
241
+ const channel = layout.channels[i];
242
+ const channelOffset = channel === "r" ? 0 : channel === "g" ? 1 : channel === "b" ? 2 : 3;
243
+ data[baseOffset + channelOffset] = values[i] ?? 0;
244
+ }
245
+ }
246
+ function extractParameterValue(material, semanticId) {
247
+ switch (semanticId) {
248
+ case "baseColor":
249
+ return colorToVector3(material.color ?? new Color(1, 1, 1));
250
+ case "opacity":
251
+ return material.opacity ?? 1;
252
+ case "shadeColor":
253
+ return colorToVector3(material.shadeColorFactor ?? new Color(0, 0, 0));
254
+ case "emissiveColor":
255
+ return colorToVector3(material.emissive ?? new Color(0, 0, 0));
256
+ case "emissiveIntensity":
257
+ return material.emissiveIntensity ?? 0;
258
+ case "shadingShift":
259
+ return ((material.shadingShiftFactor ?? 0) + 1) / 2;
260
+ case "shadingShiftTextureScale":
261
+ return material.shadingShiftTextureScale ?? 1;
262
+ case "shadingToony":
263
+ return material.shadingToonyFactor ?? 0.9;
264
+ case "rimLightingMix":
265
+ return material.rimLightingMixFactor ?? 1;
266
+ case "matcapColor":
267
+ return colorToVector3(material.matcapFactor ?? new Color(1, 1, 1));
268
+ case "outlineWidth":
269
+ return material.outlineWidthFactor ?? 0;
270
+ case "outlineColor":
271
+ return colorToVector3(material.outlineColorFactor ?? new Color(0, 0, 0));
272
+ case "outlineLightingMix":
273
+ return material.outlineLightingMixFactor ?? 1;
274
+ case "parametricRimColor":
275
+ return colorToVector3(
276
+ material.parametricRimColorFactor ?? new Color(0, 0, 0)
277
+ );
278
+ case "parametricRimLift":
279
+ return material.parametricRimLiftFactor ?? 0;
280
+ case "parametricRimFresnelPower":
281
+ return material.parametricRimFresnelPowerFactor ?? 5;
282
+ case "uvAnimationScrollX":
283
+ return 0;
284
+ // TODO: MToonMaterialのプロパティ確認
285
+ case "uvAnimationScrollY":
286
+ return 0;
287
+ // TODO: MToonMaterialのプロパティ確認
288
+ case "uvAnimationRotation":
289
+ return 0;
290
+ // TODO: MToonMaterialのプロパティ確認
291
+ case "normalScale":
292
+ return new Vector4(1, 1, 0, 0);
293
+ // x, y のみ使用
294
+ default:
295
+ return 0;
296
+ }
297
+ }
298
+ function colorToVector3(color) {
299
+ return new Vector3(color.r, color.g, color.b);
300
+ }
301
+ var ATLAS_SIZE = 2048;
302
+ var SCALE_SEARCH_EPSILON = 1e-4;
303
+ function packTexturesWithMaxRects(sizes) {
304
+ if (sizes.length === 0) {
305
+ throw new Error("No textures to pack");
306
+ }
307
+ const packer = new Packer({
308
+ packAlgo: MaxRectsBssf,
309
+ sortAlgo: SORT_AREA,
310
+ rotation: true
311
+ });
312
+ packer.addBin(ATLAS_SIZE, ATLAS_SIZE);
313
+ sizes.forEach((size, index) => {
314
+ packer.addRect(size.width, size.height, String(index));
315
+ });
316
+ packer.pack();
317
+ const bins = packer.binList();
318
+ if (bins.length === 0) {
319
+ throw new Error("Packing failed: No bins were created");
320
+ }
321
+ const bin = bins[0];
322
+ const packed = bin.rectangles.map((rect) => ({
323
+ rid: rect.rid,
324
+ offset: new Vector2(rect.x / bin.width, rect.y / bin.height),
325
+ scale: new Vector2(rect.width / bin.width, rect.height / bin.height)
326
+ }));
327
+ packed.sort((a, b) => Number(a.rid) - Number(b.rid));
328
+ if (packed.length !== sizes.length) {
329
+ throw new Error(
330
+ `Packing failed: ${packed.length}/${sizes.length} textures packed. Textures do not fit in atlas.`
331
+ );
332
+ }
333
+ if (bins.length > 1) {
334
+ throw new Error(
335
+ `Packing failed: ${bins.length} bins required, but expected single bin. Textures do not fit in atlas.`
336
+ );
337
+ }
338
+ return {
339
+ packed
340
+ };
341
+ }
342
+ async function packTexturesWithAutoScaling(sizes) {
343
+ if (sizes.length === 0) {
344
+ throw new Error("No textures to pack");
345
+ }
346
+ const attemptPack = (scale) => {
347
+ const scaledSizes = scaleTextureSizes(sizes, scale);
348
+ return packTexturesWithMaxRects(scaledSizes);
349
+ };
350
+ try {
351
+ return attemptPack(1);
352
+ } catch {
353
+ }
354
+ const minScaleLimit = computeMinScaleLimit(sizes);
355
+ let lastFailedScale = 1;
356
+ let lastSuccessScale = null;
357
+ let lastSuccessResult = null;
358
+ let currentScale = 0.5;
359
+ for (let attempt = 0; attempt < 32; attempt++) {
360
+ if (currentScale < minScaleLimit) {
361
+ currentScale = minScaleLimit;
362
+ }
363
+ try {
364
+ const result = attemptPack(currentScale);
365
+ lastSuccessScale = currentScale;
366
+ lastSuccessResult = result;
367
+ break;
368
+ } catch {
369
+ lastFailedScale = currentScale;
370
+ if (currentScale <= minScaleLimit) {
371
+ throw new Error(
372
+ "Failed to pack textures using MaxRects algorithm. Could not fit textures even after scaling down to 1x1 pixels."
373
+ );
374
+ }
375
+ currentScale *= 0.5;
376
+ }
377
+ }
378
+ if (lastSuccessScale === null || lastSuccessResult === null) {
379
+ throw new Error(
380
+ "Failed to pack textures using MaxRects algorithm. Could not fit textures even after scaling down to 1x1 pixels."
381
+ );
382
+ }
383
+ if (lastFailedScale <= lastSuccessScale) {
384
+ return lastSuccessResult;
385
+ }
386
+ let low = lastSuccessScale;
387
+ let high = lastFailedScale;
388
+ for (let i = 0; i < 25; i++) {
389
+ if (Math.abs(high - low) < SCALE_SEARCH_EPSILON) {
390
+ break;
391
+ }
392
+ const mid = (low + high) / 2;
393
+ if (mid === low || mid === high) {
394
+ break;
395
+ }
396
+ try {
397
+ const result = attemptPack(mid);
398
+ lastSuccessScale = mid;
399
+ lastSuccessResult = result;
400
+ low = mid;
401
+ } catch {
402
+ high = mid;
403
+ }
404
+ }
405
+ if (lastSuccessResult === null) {
406
+ throw new Error(
407
+ "Failed to pack textures using MaxRects algorithm. Could not fit textures even after scaling down to 1x1 pixels."
408
+ );
409
+ }
410
+ return lastSuccessResult;
411
+ }
412
+ function scaleTextureSizes(originalSizes, scaleMultiplier) {
413
+ return originalSizes.map((size) => ({
414
+ width: Math.max(1, Math.floor(size.width * scaleMultiplier)),
415
+ height: Math.max(1, Math.floor(size.height * scaleMultiplier))
416
+ }));
417
+ }
418
+ function computeMinScaleLimit(sizes) {
419
+ if (sizes.length === 0) {
420
+ return 1;
421
+ }
422
+ const maxDimension = Math.max(
423
+ ...sizes.map((size) => Math.max(size.width, size.height))
424
+ );
425
+ if (maxDimension <= 0) {
426
+ return 1;
427
+ }
428
+ return 1 / maxDimension;
429
+ }
430
+ async function packTextures(sizes) {
431
+ return packTexturesWithAutoScaling(sizes);
432
+ }
433
+
434
+ // src/process/packing.ts
435
+ function pack(patternMappings) {
436
+ return fromSafePromise(
437
+ (async () => {
438
+ const textureDescriptors = patternMappings.map((m) => m.textureDescriptor);
439
+ const texturesToPack = fillNullTexturesWithAverageDimensions(
440
+ textureDescriptors.map((d) => d.width > 0 && d.height > 0 ? d : null)
441
+ );
442
+ return await packTextures(texturesToPack);
443
+ })()
444
+ );
445
+ }
446
+ function buildPatternMaterialMappings(materials) {
447
+ const mappings = [];
448
+ for (let i = 0; i < materials.length; i++) {
449
+ const material = materials[i];
450
+ const pattern = extractTexturePattern(material);
451
+ const existingMapping = mappings.find(
452
+ (m) => isSamePattern(m.pattern, pattern)
453
+ );
454
+ if (existingMapping) {
455
+ existingMapping.materialIndices.push(i);
456
+ } else {
457
+ const mapTexture = material.map;
458
+ const textureDescriptor = mapTexture && hasSize(mapTexture.image) ? {
459
+ width: mapTexture.image.width,
460
+ height: mapTexture.image.height
461
+ } : {
462
+ width: 0,
463
+ height: 0
464
+ };
465
+ mappings.push({
466
+ pattern,
467
+ materialIndices: [i],
468
+ textureDescriptor
469
+ });
470
+ }
471
+ }
472
+ return mappings;
473
+ }
474
+ function extractTexturePattern(material) {
475
+ const slots = /* @__PURE__ */ new Map();
476
+ for (const slot of MTOON_TEXTURE_SLOTS) {
477
+ const texture = material[slot];
478
+ slots.set(slot, texture?.image ?? null);
479
+ }
480
+ return { slots };
481
+ }
482
+ function isSamePattern(pattern1, pattern2) {
483
+ for (const slot of MTOON_TEXTURE_SLOTS) {
484
+ const img1 = pattern1.slots.get(slot) ?? null;
485
+ const img2 = pattern2.slots.get(slot) ?? null;
486
+ if (img1 !== img2) return false;
487
+ }
488
+ return true;
489
+ }
490
+ function hasSize(img) {
491
+ return img != null && typeof img.width === "number" && typeof img.height === "number";
492
+ }
493
+ function cloneBufferAttribute(attr) {
494
+ const count = attr.count;
495
+ const itemSize = attr.itemSize;
496
+ const normalized = attr.normalized;
497
+ const TypedArrayConstructor = attr.array.constructor;
498
+ const newArray = new TypedArrayConstructor(count * itemSize);
499
+ if (attr instanceof InterleavedBufferAttribute) {
500
+ for (let i = 0; i < count; i++) {
501
+ for (let j = 0; j < itemSize; j++) {
502
+ newArray[i * itemSize + j] = attr.getComponent(i, j);
503
+ }
504
+ }
505
+ } else {
506
+ const sourceArray = attr.array;
507
+ for (let i = 0; i < sourceArray.length; i++) {
508
+ newArray[i] = sourceArray[i];
509
+ }
510
+ }
511
+ return new BufferAttribute(newArray, itemSize, normalized);
512
+ }
513
+ function editBufferAttribute(attr, editor) {
514
+ const cloned = cloneBufferAttribute(attr);
515
+ editor(cloned.array);
516
+ cloned.needsUpdate = true;
517
+ return cloned;
518
+ }
519
+
520
+ // src/util/mesh/uv.ts
521
+ function wrapUV(uv) {
522
+ let x = uv.x;
523
+ let y = uv.y;
524
+ if (x < 0 || x > 1) x = x - Math.floor(x);
525
+ if (y < 0 || y > 1) y = y - Math.floor(y);
526
+ return new Vector2(x, y);
527
+ }
528
+ function applyUVTransform(uvArray, scaleU, scaleV, translateU, translateV, startIndex = 0, endIndex = uvArray.length) {
529
+ for (let i = startIndex; i < endIndex; i += 2) {
530
+ const oldU = uvArray[i];
531
+ const oldV = uvArray[i + 1];
532
+ const wrapped = wrapUV(new Vector2(oldU, oldV));
533
+ const oldUWrapped = wrapped.x;
534
+ const oldVWrapped = wrapped.y;
535
+ const newU = translateU + scaleU * oldUWrapped;
536
+ const newV = translateV + scaleV * oldVWrapped;
537
+ uvArray[i] = newU;
538
+ uvArray[i + 1] = newV;
539
+ }
540
+ }
541
+ function remapGeometryUVs(geometry, uvTransform) {
542
+ const uvAttribute = geometry.getAttribute("uv");
543
+ if (!uvAttribute) {
544
+ return err({
545
+ type: "ASSET_ERROR",
546
+ message: "UV\u30A2\u30C8\u30EA\u30D3\u30E5\u30FC\u30C8\u304C\u5B58\u5728\u3057\u307E\u305B\u3093"
547
+ });
548
+ }
549
+ if (!uvAttribute.array) {
550
+ return err({
551
+ type: "ASSET_ERROR",
552
+ message: "UV\u30A2\u30C8\u30EA\u30D3\u30E5\u30FC\u30C8\u914D\u5217\u304C\u5B58\u5728\u3057\u307E\u305B\u3093"
553
+ });
554
+ }
555
+ if (uvAttribute.itemSize !== 2) {
556
+ return err({
557
+ type: "ASSET_ERROR",
558
+ message: "UV\u30A2\u30C8\u30EA\u30D3\u30E5\u30FC\u30C8\u306E\u8981\u7D20\u6570\u304C2\u3067\u306F\u3042\u308A\u307E\u305B\u3093"
559
+ });
560
+ }
561
+ const newUvAttribute = editBufferAttribute(
562
+ uvAttribute,
563
+ (uvArray) => {
564
+ applyUVTransform(
565
+ uvArray,
566
+ uvTransform.scale.x,
567
+ uvTransform.scale.y,
568
+ uvTransform.offset.x,
569
+ uvTransform.offset.y
570
+ );
571
+ }
572
+ );
573
+ geometry.setAttribute("uv", newUvAttribute);
574
+ return ok();
575
+ }
576
+
577
+ // src/process/set-uv.ts
578
+ function applyPlacementsToGeometries(rootNode, materialPlacementMap) {
579
+ const targets = /* @__PURE__ */ new Map();
580
+ rootNode.traverse((obj) => {
581
+ if (!(obj instanceof Mesh)) return;
582
+ if (!(obj.geometry instanceof BufferGeometry)) return;
583
+ let material = null;
584
+ if (Array.isArray(obj.material)) {
585
+ material = obj.material[0];
586
+ } else if (obj.material instanceof MToonMaterial) {
587
+ material = obj.material;
588
+ }
589
+ if (!(material instanceof MToonMaterial)) {
590
+ if (obj.material?.isMToonMaterial || Array.isArray(obj.material) && obj.material[0]?.isMToonMaterial) {
591
+ console.warn(
592
+ "Found MToonMaterial-like object but instanceof failed. This indicates a dual package hazard.",
593
+ obj.material
594
+ );
595
+ }
596
+ return;
597
+ }
598
+ const placement = materialPlacementMap.get(material);
599
+ if (!placement) {
600
+ console.warn("No placement found for material", material);
601
+ return;
602
+ }
603
+ const uvAttr = obj.geometry.getAttribute("uv");
604
+ if (uvAttr) {
605
+ const newUvArray = new Float32Array(uvAttr.array.length);
606
+ newUvArray.set(uvAttr.array);
607
+ const newUvAttr = new BufferAttribute(
608
+ newUvArray,
609
+ uvAttr.itemSize,
610
+ uvAttr.normalized
611
+ );
612
+ obj.geometry.setAttribute("uv", newUvAttr);
613
+ }
614
+ targets.set(obj.geometry, placement);
615
+ });
616
+ const results = [...targets].map(
617
+ (target) => remapGeometryUVs(target[0], target[1])
618
+ );
619
+ return Result.combine(results);
620
+ }
621
+ var DEFAULT_SIMPLIFY_OPTIONS = {
622
+ targetRatio: 0.5,
623
+ targetError: 0.01,
624
+ lockBorder: true,
625
+ uvWeight: 1,
626
+ normalWeight: 0.5,
627
+ morphTargetHandling: "skip"
628
+ };
629
+ async function ensureSimplifierReady() {
630
+ await MeshoptSimplifier.ready;
631
+ }
632
+ function extractAttributeData(attr) {
633
+ const count = attr.count;
634
+ const itemSize = attr.itemSize;
635
+ const result = new Float32Array(count * itemSize);
636
+ for (let i = 0; i < count; i++) {
637
+ for (let j = 0; j < itemSize; j++) {
638
+ result[i * itemSize + j] = attr.getComponent(i, j);
639
+ }
640
+ }
641
+ return result;
642
+ }
643
+ function simplifyGeometry(geometry, options = {}) {
644
+ return safeTry(function* () {
645
+ const opts = { ...DEFAULT_SIMPLIFY_OPTIONS, ...options };
646
+ const positionAttr = geometry.getAttribute("position");
647
+ if (!positionAttr) {
648
+ return err({
649
+ type: "ASSET_ERROR",
650
+ message: "position\u5C5E\u6027\u304C\u5B58\u5728\u3057\u307E\u305B\u3093"
651
+ });
652
+ }
653
+ const vertexCount = positionAttr.count;
654
+ let indices;
655
+ if (geometry.index) {
656
+ indices = new Uint32Array(geometry.index.array);
657
+ } else {
658
+ indices = new Uint32Array(vertexCount);
659
+ for (let i = 0; i < vertexCount; i++) {
660
+ indices[i] = i;
661
+ }
662
+ }
663
+ const positions = extractAttributeData(positionAttr);
664
+ const uvAttr = geometry.getAttribute("uv");
665
+ const normalAttr = geometry.getAttribute("normal");
666
+ const attributeWeights = [];
667
+ if (normalAttr) {
668
+ for (let i = 0; i < 3; i++) {
669
+ attributeWeights.push(opts.normalWeight);
670
+ }
671
+ }
672
+ if (uvAttr) {
673
+ for (let i = 0; i < 2; i++) {
674
+ attributeWeights.push(opts.uvWeight);
675
+ }
676
+ }
677
+ const rawTargetCount = Math.floor(indices.length * opts.targetRatio);
678
+ const targetIndexCount = Math.max(3, Math.floor(rawTargetCount / 3) * 3);
679
+ const flags = [];
680
+ if (opts.lockBorder) {
681
+ flags.push("LockBorder");
682
+ }
683
+ let newIndices;
684
+ let resultError;
685
+ if (attributeWeights.length > 0) {
686
+ const stride = attributeWeights.length;
687
+ const attributes = new Float32Array(vertexCount * stride);
688
+ for (let v = 0; v < vertexCount; v++) {
689
+ let offset = 0;
690
+ if (normalAttr) {
691
+ for (let j = 0; j < 3; j++) {
692
+ attributes[v * stride + offset + j] = normalAttr.getComponent(v, j);
693
+ }
694
+ offset += 3;
695
+ }
696
+ if (uvAttr) {
697
+ for (let j = 0; j < 2; j++) {
698
+ attributes[v * stride + offset + j] = uvAttr.getComponent(v, j);
699
+ }
700
+ }
701
+ }
702
+ [newIndices, resultError] = MeshoptSimplifier.simplifyWithAttributes(
703
+ indices,
704
+ positions,
705
+ 3,
706
+ // position stride
707
+ attributes,
708
+ stride,
709
+ attributeWeights,
710
+ null,
711
+ // vertex_lock
712
+ targetIndexCount,
713
+ opts.targetError,
714
+ flags
715
+ );
716
+ } else {
717
+ [newIndices, resultError] = MeshoptSimplifier.simplify(
718
+ indices,
719
+ positions,
720
+ 3,
721
+ targetIndexCount,
722
+ opts.targetError,
723
+ flags
724
+ );
725
+ }
726
+ if (newIndices.length === indices.length) {
727
+ return ok(geometry.clone());
728
+ }
729
+ const newGeometry = yield* rebuildGeometry(geometry, newIndices);
730
+ newGeometry.userData.simplifyError = resultError;
731
+ return ok(newGeometry);
732
+ });
733
+ }
734
+ function rebuildGeometry(originalGeometry, newIndices) {
735
+ return safeTry(function* () {
736
+ const usedVertices = /* @__PURE__ */ new Set();
737
+ for (let i = 0; i < newIndices.length; i++) {
738
+ usedVertices.add(newIndices[i]);
739
+ }
740
+ const vertexRemap = /* @__PURE__ */ new Map();
741
+ const sortedIndices = Array.from(usedVertices).sort((a, b) => a - b);
742
+ let newIndex = 0;
743
+ for (const oldIndex of sortedIndices) {
744
+ vertexRemap.set(oldIndex, newIndex++);
745
+ }
746
+ const newVertexCount = vertexRemap.size;
747
+ const newGeometry = new BufferGeometry();
748
+ const remappedIndices = new Uint32Array(newIndices.length);
749
+ for (let i = 0; i < newIndices.length; i++) {
750
+ const remapped = vertexRemap.get(newIndices[i]);
751
+ if (remapped === void 0) {
752
+ return err({
753
+ type: "INTERNAL_ERROR",
754
+ message: `\u9802\u70B9\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u30EA\u30DE\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${newIndices[i]}`
755
+ });
756
+ }
757
+ remappedIndices[i] = remapped;
758
+ }
759
+ newGeometry.setIndex(new BufferAttribute(remappedIndices, 1));
760
+ for (const name of Object.keys(originalGeometry.attributes)) {
761
+ const attr = originalGeometry.getAttribute(name);
762
+ const itemSize = attr.itemSize;
763
+ const normalized = attr.normalized;
764
+ let TypedArrayConstructor;
765
+ if (name === "skinIndex") {
766
+ TypedArrayConstructor = Uint16Array;
767
+ } else if (attr.array instanceof Uint8Array) {
768
+ TypedArrayConstructor = Uint8Array;
769
+ } else {
770
+ TypedArrayConstructor = Float32Array;
771
+ }
772
+ const newArray = new TypedArrayConstructor(newVertexCount * itemSize);
773
+ for (const [oldIdx, newIdx] of vertexRemap) {
774
+ for (let j = 0; j < itemSize; j++) {
775
+ newArray[newIdx * itemSize + j] = attr.array[oldIdx * itemSize + j];
776
+ }
777
+ }
778
+ const newAttr = new BufferAttribute(newArray, itemSize, normalized);
779
+ newGeometry.setAttribute(name, newAttr);
780
+ }
781
+ if (originalGeometry.groups.length > 0) {
782
+ newGeometry.addGroup(0, remappedIndices.length, 0);
783
+ if (originalGeometry.groups.length > 1) {
784
+ console.warn(
785
+ `\u8907\u6570\u30B0\u30EB\u30FC\u30D7(${originalGeometry.groups.length})\u3092\u6301\u3064\u30B8\u30AA\u30E1\u30C8\u30EA\u306F\u5358\u4E00\u30B0\u30EB\u30FC\u30D7\u306B\u7D71\u5408\u3055\u308C\u307E\u3059`
786
+ );
787
+ }
788
+ }
789
+ newGeometry.setDrawRange(0, Infinity);
790
+ newGeometry.computeBoundingBox();
791
+ newGeometry.computeBoundingSphere();
792
+ return ok(newGeometry);
793
+ });
794
+ }
795
+
796
+ // src/process/simplify.ts
797
+ function simplifyMeshes(rootNode, excludedMeshes, options = {}) {
798
+ return ResultAsync.fromSafePromise(
799
+ simplifyMeshesAsync(rootNode, excludedMeshes, options)
800
+ );
801
+ }
802
+ async function simplifyMeshesAsync(rootNode, excludedMeshes, options) {
803
+ await ensureSimplifierReady();
804
+ const stats = {
805
+ processedMeshCount: 0,
806
+ skippedMeshCount: 0,
807
+ originalVertexCount: 0,
808
+ simplifiedVertexCount: 0,
809
+ originalIndexCount: 0,
810
+ simplifiedIndexCount: 0,
811
+ vertexReductionRatio: 0,
812
+ indexReductionRatio: 0
813
+ };
814
+ const opts = {
815
+ morphTargetHandling: "skip",
816
+ ...options
817
+ };
818
+ const meshesToProcess = [];
819
+ rootNode.traverse((obj) => {
820
+ if (!(obj instanceof Mesh)) return;
821
+ if (excludedMeshes.has(obj)) {
822
+ stats.skippedMeshCount++;
823
+ return;
824
+ }
825
+ if (obj.geometry.morphAttributes && Object.keys(obj.geometry.morphAttributes).length > 0) {
826
+ if (opts.morphTargetHandling === "skip") {
827
+ console.warn(`MorphTarget\u3092\u6301\u3064\u30E1\u30C3\u30B7\u30E5\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F: ${obj.name}`);
828
+ stats.skippedMeshCount++;
829
+ return;
830
+ }
831
+ }
832
+ meshesToProcess.push(obj);
833
+ });
834
+ for (const mesh of meshesToProcess) {
835
+ const originalVertexCount = mesh.geometry.getAttribute("position")?.count ?? 0;
836
+ const originalIndexCount = mesh.geometry.index?.count ?? originalVertexCount;
837
+ stats.originalVertexCount += originalVertexCount;
838
+ stats.originalIndexCount += originalIndexCount;
839
+ const result = simplifyGeometry(mesh.geometry, options);
840
+ if (result.isErr()) {
841
+ console.warn(
842
+ `\u30E1\u30C3\u30B7\u30E5\u306E\u7C21\u7565\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${mesh.name}): ${result.error.message}`
843
+ );
844
+ stats.skippedMeshCount++;
845
+ stats.originalVertexCount -= originalVertexCount;
846
+ stats.originalIndexCount -= originalIndexCount;
847
+ continue;
848
+ }
849
+ const oldGeometry = mesh.geometry;
850
+ mesh.geometry = result.value;
851
+ for (const key of Object.keys(mesh.geometry.attributes)) {
852
+ const attr = mesh.geometry.attributes[key];
853
+ if (attr) {
854
+ attr.needsUpdate = true;
855
+ }
856
+ }
857
+ if (mesh.geometry.index) {
858
+ mesh.geometry.index.needsUpdate = true;
859
+ }
860
+ if (mesh instanceof SkinnedMesh) {
861
+ if (mesh.skeleton) {
862
+ mesh.bind(mesh.skeleton, mesh.bindMatrix);
863
+ }
864
+ }
865
+ oldGeometry.dispose();
866
+ const newVertexCount = mesh.geometry.getAttribute("position")?.count ?? 0;
867
+ const newIndexCount = mesh.geometry.index?.count ?? newVertexCount;
868
+ stats.simplifiedVertexCount += newVertexCount;
869
+ stats.simplifiedIndexCount += newIndexCount;
870
+ stats.processedMeshCount++;
871
+ }
872
+ if (stats.originalVertexCount > 0) {
873
+ stats.vertexReductionRatio = 1 - stats.simplifiedVertexCount / stats.originalVertexCount;
874
+ }
875
+ if (stats.originalIndexCount > 0) {
876
+ stats.indexReductionRatio = 1 - stats.simplifiedIndexCount / stats.originalIndexCount;
877
+ }
878
+ return stats;
879
+ }
880
+ function mergeGeometriesWithSlotAttribute(meshes, materialSlotMap, slotAttributeName) {
881
+ if (meshes.length === 0) {
882
+ return err({
883
+ type: "ASSET_ERROR",
884
+ message: "\u30DE\u30FC\u30B8\u5BFE\u8C61\u306E\u30E1\u30C3\u30B7\u30E5\u304C\u3042\u308A\u307E\u305B\u3093"
885
+ });
886
+ }
887
+ const validMeshes = [];
888
+ for (const mesh of meshes) {
889
+ if (mesh.geometry instanceof BufferGeometry) {
890
+ const vertexCount = mesh.geometry.attributes.position?.count ?? 0;
891
+ if (vertexCount > 0) {
892
+ validMeshes.push({ mesh, geometry: mesh.geometry });
893
+ }
894
+ }
895
+ }
896
+ if (validMeshes.length === 0) {
897
+ return err({
898
+ type: "ASSET_ERROR",
899
+ message: "\u6709\u52B9\u306A\u30B8\u30AA\u30E1\u30C8\u30EA\u3092\u6301\u3064\u30E1\u30C3\u30B7\u30E5\u304C\u3042\u308A\u307E\u305B\u3093"
900
+ });
901
+ }
902
+ const allBones = [];
903
+ const boneUniqueMap = /* @__PURE__ */ new Map();
904
+ const hasSkinnedMesh = validMeshes.some(
905
+ ({ mesh }) => mesh instanceof SkinnedMesh
906
+ );
907
+ if (hasSkinnedMesh) {
908
+ for (const { mesh } of validMeshes) {
909
+ if (mesh instanceof SkinnedMesh && mesh.skeleton) {
910
+ for (const bone of mesh.skeleton.bones) {
911
+ if (!boneUniqueMap.has(bone.uuid)) {
912
+ boneUniqueMap.set(bone.uuid, allBones.length);
913
+ allBones.push(bone);
914
+ }
915
+ }
916
+ }
917
+ }
918
+ }
919
+ const transformedGeometries = [];
920
+ const slotData = [];
921
+ for (const { mesh, geometry } of validMeshes) {
922
+ const transformedGeometry = geometry.clone();
923
+ const vertexCount = geometry.attributes.position?.count ?? 0;
924
+ if (mesh instanceof SkinnedMesh) {
925
+ transformedGeometry.applyMatrix4(mesh.bindMatrix);
926
+ if (mesh.skeleton && transformedGeometry.attributes.skinIndex) {
927
+ const skinIndexAttr = transformedGeometry.attributes.skinIndex;
928
+ const oldBones = mesh.skeleton.bones;
929
+ for (let i = 0; i < skinIndexAttr.count; i++) {
930
+ const a = skinIndexAttr.getX(i);
931
+ const b = skinIndexAttr.getY(i);
932
+ const c = skinIndexAttr.getZ(i);
933
+ const d = skinIndexAttr.getW(i);
934
+ const newA = oldBones[a] ? boneUniqueMap.get(oldBones[a].uuid) ?? 0 : 0;
935
+ const newB = oldBones[b] ? boneUniqueMap.get(oldBones[b].uuid) ?? 0 : 0;
936
+ const newC = oldBones[c] ? boneUniqueMap.get(oldBones[c].uuid) ?? 0 : 0;
937
+ const newD = oldBones[d] ? boneUniqueMap.get(oldBones[d].uuid) ?? 0 : 0;
938
+ skinIndexAttr.setXYZW(i, newA, newB, newC, newD);
939
+ }
940
+ }
941
+ } else {
942
+ mesh.updateMatrixWorld(true);
943
+ transformedGeometry.applyMatrix4(mesh.matrixWorld);
944
+ }
945
+ transformedGeometry.morphAttributes = {};
946
+ transformedGeometry.morphTargetsRelative = false;
947
+ transformedGeometries.push(transformedGeometry);
948
+ const slotIndex = materialSlotMap.get(mesh) ?? 0;
949
+ for (let i = 0; i < vertexCount; i++) {
950
+ slotData.push(slotIndex);
951
+ }
952
+ }
953
+ const mergedGeometry = mergeGeometries(transformedGeometries);
954
+ if (hasSkinnedMesh) {
955
+ mergedGeometry.userData.skeleton = new Skeleton(allBones);
956
+ }
957
+ const slotArray = new Float32Array(slotData);
958
+ mergedGeometry.setAttribute(
959
+ slotAttributeName,
960
+ new Float32BufferAttribute(slotArray, 1)
961
+ );
962
+ return ok([mergedGeometry]);
963
+ }
964
+
965
+ // src/util/material/combine.ts
966
+ var DEFAULT_OPTIONS = {
967
+ atlasSize: 2048,
968
+ slotAttributeName: "mtoonMaterialSlot",
969
+ texelsPerSlot: 9
970
+ };
971
+ function combineMToonMaterials(input, options = {}, excludedMeshes) {
972
+ let materialInfos;
973
+ if (input instanceof Map) {
974
+ materialInfos = [];
975
+ for (const [material, meshes] of input) {
976
+ materialInfos.push({
977
+ material,
978
+ meshes,
979
+ hasOutline: material.outlineWidthMode !== "none",
980
+ outlineWidthMode: material.outlineWidthMode,
981
+ renderMode: getRenderMode(material)
982
+ });
983
+ }
984
+ } else {
985
+ materialInfos = input;
986
+ }
987
+ return combineMToonMaterialsInternal(materialInfos, options, excludedMeshes);
988
+ }
989
+ function groupMaterialInfoByRenderMode(materialInfos) {
990
+ const groups = /* @__PURE__ */ new Map();
991
+ for (const info of materialInfos) {
992
+ const mode = info.renderMode;
993
+ if (!groups.has(mode)) {
994
+ groups.set(mode, []);
995
+ }
996
+ groups.get(mode).push(info);
997
+ }
998
+ return groups;
999
+ }
1000
+ function processSingleGroup(materialInfos, opts, excludedMeshes, renderMode) {
1001
+ return safeTry(function* () {
1002
+ const materials = materialInfos.map((info) => info.material);
1003
+ let hasAnyOutline = false;
1004
+ let outlineWidthMode = "worldCoordinates";
1005
+ for (const info of materialInfos) {
1006
+ if (info.hasOutline) {
1007
+ hasAnyOutline = true;
1008
+ outlineWidthMode = info.outlineWidthMode;
1009
+ break;
1010
+ }
1011
+ }
1012
+ const parameterTexture = yield* createParameterTexture(
1013
+ materials,
1014
+ opts.texelsPerSlot
1015
+ );
1016
+ const materialSlotIndex = /* @__PURE__ */ new Map();
1017
+ materials.forEach((mat, index) => {
1018
+ materialSlotIndex.set(mat, index);
1019
+ });
1020
+ const meshToSlotIndex = /* @__PURE__ */ new Map();
1021
+ const meshesForMerge = [];
1022
+ for (const info of materialInfos) {
1023
+ const slotIndex = materialSlotIndex.get(info.material) ?? 0;
1024
+ for (const mesh of info.meshes) {
1025
+ if (excludedMeshes?.has(mesh)) continue;
1026
+ meshToSlotIndex.set(mesh, slotIndex);
1027
+ meshesForMerge.push(mesh);
1028
+ }
1029
+ }
1030
+ const atlasMaterial = createMToonAtlasMaterial(
1031
+ materials[0],
1032
+ parameterTexture,
1033
+ materials.length,
1034
+ opts.texelsPerSlot,
1035
+ opts.slotAttributeName,
1036
+ renderMode
1037
+ );
1038
+ if (meshesForMerge.length === 0) {
1039
+ return ok({
1040
+ mesh: null,
1041
+ material: atlasMaterial,
1042
+ outlineMesh: void 0,
1043
+ outlineMaterial: void 0,
1044
+ materialSlotIndex,
1045
+ meshCount: 0,
1046
+ materialCount: materials.length
1047
+ });
1048
+ }
1049
+ const mergedGeometries = yield* mergeGeometriesWithSlotAttribute(
1050
+ meshesForMerge,
1051
+ meshToSlotIndex,
1052
+ opts.slotAttributeName
1053
+ );
1054
+ const mergedGeometry = mergedGeometries[0];
1055
+ const { mesh: combinedMesh } = createCombinedMesh(
1056
+ mergedGeometry,
1057
+ atlasMaterial,
1058
+ meshesForMerge,
1059
+ `CombinedMToonMesh_${renderMode}`
1060
+ );
1061
+ let outlineMesh;
1062
+ let outlineMaterial;
1063
+ if (hasAnyOutline) {
1064
+ outlineMaterial = atlasMaterial.createOutlineMaterial(
1065
+ outlineWidthMode === "screenCoordinates" ? "screenCoordinates" : "worldCoordinates"
1066
+ );
1067
+ const outlineResult = createCombinedMesh(
1068
+ mergedGeometry,
1069
+ outlineMaterial,
1070
+ meshesForMerge,
1071
+ `CombinedMToonMesh_${renderMode}_Outline`
1072
+ );
1073
+ outlineMesh = outlineResult.mesh;
1074
+ outlineMesh.renderOrder = combinedMesh.renderOrder - 1;
1075
+ }
1076
+ return ok({
1077
+ mesh: combinedMesh,
1078
+ material: atlasMaterial,
1079
+ outlineMesh,
1080
+ outlineMaterial,
1081
+ materialSlotIndex,
1082
+ meshCount: meshesForMerge.length,
1083
+ materialCount: materials.length
1084
+ });
1085
+ });
1086
+ }
1087
+ function createCombinedMesh(geometry, material, sourceMeshes, name) {
1088
+ const firstSkinnedMesh = sourceMeshes.find(
1089
+ (mesh2) => mesh2 instanceof SkinnedMesh
1090
+ );
1091
+ let mesh;
1092
+ if (firstSkinnedMesh) {
1093
+ const skinnedMesh = new SkinnedMesh(geometry, material);
1094
+ skinnedMesh.name = name;
1095
+ if (geometry.userData.skeleton) {
1096
+ const skeleton = geometry.userData.skeleton;
1097
+ const identityMatrix = firstSkinnedMesh.matrixWorld.clone().identity();
1098
+ skinnedMesh.bind(skeleton, identityMatrix);
1099
+ } else if (firstSkinnedMesh.skeleton) {
1100
+ const identityMatrix = firstSkinnedMesh.matrixWorld.clone().identity();
1101
+ skinnedMesh.bind(firstSkinnedMesh.skeleton, identityMatrix);
1102
+ }
1103
+ mesh = skinnedMesh;
1104
+ } else {
1105
+ mesh = new Mesh(geometry, material);
1106
+ mesh.name = name;
1107
+ }
1108
+ return { mesh, firstSkinnedMesh };
1109
+ }
1110
+ function combineMToonMaterialsInternal(materialInfos, options, excludedMeshes) {
1111
+ return safeTry(function* () {
1112
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1113
+ const groupedInfos = groupMaterialInfoByRenderMode(materialInfos);
1114
+ const groups = /* @__PURE__ */ new Map();
1115
+ const materialSlotIndex = /* @__PURE__ */ new Map();
1116
+ let totalMeshCount = 0;
1117
+ let totalMaterialCount = 0;
1118
+ const renderModeOrder = ["opaque", "alphaTest", "transparent"];
1119
+ for (const renderMode of renderModeOrder) {
1120
+ const infos = groupedInfos.get(renderMode);
1121
+ if (!infos || infos.length === 0) continue;
1122
+ const result = yield* processSingleGroup(
1123
+ infos,
1124
+ opts,
1125
+ excludedMeshes,
1126
+ renderMode
1127
+ );
1128
+ groups.set(renderMode, {
1129
+ mesh: result.mesh,
1130
+ material: result.material,
1131
+ outlineMesh: result.outlineMesh,
1132
+ outlineMaterial: result.outlineMaterial
1133
+ });
1134
+ for (const [mat, slotIndex] of result.materialSlotIndex) {
1135
+ materialSlotIndex.set(mat, { renderMode, slotIndex });
1136
+ }
1137
+ totalMeshCount += result.meshCount;
1138
+ totalMaterialCount += result.materialCount;
1139
+ }
1140
+ if (groups.size === 0) {
1141
+ return err({
1142
+ type: "ASSET_ERROR",
1143
+ message: "\u30DE\u30FC\u30B8\u5BFE\u8C61\u306E\u30E1\u30C3\u30B7\u30E5\u304C\u3042\u308A\u307E\u305B\u3093"
1144
+ });
1145
+ }
1146
+ let resultDrawCalls = 0;
1147
+ for (const group of groups.values()) {
1148
+ if (group.mesh) resultDrawCalls += 1;
1149
+ if (group.outlineMesh) resultDrawCalls += 1;
1150
+ }
1151
+ return ok({
1152
+ groups,
1153
+ materialSlotIndex,
1154
+ statistics: {
1155
+ originalMeshCount: totalMeshCount,
1156
+ originalMaterialCount: totalMaterialCount,
1157
+ reducedDrawCalls: totalMaterialCount - resultDrawCalls
1158
+ }
1159
+ });
1160
+ });
1161
+ }
1162
+ function createMToonAtlasMaterial(representativeMaterial, parameterTexture, slotCount, texelsPerSlot, slotAttributeName, renderMode) {
1163
+ const atlasedTextures = {};
1164
+ if (representativeMaterial.map) {
1165
+ atlasedTextures.baseColor = representativeMaterial.map;
1166
+ }
1167
+ if (representativeMaterial.shadeMultiplyTexture) {
1168
+ atlasedTextures.shade = representativeMaterial.shadeMultiplyTexture;
1169
+ }
1170
+ if (representativeMaterial.shadingShiftTexture) {
1171
+ atlasedTextures.shadingShift = representativeMaterial.shadingShiftTexture;
1172
+ }
1173
+ if (representativeMaterial.normalMap) {
1174
+ atlasedTextures.normal = representativeMaterial.normalMap;
1175
+ }
1176
+ if (representativeMaterial.emissiveMap) {
1177
+ atlasedTextures.emissive = representativeMaterial.emissiveMap;
1178
+ }
1179
+ if (representativeMaterial.matcapTexture) {
1180
+ atlasedTextures.matcap = representativeMaterial.matcapTexture;
1181
+ }
1182
+ if (representativeMaterial.rimMultiplyTexture) {
1183
+ atlasedTextures.rim = representativeMaterial.rimMultiplyTexture;
1184
+ }
1185
+ if (representativeMaterial.uvAnimationMaskTexture) {
1186
+ atlasedTextures.uvAnimationMask = representativeMaterial.uvAnimationMaskTexture;
1187
+ }
1188
+ const parameterTextureDescriptor = {
1189
+ texture: parameterTexture,
1190
+ slotCount,
1191
+ texelsPerSlot,
1192
+ atlasedTextures
1193
+ };
1194
+ const slotAttribute = {
1195
+ name: slotAttributeName,
1196
+ description: "Material slot index for instancing"
1197
+ };
1198
+ const material = new MToonAtlasMaterial({
1199
+ parameterTexture: parameterTextureDescriptor,
1200
+ slotAttribute
1201
+ });
1202
+ if (renderMode === "transparent") {
1203
+ material.transparent = true;
1204
+ material.depthWrite = false;
1205
+ } else if (renderMode === "alphaTest") {
1206
+ material.transparent = false;
1207
+ material.alphaTest = representativeMaterial.alphaTest;
1208
+ } else {
1209
+ material.transparent = false;
1210
+ }
1211
+ return material;
1212
+ }
1213
+
1214
+ // src/util/material/index.ts
1215
+ function getRenderMode(material) {
1216
+ if (material.transparent) return "transparent";
1217
+ if (material.alphaTest > 0) return "alphaTest";
1218
+ return "opaque";
1219
+ }
1220
+ function assignAtlasTexturesToMaterial(material, atlasMap) {
1221
+ if (atlasMap.map) material.map = atlasMap.map;
1222
+ if (atlasMap.normalMap) material.normalMap = atlasMap.normalMap;
1223
+ if (atlasMap.emissiveMap) material.emissiveMap = atlasMap.emissiveMap;
1224
+ if (atlasMap.shadeMultiplyTexture)
1225
+ material.shadeMultiplyTexture = atlasMap.shadeMultiplyTexture;
1226
+ if (atlasMap.shadingShiftTexture)
1227
+ material.shadingShiftTexture = atlasMap.shadingShiftTexture;
1228
+ if (atlasMap.rimMultiplyTexture)
1229
+ material.rimMultiplyTexture = atlasMap.rimMultiplyTexture;
1230
+ if (atlasMap.outlineWidthMultiplyTexture)
1231
+ material.outlineWidthMultiplyTexture = atlasMap.outlineWidthMultiplyTexture;
1232
+ if (atlasMap.uvAnimationMaskTexture)
1233
+ material.uvAnimationMaskTexture = atlasMap.uvAnimationMaskTexture;
1234
+ }
1235
+ function getMToonMaterialInfoFromObject3D(rootNode) {
1236
+ const meshes = [];
1237
+ rootNode.traverse((obj) => {
1238
+ if (obj instanceof Mesh) {
1239
+ meshes.push(obj);
1240
+ }
1241
+ });
1242
+ const materialInfoMap = /* @__PURE__ */ new Map();
1243
+ for (const mesh of meshes) {
1244
+ if (Array.isArray(mesh.material)) {
1245
+ if (mesh.material.length === 0) continue;
1246
+ if (!(mesh.material[0] instanceof MToonMaterial)) continue;
1247
+ const material = mesh.material[0];
1248
+ const hasOutline = material.outlineWidthMode !== "none";
1249
+ const outlineWidthMode = material.outlineWidthMode;
1250
+ if (!materialInfoMap.has(material)) {
1251
+ materialInfoMap.set(material, {
1252
+ meshes: [],
1253
+ hasOutline,
1254
+ outlineWidthMode,
1255
+ renderMode: getRenderMode(material)
1256
+ });
1257
+ }
1258
+ materialInfoMap.get(material).meshes.push(mesh);
1259
+ } else if (mesh.material instanceof MToonMaterial) {
1260
+ const material = mesh.material;
1261
+ const hasOutline = material.outlineWidthMode !== "none";
1262
+ const outlineWidthMode = material.outlineWidthMode;
1263
+ if (!materialInfoMap.has(material)) {
1264
+ materialInfoMap.set(material, {
1265
+ meshes: [],
1266
+ hasOutline,
1267
+ outlineWidthMode,
1268
+ renderMode: getRenderMode(material)
1269
+ });
1270
+ }
1271
+ materialInfoMap.get(material).meshes.push(mesh);
1272
+ }
1273
+ }
1274
+ if (materialInfoMap.size === 0) {
1275
+ return err({
1276
+ type: "ASSET_ERROR",
1277
+ message: "MToonMaterial\u304C\u3042\u308A\u307E\u305B\u3093"
1278
+ });
1279
+ }
1280
+ const result = [];
1281
+ for (const [material, info] of materialInfoMap) {
1282
+ result.push({
1283
+ material,
1284
+ meshes: info.meshes,
1285
+ hasOutline: info.hasOutline,
1286
+ outlineWidthMode: info.outlineWidthMode,
1287
+ renderMode: info.renderMode
1288
+ });
1289
+ }
1290
+ return ok(result);
1291
+ }
1292
+
1293
+ // src/util/mesh/deleter.ts
1294
+ function deleteMesh(mesh) {
1295
+ if (Array.isArray(mesh.material)) {
1296
+ mesh.material.forEach((m) => m.dispose());
1297
+ } else {
1298
+ mesh.material.dispose();
1299
+ }
1300
+ mesh.geometry.dispose();
1301
+ }
1302
+ function migrateSkeletonVRM0ToVRM1(rootNode, options = {}) {
1303
+ return safeTry(function* () {
1304
+ const skinnedMeshes = [];
1305
+ rootNode.traverse((obj) => {
1306
+ if (obj instanceof SkinnedMesh) {
1307
+ skinnedMeshes.push(obj);
1308
+ }
1309
+ });
1310
+ if (skinnedMeshes.length === 0) {
1311
+ return err({
1312
+ type: "ASSET_ERROR",
1313
+ message: "SkinnedMesh\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093"
1314
+ });
1315
+ }
1316
+ if (!options.skipVertexRotation) {
1317
+ rotateAllVertexBuffers(skinnedMeshes);
1318
+ }
1319
+ if (!options.skipBoneTransform) {
1320
+ const allBonePositions = /* @__PURE__ */ new Map();
1321
+ const processedSkeletons = /* @__PURE__ */ new Set();
1322
+ for (const mesh of skinnedMeshes) {
1323
+ const skeleton = mesh.skeleton;
1324
+ if (!skeleton || processedSkeletons.has(skeleton)) continue;
1325
+ processedSkeletons.add(skeleton);
1326
+ const positions = recordBoneWorldPositions(skeleton);
1327
+ for (const [bone, pos] of positions) {
1328
+ if (!allBonePositions.has(bone)) {
1329
+ allBonePositions.set(bone, pos);
1330
+ }
1331
+ }
1332
+ }
1333
+ const rotatedPositions = rotateBonePositions(allBonePositions);
1334
+ const allRootBones = /* @__PURE__ */ new Set();
1335
+ for (const bone of allBonePositions.keys()) {
1336
+ let current = bone;
1337
+ while (current.parent instanceof Bone) {
1338
+ current = current.parent;
1339
+ }
1340
+ allRootBones.add(current);
1341
+ }
1342
+ if (allRootBones.size === 0) {
1343
+ return err({
1344
+ type: "ASSET_ERROR",
1345
+ message: "\u30EB\u30FC\u30C8\u30DC\u30FC\u30F3\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093"
1346
+ });
1347
+ }
1348
+ for (const rootBone of allRootBones) {
1349
+ rebuildBoneTransforms(rootBone, rotatedPositions, options.humanoidBones);
1350
+ }
1351
+ processedSkeletons.forEach((skeleton) => {
1352
+ recalculateBoneInverses(skeleton);
1353
+ });
1354
+ }
1355
+ if (!options.skipBindMatrix) {
1356
+ for (const mesh of skinnedMeshes) {
1357
+ mesh.skeleton.calculateInverses();
1358
+ }
1359
+ }
1360
+ return ok(void 0);
1361
+ });
1362
+ }
1363
+ function rotateAllVertexBuffers(skinnedMeshes) {
1364
+ function rotateVec3Array(array) {
1365
+ const count = array.length / 3;
1366
+ for (let i = 0; i < count; i++) {
1367
+ const baseIdx = i * 3;
1368
+ array[baseIdx + 0] = -array[baseIdx + 0];
1369
+ array[baseIdx + 2] = -array[baseIdx + 2];
1370
+ }
1371
+ }
1372
+ function rotateAndSetAttribute(geometry, attrName, attr) {
1373
+ if (attr.itemSize !== 3) return;
1374
+ const rotated = editBufferAttribute(attr, rotateVec3Array);
1375
+ geometry.setAttribute(attrName, rotated);
1376
+ }
1377
+ const processedGeometries = /* @__PURE__ */ new Set();
1378
+ for (const mesh of skinnedMeshes) {
1379
+ const geometry = mesh.geometry;
1380
+ if (processedGeometries.has(geometry)) {
1381
+ continue;
1382
+ }
1383
+ processedGeometries.add(geometry);
1384
+ const posAttr = geometry.getAttribute("position");
1385
+ if (posAttr) {
1386
+ rotateAndSetAttribute(geometry, "position", posAttr);
1387
+ }
1388
+ const normalAttr = geometry.getAttribute("normal");
1389
+ if (normalAttr) {
1390
+ rotateAndSetAttribute(geometry, "normal", normalAttr);
1391
+ }
1392
+ const morphPositions = geometry.morphAttributes.position;
1393
+ if (morphPositions && Array.isArray(morphPositions)) {
1394
+ for (let i = 0; i < morphPositions.length; i++) {
1395
+ const morphAttr = morphPositions[i];
1396
+ if (morphAttr.itemSize !== 3) continue;
1397
+ const array = morphAttr.array;
1398
+ rotateVec3Array(array);
1399
+ morphAttr.needsUpdate = true;
1400
+ }
1401
+ }
1402
+ const morphNormals = geometry.morphAttributes.normal;
1403
+ if (morphNormals && Array.isArray(morphNormals)) {
1404
+ for (let i = 0; i < morphNormals.length; i++) {
1405
+ const morphAttr = morphNormals[i];
1406
+ if (morphAttr.itemSize !== 3) continue;
1407
+ const array = morphAttr.array;
1408
+ rotateVec3Array(array);
1409
+ morphAttr.needsUpdate = true;
1410
+ }
1411
+ }
1412
+ geometry.dispatchEvent({ type: "dispose" });
1413
+ }
1414
+ }
1415
+ function recordBoneWorldPositions(skeleton) {
1416
+ const positions = /* @__PURE__ */ new Map();
1417
+ if (skeleton.bones.length > 0) {
1418
+ skeleton.bones[0].updateWorldMatrix(true, true);
1419
+ }
1420
+ for (const bone of skeleton.bones) {
1421
+ const worldPos = new Vector3();
1422
+ bone.getWorldPosition(worldPos);
1423
+ positions.set(bone, worldPos.clone());
1424
+ }
1425
+ return positions;
1426
+ }
1427
+ function rotateBonePositions(positions) {
1428
+ const rotated = /* @__PURE__ */ new Map();
1429
+ const rotationMatrix = new Matrix4().makeRotationY(Math.PI);
1430
+ for (const [bone, pos] of positions) {
1431
+ const newPos = pos.clone().applyMatrix4(rotationMatrix);
1432
+ rotated.set(bone, newPos);
1433
+ }
1434
+ return rotated;
1435
+ }
1436
+ function rebuildBoneTransforms(rootBone, rotatedPositions, humanoidBones) {
1437
+ const identityQuat = new Quaternion();
1438
+ const rotationMatrix = new Matrix4().makeRotationY(Math.PI);
1439
+ const rotationQuat = new Quaternion().setFromRotationMatrix(rotationMatrix);
1440
+ function processBone(bone, parentWorldMatrix2) {
1441
+ let targetWorldPos = rotatedPositions.get(bone);
1442
+ if (!targetWorldPos) {
1443
+ bone.updateMatrixWorld(true);
1444
+ const currentWorldPos = new Vector3();
1445
+ bone.getWorldPosition(currentWorldPos);
1446
+ targetWorldPos = currentWorldPos.applyMatrix4(rotationMatrix);
1447
+ }
1448
+ const parentWorldMatrixInverse = parentWorldMatrix2.clone().invert();
1449
+ const isHumanoidBone = humanoidBones?.has(bone) ?? true;
1450
+ if (isHumanoidBone) {
1451
+ bone.quaternion.copy(identityQuat);
1452
+ const parentWorldPos = new Vector3().setFromMatrixPosition(
1453
+ parentWorldMatrix2
1454
+ );
1455
+ const localPos = targetWorldPos.clone().sub(parentWorldPos);
1456
+ bone.position.copy(localPos);
1457
+ } else {
1458
+ const originalLocalQuat = bone.quaternion.clone();
1459
+ const newLocalQuat = rotationQuat.clone().multiply(originalLocalQuat).multiply(rotationQuat.clone().invert());
1460
+ bone.quaternion.copy(newLocalQuat);
1461
+ const localPos = targetWorldPos.clone().applyMatrix4(parentWorldMatrixInverse);
1462
+ bone.position.copy(localPos);
1463
+ }
1464
+ bone.updateMatrix();
1465
+ bone.updateMatrixWorld(true);
1466
+ for (const child of bone.children) {
1467
+ if (child instanceof Bone) {
1468
+ processBone(child, bone.matrixWorld);
1469
+ }
1470
+ }
1471
+ }
1472
+ let parentWorldMatrix = new Matrix4();
1473
+ if (rootBone.parent) {
1474
+ rootBone.parent.updateMatrixWorld(true);
1475
+ parentWorldMatrix = rootBone.parent.matrixWorld.clone();
1476
+ }
1477
+ processBone(rootBone, parentWorldMatrix);
1478
+ }
1479
+ function recalculateBoneInverses(skeleton) {
1480
+ skeleton.bones.forEach((bone) => bone.updateMatrixWorld(true));
1481
+ skeleton.calculateInverses();
1482
+ skeleton.computeBoneTexture();
1483
+ }
1484
+ function createVirtualTailNodes(vrm, preRecordedDirections) {
1485
+ const createdTailNodes = [];
1486
+ if (!vrm.springBoneManager) return createdTailNodes;
1487
+ const springBoneManager = vrm.springBoneManager;
1488
+ const joints = springBoneManager.joints;
1489
+ if (!joints || joints.size === 0) return createdTailNodes;
1490
+ joints.forEach((joint) => {
1491
+ const bone = joint.bone;
1492
+ if (!bone) return;
1493
+ const hasJointChild = joint.child != null;
1494
+ const hasBoneChild = bone.children.some(
1495
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1496
+ (child) => child.type === "Bone" || child.isBone
1497
+ );
1498
+ if (!hasJointChild && !hasBoneChild) {
1499
+ const tailBone = new Bone();
1500
+ tailBone.name = `${bone.name}_tail`;
1501
+ let direction;
1502
+ if (preRecordedDirections?.has(bone)) {
1503
+ direction = preRecordedDirections.get(bone).clone().multiplyScalar(0.07);
1504
+ } else {
1505
+ direction = new Vector3().copy(bone.position);
1506
+ if (direction.lengthSq() > 0) {
1507
+ direction.normalize().multiplyScalar(0.07);
1508
+ } else {
1509
+ direction.set(0, 0.07, 0);
1510
+ }
1511
+ }
1512
+ tailBone.position.copy(direction);
1513
+ bone.add(tailBone);
1514
+ tailBone.updateMatrixWorld(true);
1515
+ createdTailNodes.push(tailBone);
1516
+ }
1517
+ });
1518
+ return createdTailNodes;
1519
+ }
1520
+ function rotateSpringBoneGravityDirections(vrm) {
1521
+ if (!vrm.springBoneManager) {
1522
+ return;
1523
+ }
1524
+ const springBoneManager = vrm.springBoneManager;
1525
+ const joints = springBoneManager.joints;
1526
+ if (!joints || joints.size === 0) {
1527
+ return;
1528
+ }
1529
+ const rotationMatrix = new Matrix4().makeRotationY(Math.PI);
1530
+ joints.forEach((joint) => {
1531
+ const settings = joint.settings;
1532
+ if (!settings || !settings.gravityDir) return;
1533
+ const gravityDir = settings.gravityDir;
1534
+ gravityDir.applyMatrix4(rotationMatrix);
1535
+ });
1536
+ }
1537
+ function rotateSpringBoneColliderOffsets(vrm) {
1538
+ if (!vrm.springBoneManager) {
1539
+ return;
1540
+ }
1541
+ const colliders = vrm.springBoneManager.colliders;
1542
+ if (!colliders || colliders.length === 0) {
1543
+ return;
1544
+ }
1545
+ const rotationMatrix = new Matrix4().makeRotationY(Math.PI);
1546
+ for (const collider of colliders) {
1547
+ const shape = collider.shape;
1548
+ const shapeAny = shape;
1549
+ if (shapeAny.offset instanceof Vector3) {
1550
+ shapeAny.offset.applyMatrix4(rotationMatrix);
1551
+ }
1552
+ if (shapeAny.tail instanceof Vector3) {
1553
+ shapeAny.tail.applyMatrix4(rotationMatrix);
1554
+ }
1555
+ }
1556
+ }
1557
+ function migrateSpringBone(vrm) {
1558
+ const tailNodes = createVirtualTailNodes(vrm);
1559
+ rotateSpringBoneGravityDirections(vrm);
1560
+ rotateSpringBoneColliderOffsets(vrm);
1561
+ vrm.springBoneManager?.setInitState();
1562
+ vrm.springBoneManager?.reset();
1563
+ return tailNodes;
1564
+ }
1565
+
1566
+ // src/avatar-optimizer.ts
1567
+ function optimizeModel(vrm, options = {}) {
1568
+ return safeTry(async function* () {
1569
+ const rootNode = vrm.scene;
1570
+ vrm.springBoneManager?.reset();
1571
+ const materialInfos = yield* getMToonMaterialInfoFromObject3D(rootNode);
1572
+ const materials = materialInfos.map((info) => info.material);
1573
+ const materialMeshMap = /* @__PURE__ */ new Map();
1574
+ for (const info of materialInfos) {
1575
+ materialMeshMap.set(info.material, info.meshes);
1576
+ }
1577
+ const patternMappings = buildPatternMaterialMappings(materials);
1578
+ const packLayouts = yield* await pack(patternMappings);
1579
+ const atlasMap = yield* generateAtlasImagesFromPatterns(
1580
+ materials,
1581
+ patternMappings,
1582
+ packLayouts.packed,
1583
+ options.atlas
1584
+ );
1585
+ const materialPlacementMap = /* @__PURE__ */ new Map();
1586
+ patternMappings.forEach((mapping, index) => {
1587
+ const placement = packLayouts.packed[index];
1588
+ for (const materialIndex of mapping.materialIndices) {
1589
+ const material = materials[materialIndex];
1590
+ assignAtlasTexturesToMaterial(material, atlasMap);
1591
+ materialPlacementMap.set(material, placement);
1592
+ }
1593
+ });
1594
+ yield* applyPlacementsToGeometries(rootNode, materialPlacementMap);
1595
+ const excludedMeshes = /* @__PURE__ */ new Set();
1596
+ if (vrm.expressionManager) {
1597
+ for (const expression of vrm.expressionManager.expressions) {
1598
+ for (const bind of expression.binds) {
1599
+ const bindAny = bind;
1600
+ if (bindAny.primitives) {
1601
+ for (const mesh of bindAny.primitives) {
1602
+ if (mesh && mesh.isMesh) {
1603
+ excludedMeshes.add(mesh);
1604
+ }
1605
+ }
1606
+ }
1607
+ if (bindAny.material) {
1608
+ const meshes = materialMeshMap.get(bindAny.material);
1609
+ if (meshes) {
1610
+ meshes.forEach((mesh) => excludedMeshes.add(mesh));
1611
+ }
1612
+ }
1613
+ }
1614
+ }
1615
+ }
1616
+ let simplifyStats;
1617
+ if (options.simplify) {
1618
+ simplifyStats = yield* await simplifyMeshes(
1619
+ rootNode,
1620
+ excludedMeshes,
1621
+ options.simplify
1622
+ );
1623
+ }
1624
+ const combineResult = yield* combineMToonMaterials(
1625
+ materialInfos,
1626
+ {},
1627
+ excludedMeshes
1628
+ );
1629
+ const firstGroup = combineResult.groups.values().next().value;
1630
+ const slotAttributeName = firstGroup?.material.slotAttribute?.name || "mtoonMaterialSlot";
1631
+ for (const mesh of excludedMeshes) {
1632
+ const originalMaterial = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
1633
+ if (!(originalMaterial instanceof MToonMaterial)) continue;
1634
+ const slotInfo = combineResult.materialSlotIndex.get(originalMaterial);
1635
+ if (!slotInfo) continue;
1636
+ const group = combineResult.groups.get(slotInfo.renderMode);
1637
+ if (!group) continue;
1638
+ const geometry = mesh.geometry;
1639
+ const vertexCount = geometry.getAttribute("position").count;
1640
+ const slotArray = new Float32Array(vertexCount).fill(slotInfo.slotIndex);
1641
+ geometry.setAttribute(
1642
+ slotAttributeName,
1643
+ new BufferAttribute(slotArray, 1)
1644
+ );
1645
+ mesh.material = group.material;
1646
+ }
1647
+ const meshesToRemove = [];
1648
+ for (const meshes of materialMeshMap.values()) {
1649
+ for (const mesh of meshes) {
1650
+ if (!excludedMeshes.has(mesh)) {
1651
+ meshesToRemove.push(mesh);
1652
+ }
1653
+ }
1654
+ }
1655
+ const firstMeshParent = meshesToRemove[0]?.parent || rootNode;
1656
+ meshesToRemove.forEach((mesh) => mesh.parent?.remove(mesh));
1657
+ for (const mesh of meshesToRemove) {
1658
+ deleteMesh(mesh);
1659
+ }
1660
+ for (const group of combineResult.groups.values()) {
1661
+ if (group.outlineMesh) {
1662
+ firstMeshParent.add(group.outlineMesh);
1663
+ }
1664
+ if (group.mesh) {
1665
+ firstMeshParent.add(group.mesh);
1666
+ }
1667
+ }
1668
+ if (options.migrateVRM0ToVRM1) {
1669
+ const springBoneManager = vrm.springBoneManager;
1670
+ vrm.springBoneManager = null;
1671
+ springBoneManager?.reset();
1672
+ const humanoidBones = /* @__PURE__ */ new Set();
1673
+ if (vrm.humanoid) {
1674
+ Object.values(vrm.humanoid.humanBones).forEach((humanBone) => {
1675
+ const node = humanBone.node;
1676
+ if (node instanceof Bone) {
1677
+ humanoidBones.add(node);
1678
+ }
1679
+ });
1680
+ }
1681
+ yield* migrateSkeletonVRM0ToVRM1(rootNode, { humanoidBones });
1682
+ vrm.springBoneManager = springBoneManager;
1683
+ migrateSpringBone(vrm);
1684
+ }
1685
+ if (simplifyStats) {
1686
+ combineResult.statistics.simplify = simplifyStats;
1687
+ }
1688
+ return ok(combineResult);
1689
+ });
1690
+ }
1691
+ var VRMExporterPlugin = class {
1692
+ constructor(writer) {
1693
+ this.name = "VRMC_vrm";
1694
+ this.vrm = null;
1695
+ // 動的に作成されたtailノードを追跡(エクスポート後にクリーンアップするため)
1696
+ this.createdTailNodes = [];
1697
+ this.writer = writer;
1698
+ }
1699
+ setVRM(vrm) {
1700
+ this.vrm = vrm;
1701
+ }
1702
+ beforeParse(input) {
1703
+ const root = Array.isArray(input) ? input[0] : input;
1704
+ if (!root) return;
1705
+ root.traverse((obj) => {
1706
+ if (obj.userData && obj.userData.vrm && !this.vrm) {
1707
+ this.vrm = obj.userData.vrm;
1708
+ }
1709
+ });
1710
+ this.createVirtualTailNodes();
1711
+ }
1712
+ /**
1713
+ * SpringBone末端ジョイントに仮想tailノードを作成
1714
+ * three-vrmと同様に、ボーン方向に7cmのオフセットを持つ仮想ノードを追加
1715
+ */
1716
+ createVirtualTailNodes() {
1717
+ if (!this.vrm?.springBoneManager) return;
1718
+ const springBoneManager = this.vrm.springBoneManager;
1719
+ const joints = springBoneManager.joints;
1720
+ if (!joints || joints.size === 0) return;
1721
+ const jointsByBone = /* @__PURE__ */ new Map();
1722
+ joints.forEach((joint) => {
1723
+ jointsByBone.set(joint.bone, joint);
1724
+ });
1725
+ joints.forEach((joint) => {
1726
+ const bone = joint.bone;
1727
+ if (!bone) return;
1728
+ const hasJointChild = joint.child != null;
1729
+ const hasBoneChild = bone.children.some(
1730
+ (child) => child.type === "Bone" || child.isBone
1731
+ );
1732
+ if (!hasJointChild && !hasBoneChild) {
1733
+ const tailBone = new Bone();
1734
+ tailBone.name = `${bone.name}_tail`;
1735
+ const direction = new Vector3().copy(bone.position);
1736
+ if (direction.lengthSq() > 0) {
1737
+ direction.normalize().multiplyScalar(0.07);
1738
+ } else {
1739
+ direction.set(0, 0.07, 0);
1740
+ }
1741
+ tailBone.position.copy(direction);
1742
+ bone.add(tailBone);
1743
+ tailBone.updateMatrixWorld(true);
1744
+ this.createdTailNodes.push(tailBone);
1745
+ }
1746
+ });
1747
+ }
1748
+ /**
1749
+ * エクスポート後に作成した仮想tailノードをクリーンアップ
1750
+ */
1751
+ cleanupTailNodes() {
1752
+ for (const tailNode of this.createdTailNodes) {
1753
+ if (tailNode.parent) {
1754
+ tailNode.parent.remove(tailNode);
1755
+ }
1756
+ }
1757
+ this.createdTailNodes = [];
1758
+ }
1759
+ afterParse(_input) {
1760
+ if (!this.vrm) {
1761
+ return;
1762
+ }
1763
+ const json = this.writer.json;
1764
+ const vrm = this.vrm;
1765
+ json.extensions = json.extensions || {};
1766
+ json.extensions.VRMC_vrm = {
1767
+ specVersion: "1.0",
1768
+ meta: this.exportMeta(vrm),
1769
+ humanoid: this.exportHumanoid(vrm),
1770
+ expressions: this.exportExpressions(vrm),
1771
+ lookAt: this.exportLookAt(vrm),
1772
+ firstPerson: this.exportFirstPerson(vrm)
1773
+ };
1774
+ json.extensionsUsed = json.extensionsUsed || [];
1775
+ if (!json.extensionsUsed.includes("VRMC_vrm")) {
1776
+ json.extensionsUsed.push("VRMC_vrm");
1777
+ }
1778
+ const springBoneExtension = this.exportSpringBone(vrm);
1779
+ if (springBoneExtension) {
1780
+ json.extensions.VRMC_springBone = springBoneExtension;
1781
+ if (!json.extensionsUsed.includes("VRMC_springBone")) {
1782
+ json.extensionsUsed.push("VRMC_springBone");
1783
+ }
1784
+ }
1785
+ }
1786
+ exportMeta(vrm) {
1787
+ if (!vrm.meta) return void 0;
1788
+ const meta = vrm.meta;
1789
+ let licenseUrl = meta.licenseUrl;
1790
+ if (!licenseUrl && meta.licenseName) {
1791
+ const licenseMapping = {
1792
+ Redistribution_Prohibited: "https://vrm.dev/licenses/1.0/",
1793
+ CC0: "https://creativecommons.org/publicdomain/zero/1.0/",
1794
+ CC_BY: "https://creativecommons.org/licenses/by/4.0/",
1795
+ CC_BY_NC: "https://creativecommons.org/licenses/by-nc/4.0/",
1796
+ CC_BY_SA: "https://creativecommons.org/licenses/by-sa/4.0/",
1797
+ CC_BY_NC_SA: "https://creativecommons.org/licenses/by-nc-sa/4.0/",
1798
+ CC_BY_ND: "https://creativecommons.org/licenses/by-nd/4.0/",
1799
+ CC_BY_NC_ND: "https://creativecommons.org/licenses/by-nc-nd/4.0/",
1800
+ Other: "https://vrm.dev/licenses/1.0/"
1801
+ };
1802
+ licenseUrl = licenseMapping[meta.licenseName] ?? "https://vrm.dev/licenses/1.0/";
1803
+ }
1804
+ if (!licenseUrl) {
1805
+ licenseUrl = "https://vrm.dev/licenses/1.0/";
1806
+ }
1807
+ return {
1808
+ name: meta.name ?? meta.title,
1809
+ version: meta.version,
1810
+ authors: meta.authors ?? (meta.author ? [meta.author] : void 0),
1811
+ copyrightInformation: meta.copyrightInformation,
1812
+ contactInformation: meta.contactInformation,
1813
+ references: meta.references ?? (meta.reference ? [meta.reference] : void 0),
1814
+ thirdPartyLicenses: meta.thirdPartyLicenses,
1815
+ thumbnailImage: meta.thumbnailImage ? this.writer.processTexture(meta.thumbnailImage) : void 0,
1816
+ licenseUrl,
1817
+ avatarPermission: meta.avatarPermission ?? "onlyAuthor",
1818
+ allowExcessivelyViolentUsage: meta.allowExcessivelyViolentUsage ?? false,
1819
+ allowExcessivelySexualUsage: meta.allowExcessivelySexualUsage ?? false,
1820
+ commercialUsage: meta.commercialUsage ?? "personalNonProfit",
1821
+ allowPoliticalOrReligiousUsage: meta.allowPoliticalOrReligiousUsage ?? false,
1822
+ allowAntisocialOrHateUsage: meta.allowAntisocialOrHateUsage ?? false,
1823
+ creditNotation: meta.creditNotation ?? "required",
1824
+ allowRedistribution: meta.allowRedistribution ?? false,
1825
+ modification: meta.modification ?? "prohibited"
1826
+ };
1827
+ }
1828
+ exportHumanoid(vrm) {
1829
+ if (!vrm.humanoid) return void 0;
1830
+ const humanBones = {};
1831
+ Object.entries(vrm.humanoid.humanBones).forEach(([name, bone]) => {
1832
+ if (bone && bone.node) {
1833
+ const nodeIndex = this.writer.nodeMap.get(bone.node);
1834
+ if (nodeIndex !== void 0) {
1835
+ humanBones[name] = { node: nodeIndex };
1836
+ }
1837
+ }
1838
+ });
1839
+ return {
1840
+ humanBones
1841
+ };
1842
+ }
1843
+ exportExpressions(vrm) {
1844
+ if (!vrm.expressionManager) return void 0;
1845
+ const presetNames = /* @__PURE__ */ new Set([
1846
+ "happy",
1847
+ "angry",
1848
+ "sad",
1849
+ "relaxed",
1850
+ "surprised",
1851
+ "aa",
1852
+ "ih",
1853
+ "ou",
1854
+ "ee",
1855
+ "oh",
1856
+ "blink",
1857
+ "blinkLeft",
1858
+ "blinkRight",
1859
+ "lookUp",
1860
+ "lookDown",
1861
+ "lookLeft",
1862
+ "lookRight",
1863
+ "neutral"
1864
+ ]);
1865
+ const preset = {};
1866
+ const custom = {};
1867
+ if (vrm.expressionManager.expressions) {
1868
+ vrm.expressionManager.expressions.forEach((expression) => {
1869
+ const expr = expression;
1870
+ const exprDef = {};
1871
+ if (expr.isBinary) exprDef.isBinary = expr.isBinary;
1872
+ if (expr.overrideBlink && expr.overrideBlink !== "none")
1873
+ exprDef.overrideBlink = expr.overrideBlink;
1874
+ if (expr.overrideLookAt && expr.overrideLookAt !== "none")
1875
+ exprDef.overrideLookAt = expr.overrideLookAt;
1876
+ if (expr.overrideMouth && expr.overrideMouth !== "none")
1877
+ exprDef.overrideMouth = expr.overrideMouth;
1878
+ const binds = expr._binds || expr.binds || [];
1879
+ const morphTargetBinds = binds.filter(
1880
+ (b) => b.type === "morphTarget" || b.primitives
1881
+ );
1882
+ if (morphTargetBinds.length > 0) {
1883
+ const allBinds = [];
1884
+ morphTargetBinds.forEach((bind) => {
1885
+ const primitives = bind.primitives || [];
1886
+ primitives.forEach((mesh) => {
1887
+ const nodeIndex = this.writer.nodeMap.get(mesh);
1888
+ if (nodeIndex !== void 0) {
1889
+ allBinds.push({
1890
+ node: nodeIndex,
1891
+ index: bind.index,
1892
+ weight: bind.weight
1893
+ });
1894
+ }
1895
+ });
1896
+ });
1897
+ if (allBinds.length > 0) {
1898
+ exprDef.morphTargetBinds = allBinds;
1899
+ }
1900
+ }
1901
+ if (expr.materialColorBinds && expr.materialColorBinds.length > 0) {
1902
+ exprDef.materialColorBinds = expr.materialColorBinds.map(
1903
+ (bind) => {
1904
+ const materialIndex = this.writer.processMaterial(bind.material);
1905
+ return {
1906
+ material: materialIndex,
1907
+ type: bind.type,
1908
+ targetValue: bind.targetValue.toArray()
1909
+ };
1910
+ }
1911
+ );
1912
+ }
1913
+ if (expr.textureTransformBinds && expr.textureTransformBinds.length > 0) {
1914
+ exprDef.textureTransformBinds = expr.textureTransformBinds.map(
1915
+ (bind) => {
1916
+ const materialIndex = this.writer.processMaterial(bind.material);
1917
+ return {
1918
+ material: materialIndex,
1919
+ scale: bind.scale.toArray(),
1920
+ offset: bind.offset.toArray()
1921
+ };
1922
+ }
1923
+ );
1924
+ }
1925
+ const name = expr.expressionName;
1926
+ if (presetNames.has(name)) {
1927
+ preset[name] = exprDef;
1928
+ } else {
1929
+ custom[name] = exprDef;
1930
+ }
1931
+ });
1932
+ }
1933
+ const result = { preset };
1934
+ if (Object.keys(custom).length > 0) {
1935
+ result.custom = custom;
1936
+ }
1937
+ return result;
1938
+ }
1939
+ exportLookAt(vrm) {
1940
+ if (!vrm.lookAt) return void 0;
1941
+ const lookAt = vrm.lookAt;
1942
+ const defaultRangeMap = { inputMaxValue: 90, outputScale: 10 };
1943
+ return {
1944
+ offsetFromHeadBone: lookAt.offsetFromHeadBone?.toArray?.() ?? [0, 0, 0],
1945
+ type: lookAt.applier?.type ?? "bone",
1946
+ // 'bone' or 'expression'
1947
+ rangeMapHorizontalInner: lookAt.rangeMapHorizontalInner ? {
1948
+ inputMaxValue: lookAt.rangeMapHorizontalInner.inputMaxValue,
1949
+ outputScale: lookAt.rangeMapHorizontalInner.outputScale
1950
+ } : defaultRangeMap,
1951
+ rangeMapHorizontalOuter: lookAt.rangeMapHorizontalOuter ? {
1952
+ inputMaxValue: lookAt.rangeMapHorizontalOuter.inputMaxValue,
1953
+ outputScale: lookAt.rangeMapHorizontalOuter.outputScale
1954
+ } : defaultRangeMap,
1955
+ rangeMapVerticalDown: lookAt.rangeMapVerticalDown ? {
1956
+ inputMaxValue: lookAt.rangeMapVerticalDown.inputMaxValue,
1957
+ outputScale: lookAt.rangeMapVerticalDown.outputScale
1958
+ } : defaultRangeMap,
1959
+ rangeMapVerticalUp: lookAt.rangeMapVerticalUp ? {
1960
+ inputMaxValue: lookAt.rangeMapVerticalUp.inputMaxValue,
1961
+ outputScale: lookAt.rangeMapVerticalUp.outputScale
1962
+ } : defaultRangeMap
1963
+ };
1964
+ }
1965
+ exportFirstPerson(vrm) {
1966
+ if (!vrm.firstPerson) return void 0;
1967
+ const meshAnnotations = [];
1968
+ if (vrm.firstPerson.meshAnnotations) {
1969
+ vrm.firstPerson.meshAnnotations.forEach((annotation) => {
1970
+ const ann = annotation;
1971
+ const mesh = ann.mesh || ann.node;
1972
+ const nodeIndex = this.writer.nodeMap.get(mesh);
1973
+ if (nodeIndex !== void 0) {
1974
+ meshAnnotations.push({
1975
+ node: nodeIndex,
1976
+ type: ann.type
1977
+ });
1978
+ }
1979
+ });
1980
+ }
1981
+ return {
1982
+ meshAnnotations
1983
+ };
1984
+ }
1985
+ /**
1986
+ * VRMC_springBone 拡張をエクスポート
1987
+ * three-vrm の VRMSpringBoneManager から SpringBone データを抽出
1988
+ */
1989
+ exportSpringBone(vrm) {
1990
+ const springBoneManager = vrm.springBoneManager;
1991
+ if (!springBoneManager) return void 0;
1992
+ const joints = springBoneManager.joints;
1993
+ if (!joints || joints.size === 0) return void 0;
1994
+ const colliders = springBoneManager.colliders;
1995
+ const colliderIndexMap = /* @__PURE__ */ new Map();
1996
+ const colliderDefs = [];
1997
+ colliders.forEach((collider) => {
1998
+ const nodeIndex = this.writer.nodeMap.get(collider);
1999
+ if (nodeIndex === void 0) return;
2000
+ const shape = collider.shape;
2001
+ let shapeDef;
2002
+ if (shape.type === "sphere") {
2003
+ shapeDef = {
2004
+ sphere: {
2005
+ offset: shape.offset ? [shape.offset.x, shape.offset.y, shape.offset.z] : [0, 0, 0],
2006
+ radius: shape.radius ?? 0
2007
+ }
2008
+ };
2009
+ } else if (shape.type === "capsule") {
2010
+ shapeDef = {
2011
+ capsule: {
2012
+ offset: shape.offset ? [shape.offset.x, shape.offset.y, shape.offset.z] : [0, 0, 0],
2013
+ radius: shape.radius ?? 0,
2014
+ tail: shape.tail ? [shape.tail.x, shape.tail.y, shape.tail.z] : [0, 0, 0]
2015
+ }
2016
+ };
2017
+ } else {
2018
+ return;
2019
+ }
2020
+ const colliderIndex = colliderDefs.length;
2021
+ colliderIndexMap.set(collider, colliderIndex);
2022
+ colliderDefs.push({
2023
+ node: nodeIndex,
2024
+ shape: shapeDef
2025
+ });
2026
+ });
2027
+ const colliderGroups = springBoneManager.colliderGroups;
2028
+ const colliderGroupIndexMap = /* @__PURE__ */ new Map();
2029
+ const colliderGroupDefs = [];
2030
+ colliderGroups.forEach((group) => {
2031
+ const colliderIndices = [];
2032
+ group.colliders.forEach((collider) => {
2033
+ const index = colliderIndexMap.get(collider);
2034
+ if (index !== void 0) {
2035
+ colliderIndices.push(index);
2036
+ }
2037
+ });
2038
+ if (colliderIndices.length > 0) {
2039
+ const groupIndex = colliderGroupDefs.length;
2040
+ colliderGroupIndexMap.set(group, groupIndex);
2041
+ colliderGroupDefs.push({
2042
+ name: group.name,
2043
+ colliders: colliderIndices
2044
+ });
2045
+ }
2046
+ });
2047
+ const springDefs = [];
2048
+ const processedJoints = /* @__PURE__ */ new Set();
2049
+ const jointsByBone = /* @__PURE__ */ new Map();
2050
+ joints.forEach((joint) => {
2051
+ jointsByBone.set(joint.bone, joint);
2052
+ });
2053
+ joints.forEach((joint) => {
2054
+ if (processedJoints.has(joint)) return;
2055
+ const parentBone = joint.bone.parent;
2056
+ if (parentBone && jointsByBone.has(parentBone)) {
2057
+ return;
2058
+ }
2059
+ const chainJoints = [];
2060
+ let currentJoint = joint;
2061
+ while (currentJoint && !processedJoints.has(currentJoint)) {
2062
+ processedJoints.add(currentJoint);
2063
+ chainJoints.push(currentJoint);
2064
+ const childBone = currentJoint.child;
2065
+ currentJoint = childBone ? jointsByBone.get(childBone) : null;
2066
+ }
2067
+ if (chainJoints.length === 0) return;
2068
+ const jointDefs = [];
2069
+ for (let i = 0; i < chainJoints.length; i++) {
2070
+ const j = chainJoints[i];
2071
+ const nodeIndex = this.writer.nodeMap.get(j.bone);
2072
+ if (nodeIndex === void 0) continue;
2073
+ jointDefs.push({
2074
+ node: nodeIndex,
2075
+ hitRadius: j.settings.hitRadius,
2076
+ stiffness: j.settings.stiffness,
2077
+ gravityPower: j.settings.gravityPower,
2078
+ gravityDir: j.settings.gravityDir ? [
2079
+ j.settings.gravityDir.x,
2080
+ j.settings.gravityDir.y,
2081
+ j.settings.gravityDir.z
2082
+ ] : [0, -1, 0],
2083
+ dragForce: j.settings.dragForce
2084
+ });
2085
+ }
2086
+ const lastJoint = chainJoints[chainJoints.length - 1];
2087
+ let tailNode = lastJoint?.child;
2088
+ if (!tailNode && lastJoint?.bone) {
2089
+ const boneChildren = lastJoint.bone.children.filter(
2090
+ (child) => child.type === "Bone" || child.isBone
2091
+ );
2092
+ if (boneChildren.length > 0) {
2093
+ tailNode = boneChildren[0];
2094
+ }
2095
+ }
2096
+ if (tailNode) {
2097
+ const tailNodeIndex = this.writer.nodeMap.get(tailNode);
2098
+ if (tailNodeIndex !== void 0) {
2099
+ jointDefs.push({
2100
+ node: tailNodeIndex
2101
+ });
2102
+ }
2103
+ }
2104
+ if (jointDefs.length === 0) return;
2105
+ const colliderGroupIndices = [];
2106
+ const firstJoint = chainJoints[0];
2107
+ if (firstJoint.colliderGroups) {
2108
+ firstJoint.colliderGroups.forEach((group) => {
2109
+ const index = colliderGroupIndexMap.get(group);
2110
+ if (index !== void 0 && !colliderGroupIndices.includes(index)) {
2111
+ colliderGroupIndices.push(index);
2112
+ }
2113
+ });
2114
+ }
2115
+ let centerNodeIndex;
2116
+ if (firstJoint.center) {
2117
+ centerNodeIndex = this.writer.nodeMap.get(firstJoint.center);
2118
+ }
2119
+ const springDef = {
2120
+ joints: jointDefs
2121
+ };
2122
+ if (centerNodeIndex !== void 0) {
2123
+ springDef.center = centerNodeIndex;
2124
+ }
2125
+ if (colliderGroupIndices.length > 0) {
2126
+ springDef.colliderGroups = colliderGroupIndices;
2127
+ }
2128
+ springDefs.push(springDef);
2129
+ });
2130
+ if (springDefs.length === 0 && colliderDefs.length === 0) {
2131
+ return void 0;
2132
+ }
2133
+ const result = {
2134
+ specVersion: "1.0"
2135
+ };
2136
+ if (colliderDefs.length > 0) {
2137
+ result.colliders = colliderDefs;
2138
+ }
2139
+ if (colliderGroupDefs.length > 0) {
2140
+ result.colliderGroups = colliderGroupDefs;
2141
+ }
2142
+ if (springDefs.length > 0) {
2143
+ result.springs = springDefs;
2144
+ }
2145
+ return result;
2146
+ }
2147
+ };
2148
+
2149
+ // src/io/export.ts
2150
+ function exportVRM(vrm, options = {}) {
2151
+ const { binary = true, textureCompression } = options;
2152
+ const initPromise = textureCompression ? initBasisEncoder().map(() => void 0).mapErr((err10) => ({
2153
+ type: "EXPORT_FAILED",
2154
+ message: `Basis WASM \u521D\u671F\u5316\u306B\u5931\u6557: ${err10.message}`
2155
+ })) : okAsync(void 0);
2156
+ return initPromise.andThen(
2157
+ () => ResultAsync.fromPromise(
2158
+ new Promise((resolve, reject) => {
2159
+ vrm.springBoneManager?.reset();
2160
+ vrm.springBoneManager?.setInitState();
2161
+ const exporter = new GLTFExporter();
2162
+ exporter.register((writer) => {
2163
+ const plugin = new MToonAtlasExporterPlugin(writer);
2164
+ if (textureCompression) {
2165
+ plugin.setTextureCompressionOptions(textureCompression);
2166
+ }
2167
+ return plugin;
2168
+ });
2169
+ exporter.register((writer) => {
2170
+ const plugin = new VRMExporterPlugin(writer);
2171
+ plugin.setVRM(vrm);
2172
+ return plugin;
2173
+ });
2174
+ const exportScene = new Scene();
2175
+ const children = [...vrm.scene.children].filter(
2176
+ (child) => child.name !== "VRMHumanoidRig" && !child.name.startsWith("VRMExpression")
2177
+ );
2178
+ children.forEach((child) => exportScene.add(child));
2179
+ exporter.parse(
2180
+ exportScene,
2181
+ (result) => {
2182
+ children.forEach((child) => vrm.scene.add(child));
2183
+ try {
2184
+ if (result instanceof ArrayBuffer) {
2185
+ resolve(result);
2186
+ } else {
2187
+ const jsonString = JSON.stringify(result);
2188
+ const encoder = new TextEncoder();
2189
+ resolve(encoder.encode(jsonString).buffer);
2190
+ }
2191
+ } catch (err10) {
2192
+ reject(err10);
2193
+ }
2194
+ },
2195
+ (error) => {
2196
+ children.forEach((child) => vrm.scene.add(child));
2197
+ reject(error);
2198
+ },
2199
+ {
2200
+ binary,
2201
+ trs: false,
2202
+ onlyVisible: true
2203
+ }
2204
+ );
2205
+ }),
2206
+ (error) => ({
2207
+ type: "EXPORT_FAILED",
2208
+ message: `Failed to export VRM: ${String(error)}`
2209
+ })
2210
+ )
2211
+ );
2212
+ }
2213
+ var ktx2LoaderInstance = null;
2214
+ function getKTX2Loader() {
2215
+ if (ktx2LoaderInstance) {
2216
+ return ktx2LoaderInstance;
2217
+ }
2218
+ if (typeof document === "undefined" || typeof WebGLRenderingContext === "undefined") {
2219
+ return null;
2220
+ }
2221
+ try {
2222
+ const canvas = document.createElement("canvas");
2223
+ const gl = canvas.getContext("webgl2");
2224
+ if (!gl) {
2225
+ console.warn(
2226
+ "WebGL2 \u304C\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002KTX2 \u30C6\u30AF\u30B9\u30C1\u30E3\u306F\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3002"
2227
+ );
2228
+ return null;
2229
+ }
2230
+ const renderer = new WebGLRenderer({
2231
+ canvas,
2232
+ context: gl
2233
+ });
2234
+ ktx2LoaderInstance = new KTX2Loader();
2235
+ ktx2LoaderInstance.setTranscoderPath(
2236
+ "https://cdn.jsdelivr.net/npm/three@0.175.0/examples/jsm/libs/basis/"
2237
+ );
2238
+ ktx2LoaderInstance.detectSupport(renderer);
2239
+ console.log("KTX2Loader \u521D\u671F\u5316\u5B8C\u4E86");
2240
+ renderer.dispose();
2241
+ return ktx2LoaderInstance;
2242
+ } catch (error) {
2243
+ console.warn(
2244
+ "KTX2Loader \u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002KTX2 \u30C6\u30AF\u30B9\u30C1\u30E3\u306F\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3002",
2245
+ error
2246
+ );
2247
+ return null;
2248
+ }
2249
+ }
2250
+ function loadVRM(source) {
2251
+ return ResultAsync.fromPromise(
2252
+ (async () => {
2253
+ const loader = new GLTFLoader();
2254
+ const ktx2Loader = getKTX2Loader();
2255
+ if (ktx2Loader) {
2256
+ loader.setKTX2Loader(ktx2Loader);
2257
+ }
2258
+ loader.register((parser) => new VRMLoaderPlugin(parser));
2259
+ loader.register((parser) => new MToonAtlasLoaderPlugin(parser));
2260
+ let gltf;
2261
+ let blobUrl = null;
2262
+ try {
2263
+ if (typeof source === "string") {
2264
+ gltf = await loader.loadAsync(source);
2265
+ } else if (source instanceof ArrayBuffer) {
2266
+ const blob = new Blob([source], { type: "model/gltf-binary" });
2267
+ blobUrl = URL.createObjectURL(blob);
2268
+ gltf = await loader.loadAsync(blobUrl);
2269
+ } else {
2270
+ blobUrl = URL.createObjectURL(source);
2271
+ gltf = await loader.loadAsync(blobUrl);
2272
+ }
2273
+ const vrm = gltf.userData.vrm;
2274
+ if (!vrm) {
2275
+ throw new Error("VRM data not found in loaded file");
2276
+ }
2277
+ return vrm;
2278
+ } finally {
2279
+ if (blobUrl) {
2280
+ URL.revokeObjectURL(blobUrl);
2281
+ }
2282
+ }
2283
+ })(),
2284
+ (error) => ({
2285
+ type: "VRM_LOAD_FAILED",
2286
+ message: `Failed to load VRM: ${String(error)}`
2287
+ })
2288
+ );
2289
+ }
2290
+
2291
+ export { VRMExporterPlugin, createVirtualTailNodes, exportVRM, loadVRM, migrateSkeletonVRM0ToVRM1, migrateSpringBone, optimizeModel, rotateSpringBoneColliderOffsets, rotateSpringBoneGravityDirections, simplifyMeshes };
2292
+ //# sourceMappingURL=index.js.map
2293
+ //# sourceMappingURL=index.js.map