@call-me-sensei/toonlab 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/ATTRIBUTION.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +125 -0
  4. package/package.json +97 -0
  5. package/src/character/characterRig.js +353 -0
  6. package/src/character/freestyleSwimClip.js +541 -0
  7. package/src/character/index.js +4 -0
  8. package/src/character/modelLoader.js +433 -0
  9. package/src/core/materialRoles.js +438 -0
  10. package/src/core/presetDocuments.js +110 -0
  11. package/src/core/shaderBackend.js +24 -0
  12. package/src/debrisgen/debrisFields.js +544 -0
  13. package/src/debrisgen/debrisGenerator.js +1929 -0
  14. package/src/debrisgen/debrisPalettes.js +71 -0
  15. package/src/debrisgen/debrisPhysics.js +198 -0
  16. package/src/debrisgen/debrisPresets.js +155 -0
  17. package/src/debrisgen/debrisSettings.js +333 -0
  18. package/src/debrisgen/debrisTextures.js +380 -0
  19. package/src/debrisgen/index.js +5 -0
  20. package/src/debug/fieldValues.js +95 -0
  21. package/src/debug/index.js +7 -0
  22. package/src/debug/settingsPanel.js +170 -0
  23. package/src/environment/environmentAmbientProbe.js +160 -0
  24. package/src/environment/environmentMaterialAdapter.js +336 -0
  25. package/src/environment/environmentMaterialClassifier.js +178 -0
  26. package/src/environment/environmentPlanarReflection.js +201 -0
  27. package/src/environment/environmentPresets.js +443 -0
  28. package/src/environment/environmentRigs.js +684 -0
  29. package/src/environment/environmentSettings.js +504 -0
  30. package/src/environment/environmentShaderMaterials.js +318 -0
  31. package/src/environment/environmentSunShadowPass.js +268 -0
  32. package/src/environment/environmentTextureResolver.js +161 -0
  33. package/src/environment/environmentTimeOfDay.js +167 -0
  34. package/src/environment/environmentVertexAo.js +188 -0
  35. package/src/environment/index.js +8 -0
  36. package/src/environment/scanAssetStylize.js +103 -0
  37. package/src/index.js +21 -0
  38. package/src/loaders/index.js +1 -0
  39. package/src/post/index.js +2 -0
  40. package/src/post/postProcessing.js +1087 -0
  41. package/src/rockgen/export/glbExport.js +177 -0
  42. package/src/rockgen/heightfield/heightfieldErosion.js +7 -0
  43. package/src/rockgen/heightfield/heightfieldPatch.js +183 -0
  44. package/src/rockgen/heightfield/stylizedErosionSim.js +356 -0
  45. package/src/rockgen/index.js +14 -0
  46. package/src/rockgen/mesh/meshAttributes.js +374 -0
  47. package/src/rockgen/mesh/meshDocument.js +158 -0
  48. package/src/rockgen/mesh/surfaceNets.js +329 -0
  49. package/src/rockgen/noise/cellularNoise3.js +92 -0
  50. package/src/rockgen/noise/prng.js +62 -0
  51. package/src/rockgen/noise/simplexNoise3.js +89 -0
  52. package/src/rockgen/noise/valueNoise3.js +87 -0
  53. package/src/rockgen/rockDocument.js +255 -0
  54. package/src/rockgen/rockHelpers.js +8 -0
  55. package/src/rockgen/rockgenPresets.js +559 -0
  56. package/src/rockgen/rockgenSettings.js +756 -0
  57. package/src/rockgen/sdf/fieldCompiler.js +544 -0
  58. package/src/rockgen/sdf/sculptEdits.js +66 -0
  59. package/src/rockgen/sdf/sdfModifiers.js +45 -0
  60. package/src/rockgen/sdf/sdfOps.js +37 -0
  61. package/src/rockgen/sdf/sdfPrimitives.js +88 -0
  62. package/src/shaders-tsl/anime.js +963 -0
  63. package/src/shaders-tsl/chunks/character-color.js +64 -0
  64. package/src/shaders-tsl/chunks/character-highlights.js +174 -0
  65. package/src/shaders-tsl/chunks/character-lighting.js +309 -0
  66. package/src/shaders-tsl/chunks/character-material-maps.js +146 -0
  67. package/src/shaders-tsl/chunks/character-roles.js +52 -0
  68. package/src/shaders-tsl/chunks/character-scene-lights.js +270 -0
  69. package/src/shaders-tsl/chunks/character-shadow-color.js +61 -0
  70. package/src/shaders-tsl/chunks/character-skinning.js +140 -0
  71. package/src/shaders-tsl/chunks/environment-color.js +47 -0
  72. package/src/shaders-tsl/chunks/environment-debug.js +79 -0
  73. package/src/shaders-tsl/chunks/environment-lighting.js +260 -0
  74. package/src/shaders-tsl/chunks/environment-sun-shadow.js +87 -0
  75. package/src/shaders-tsl/chunks/foliage-fog.js +60 -0
  76. package/src/shaders-tsl/chunks/pass-depth-color.js +89 -0
  77. package/src/shaders-tsl/chunks/stylized-cloud-shadow.js +70 -0
  78. package/src/shaders-tsl/chunks/water-color.js +190 -0
  79. package/src/shaders-tsl/chunks/water-common.js +124 -0
  80. package/src/shaders-tsl/chunks/water-foam.js +93 -0
  81. package/src/shaders-tsl/chunks/water-lighting.js +116 -0
  82. package/src/shaders-tsl/chunks/water-ripple.js +64 -0
  83. package/src/shaders-tsl/chunks/water-waves.js +100 -0
  84. package/src/shaders-tsl/environment-ao-overlay.js +60 -0
  85. package/src/shaders-tsl/environment.js +680 -0
  86. package/src/shaders-tsl/flower.js +267 -0
  87. package/src/shaders-tsl/grass.js +193 -0
  88. package/src/shaders-tsl/post-composite.js +461 -0
  89. package/src/shaders-tsl/sky.js +175 -0
  90. package/src/shaders-tsl/tree-leaf.js +264 -0
  91. package/src/shaders-tsl/water-breaker.js +423 -0
  92. package/src/shaders-tsl/water-kelp.js +171 -0
  93. package/src/shaders-tsl/water-rain.js +107 -0
  94. package/src/shaders-tsl/water-simulation.js +140 -0
  95. package/src/shaders-tsl/water-splash.js +298 -0
  96. package/src/shaders-tsl/water.js +509 -0
  97. package/src/sky/index.js +2 -0
  98. package/src/sky/stylizedSky.js +341 -0
  99. package/src/toon/characterRenderPasses.js +646 -0
  100. package/src/toon/index.js +4 -0
  101. package/src/toon/settings/alphaSettings.js +170 -0
  102. package/src/toon/settings/averageShadowSettings.js +181 -0
  103. package/src/toon/settings/baseTextureSettings.js +133 -0
  104. package/src/toon/settings/celShadeSettings.js +96 -0
  105. package/src/toon/settings/contactShadowSettings.js +114 -0
  106. package/src/toon/settings/eyeHighlightSettings.js +187 -0
  107. package/src/toon/settings/faceLightingSettings.js +135 -0
  108. package/src/toon/settings/furSettings.js +112 -0
  109. package/src/toon/settings/glitterSettings.js +102 -0
  110. package/src/toon/settings/hairHighlightSettings.js +325 -0
  111. package/src/toon/settings/indirectLightSettings.js +244 -0
  112. package/src/toon/settings/localLightSettings.js +176 -0
  113. package/src/toon/settings/materialMapSettings.js +299 -0
  114. package/src/toon/settings/outlineSettings.js +263 -0
  115. package/src/toon/settings/perspectiveRemovalSettings.js +60 -0
  116. package/src/toon/settings/rimLightSettings.js +268 -0
  117. package/src/toon/settings/sceneShadowSettings.js +147 -0
  118. package/src/toon/settings/selfShadowSettings.js +221 -0
  119. package/src/toon/settings/shadowColorSettings.js +238 -0
  120. package/src/toon/settings/skinToneSettings.js +150 -0
  121. package/src/toon/settings/specularSettings.js +314 -0
  122. package/src/toon/settings/stickerSettings.js +102 -0
  123. package/src/toon/toonMaterialAdapter.js +2235 -0
  124. package/src/toon/toonSettings.js +860 -0
  125. package/src/vegetation/flowerSpecies.js +260 -0
  126. package/src/vegetation/index.js +10 -0
  127. package/src/vegetation/stylizedBush.js +136 -0
  128. package/src/vegetation/stylizedFlower.js +213 -0
  129. package/src/vegetation/stylizedFlowers.js +297 -0
  130. package/src/vegetation/stylizedGrass.js +491 -0
  131. package/src/vegetation/stylizedTree.js +2449 -0
  132. package/src/vegetation/stylizedTreeFoliage.js +691 -0
  133. package/src/vegetation/treeExport.js +300 -0
  134. package/src/vegetation/treeRecipe.js +647 -0
  135. package/src/water/index.js +2 -0
  136. package/src/water/water.js +20 -0
  137. package/src/water/waterBreakerSystem.js +591 -0
  138. package/src/water/waterInteraction.js +193 -0
  139. package/src/water/waterMaterial.js +263 -0
  140. package/src/water/waterRain.js +136 -0
  141. package/src/water/waterRippleSimulation.js +227 -0
  142. package/src/water/waterScenePasses.js +415 -0
  143. package/src/water/waterSettings.js +1138 -0
  144. package/src/water/waterSplashSystem.js +251 -0
  145. package/src/water/waterSurface.js +475 -0
  146. package/src/water/waterVegetation.js +83 -0
@@ -0,0 +1,2235 @@
1
+ import * as THREE from 'three';
2
+
3
+ import {
4
+ createAnimeNodeMaterial,
5
+ syncToonSceneLights,
6
+ updateToonStorageSkinning,
7
+ } from '../shaders-tsl/anime.js';
8
+
9
+ // Node materials read scene lights from shared uniforms (see
10
+ // chunks/character-scene-lights.js) instead of three's classic light blocks;
11
+ // converted meshes mirror the light state right before they render. Both
12
+ // renderers invoke Object3D.onBeforeRender. Skinned meshes also refresh the
13
+ // storage-skinning bone buffer (WebGL2-backend path).
14
+ function toonNodeLightSync(renderer, scene, camera) {
15
+ syncToonSceneLights(scene, camera);
16
+ if (this.isSkinnedMesh) updateToonStorageSkinning(this);
17
+ }
18
+
19
+ import {
20
+ alphaTestForMaterial,
21
+ createAlphaSettings,
22
+ DEFAULT_ALPHA_SETTINGS,
23
+ materialAlphaDrawOrder,
24
+ resolveAlphaForMaterial,
25
+ sourceOpacity,
26
+ usesAlphaBlend,
27
+ usesAlphaCutout,
28
+ } from './settings/alphaSettings.js';
29
+ import {
30
+ createAverageShadowSettings,
31
+ DEFAULT_AVERAGE_SHADOW_SETTINGS,
32
+ resolveAverageShadowForMaterial,
33
+ } from './settings/averageShadowSettings.js';
34
+ import {
35
+ createBaseTextureSettings,
36
+ getSourceMaterialColor,
37
+ resolveBaseMapSaturation,
38
+ resolveBaseMaterialColor,
39
+ } from './settings/baseTextureSettings.js';
40
+ import {
41
+ CEL_SHADE_PRESETS,
42
+ createCelShadeSettings,
43
+ DEFAULT_CEL_SHADE_SETTINGS,
44
+ REFERENCE_CEL_SHADE_SETTINGS,
45
+ } from './settings/celShadeSettings.js';
46
+ import {
47
+ createContactShadowSettings,
48
+ DEFAULT_CONTACT_SHADOW_SETTINGS,
49
+ resolveContactShadowForMaterial,
50
+ } from './settings/contactShadowSettings.js';
51
+ import {
52
+ createFurSettings,
53
+ DEFAULT_FUR_SETTINGS,
54
+ materialUsesFur,
55
+ } from './settings/furSettings.js';
56
+ import {
57
+ createGlitterSettings,
58
+ DEFAULT_GLITTER_SETTINGS,
59
+ resolveGlitterForMaterial,
60
+ } from './settings/glitterSettings.js';
61
+ import {
62
+ createPerspectiveRemovalSettings,
63
+ DEFAULT_PERSPECTIVE_REMOVAL_SETTINGS,
64
+ } from './settings/perspectiveRemovalSettings.js';
65
+ import {
66
+ createStickerSettings,
67
+ DEFAULT_STICKER_SETTINGS,
68
+ resolveStickerForMaterial,
69
+ STICKER_BLEND_MODES,
70
+ } from './settings/stickerSettings.js';
71
+ import {
72
+ createFaceLightingSettings,
73
+ DEFAULT_FACE_LIGHTING_SETTINGS,
74
+ FACE_HEAD_SPACE_MODE_VALUES,
75
+ FACE_HEAD_SPACE_MODES,
76
+ } from './settings/faceLightingSettings.js';
77
+ import {
78
+ createEyeHighlightSettings,
79
+ DEFAULT_EYE_HIGHLIGHT_SETTINGS,
80
+ EYE_HIGHLIGHT_SOURCE_MASK_MODES,
81
+ resolveEyeHighlightForMaterial,
82
+ } from './settings/eyeHighlightSettings.js';
83
+ import {
84
+ createIndirectLightSettings,
85
+ DEFAULT_INDIRECT_LIGHT_SETTINGS,
86
+ resolveIndirectLightForMaterial,
87
+ } from './settings/indirectLightSettings.js';
88
+ import {
89
+ createHairHighlightSettings,
90
+ DEFAULT_HAIR_HIGHLIGHT_SETTINGS,
91
+ HAIR_HIGHLIGHT_MASK_CHANNELS,
92
+ HAIR_HIGHLIGHT_MODES,
93
+ HAIR_HIGHLIGHT_MODE_VALUES,
94
+ HAIR_HIGHLIGHT_SOURCE_MASK_MODES,
95
+ resolveHairHighlightForMaterial,
96
+ } from './settings/hairHighlightSettings.js';
97
+ import {
98
+ createLocalLightSettings,
99
+ DEFAULT_LOCAL_LIGHT_SETTINGS,
100
+ resolveLocalLightForMaterial,
101
+ } from './settings/localLightSettings.js';
102
+ import {
103
+ collectMaterialMapTextures,
104
+ createMaterialMapSettings,
105
+ DEFAULT_MATERIAL_MAP_SETTINGS,
106
+ resolveMaterialMapsForMaterial,
107
+ } from './settings/materialMapSettings.js';
108
+ import {
109
+ createOutlineSettings,
110
+ DEFAULT_OUTLINE_SETTINGS,
111
+ resolveOutlineForMaterial,
112
+ } from './settings/outlineSettings.js';
113
+ import {
114
+ createRimLightSettings,
115
+ DEFAULT_RIM_LIGHT_SETTINGS,
116
+ resolveRimLightForMaterial,
117
+ RIM_LIGHT_MODES,
118
+ } from './settings/rimLightSettings.js';
119
+ import {
120
+ createSpecularSettings,
121
+ DEFAULT_SPECULAR_SETTINGS,
122
+ resolveSpecularForMaterial,
123
+ } from './settings/specularSettings.js';
124
+ import {
125
+ createSkinToneSettings,
126
+ DEFAULT_SKIN_TONE_SETTINGS,
127
+ } from './settings/skinToneSettings.js';
128
+ import {
129
+ createToonSettings,
130
+ } from './toonSettings.js';
131
+ import {
132
+ createShadowColorSettings,
133
+ DEFAULT_SHADOW_COLOR_SETTINGS,
134
+ REFERENCE_SHADOW_COLOR_SETTINGS,
135
+ SHADOW_COLOR_PRESETS,
136
+ } from './settings/shadowColorSettings.js';
137
+ import {
138
+ createSceneShadowSettings,
139
+ DEFAULT_SCENE_SHADOW_SETTINGS,
140
+ resolveSceneShadowForMaterial,
141
+ } from './settings/sceneShadowSettings.js';
142
+ import {
143
+ createSelfShadowSettings,
144
+ DEFAULT_SELF_SHADOW_SETTINGS,
145
+ resolveSelfShadowForMaterial,
146
+ SELF_SHADOW_SOURCE_MODES,
147
+ } from './settings/selfShadowSettings.js';
148
+ import {
149
+ classifyMaterialRole,
150
+ MATERIAL_ROLES,
151
+ normalizeMaterialRoleOverrides,
152
+ roleIsEye,
153
+ roleIsEyeHighlight,
154
+ roleIsFace,
155
+ roleIsHair,
156
+ roleIsSkin,
157
+ roleIsTransparentOverlay,
158
+ } from '../core/materialRoles.js';
159
+
160
+ export {
161
+ alphaTestForMaterial,
162
+ createAlphaSettings,
163
+ DEFAULT_ALPHA_SETTINGS,
164
+ resolveAlphaForMaterial,
165
+ sourceOpacity,
166
+ usesAlphaBlend,
167
+ usesAlphaCutout,
168
+ } from './settings/alphaSettings.js';
169
+
170
+ export {
171
+ createAverageShadowSettings,
172
+ DEFAULT_AVERAGE_SHADOW_SETTINGS,
173
+ resolveAverageShadowForMaterial,
174
+ } from './settings/averageShadowSettings.js';
175
+
176
+ export {
177
+ BASE_TEXTURE_MATERIAL_COLOR_MODES,
178
+ BASE_TEXTURE_SATURATION_MODES,
179
+ createBaseTextureSettings,
180
+ DEFAULT_BASE_TEXTURE_SETTINGS,
181
+ } from './settings/baseTextureSettings.js';
182
+
183
+ export {
184
+ CEL_SHADE_PRESETS,
185
+ createCelShadeSettings,
186
+ DEFAULT_CEL_SHADE_SETTINGS,
187
+ REFERENCE_CEL_SHADE_SETTINGS,
188
+ } from './settings/celShadeSettings.js';
189
+
190
+ export {
191
+ createContactShadowSettings,
192
+ DEFAULT_CONTACT_SHADOW_SETTINGS,
193
+ resolveContactShadowForMaterial,
194
+ } from './settings/contactShadowSettings.js';
195
+
196
+ export {
197
+ createFaceLightingSettings,
198
+ DEFAULT_FACE_LIGHTING_SETTINGS,
199
+ FACE_HEAD_SPACE_MODES,
200
+ } from './settings/faceLightingSettings.js';
201
+
202
+ export {
203
+ createFurSettings,
204
+ DEFAULT_FUR_SETTINGS,
205
+ materialUsesFur,
206
+ } from './settings/furSettings.js';
207
+
208
+ export {
209
+ createGlitterSettings,
210
+ DEFAULT_GLITTER_SETTINGS,
211
+ resolveGlitterForMaterial,
212
+ } from './settings/glitterSettings.js';
213
+
214
+ export {
215
+ createPerspectiveRemovalSettings,
216
+ DEFAULT_PERSPECTIVE_REMOVAL_SETTINGS,
217
+ } from './settings/perspectiveRemovalSettings.js';
218
+
219
+ export {
220
+ createStickerSettings,
221
+ DEFAULT_STICKER_SETTINGS,
222
+ resolveStickerForMaterial,
223
+ STICKER_BLEND_MODES,
224
+ } from './settings/stickerSettings.js';
225
+
226
+ export {
227
+ createEyeHighlightSettings,
228
+ DEFAULT_EYE_HIGHLIGHT_SETTINGS,
229
+ EYE_HIGHLIGHT_MASK_CHANNELS,
230
+ EYE_HIGHLIGHT_SOURCE_MASK_MODES,
231
+ resolveEyeHighlightForMaterial,
232
+ } from './settings/eyeHighlightSettings.js';
233
+
234
+ export {
235
+ createIndirectLightSettings,
236
+ DEFAULT_INDIRECT_LIGHT_SETTINGS,
237
+ resolveIndirectLightForMaterial,
238
+ } from './settings/indirectLightSettings.js';
239
+
240
+ export {
241
+ createHairHighlightSettings,
242
+ DEFAULT_HAIR_HIGHLIGHT_SETTINGS,
243
+ HAIR_HIGHLIGHT_MASK_CHANNELS,
244
+ HAIR_HIGHLIGHT_MODES,
245
+ HAIR_HIGHLIGHT_MODE_VALUES,
246
+ HAIR_HIGHLIGHT_SOURCE_MASK_MODES,
247
+ resolveHairHighlightForMaterial,
248
+ } from './settings/hairHighlightSettings.js';
249
+
250
+ export {
251
+ createLocalLightSettings,
252
+ DEFAULT_LOCAL_LIGHT_SETTINGS,
253
+ resolveLocalLightForMaterial,
254
+ } from './settings/localLightSettings.js';
255
+
256
+ export {
257
+ createMaterialMapSettings,
258
+ DEFAULT_MATERIAL_MAP_SETTINGS,
259
+ } from './settings/materialMapSettings.js';
260
+
261
+ export {
262
+ createOutlineSettings,
263
+ DEFAULT_OUTLINE_SETTINGS,
264
+ resolveOutlineForMaterial,
265
+ } from './settings/outlineSettings.js';
266
+
267
+ export {
268
+ createRimLightSettings,
269
+ DEFAULT_RIM_LIGHT_SETTINGS,
270
+ resolveRimLightForMaterial,
271
+ RIM_LIGHT_MODES,
272
+ } from './settings/rimLightSettings.js';
273
+
274
+ export {
275
+ createSpecularSettings,
276
+ DEFAULT_SPECULAR_SETTINGS,
277
+ resolveSpecularForMaterial,
278
+ SPECULAR_DIRECTION_MODES,
279
+ SPECULAR_MASK_CHANNELS,
280
+ SPECULAR_SOURCE_MASK_MODES,
281
+ } from './settings/specularSettings.js';
282
+
283
+ export {
284
+ createSkinToneSettings,
285
+ DEFAULT_SKIN_TONE_SETTINGS,
286
+ } from './settings/skinToneSettings.js';
287
+
288
+ export {
289
+ createShadowColorSettings,
290
+ DEFAULT_SHADOW_COLOR_SETTINGS,
291
+ REFERENCE_SHADOW_COLOR_SETTINGS,
292
+ SHADOW_COLOR_PRESETS,
293
+ } from './settings/shadowColorSettings.js';
294
+
295
+ export {
296
+ createSceneShadowSettings,
297
+ DEFAULT_SCENE_SHADOW_SETTINGS,
298
+ resolveSceneShadowForMaterial,
299
+ } from './settings/sceneShadowSettings.js';
300
+
301
+ export {
302
+ createSelfShadowSettings,
303
+ DEFAULT_SELF_SHADOW_SETTINGS,
304
+ resolveSelfShadowForMaterial,
305
+ SELF_SHADOW_SOURCE_MODES,
306
+ } from './settings/selfShadowSettings.js';
307
+
308
+ export {
309
+ createToonPresetDocument,
310
+ createToonSettings,
311
+ getToonPresetDefinition,
312
+ getToonPresetIds,
313
+ getToonPresetMetadata,
314
+ getToonPresetOptions,
315
+ getToonSettingFieldSchema,
316
+ getToonSettingGroupMetadata,
317
+ normalizeToonPresetName,
318
+ parseToonPresetDocument,
319
+ registerToonPreset,
320
+ registerSerializedToonPreset,
321
+ sanitizeToonPresetSettings,
322
+ serializeToonPreset,
323
+ TOON_PRESET_DOCUMENT_TYPE,
324
+ TOON_PRESET_IDS,
325
+ TOON_PRESET_SCHEMA_VERSION,
326
+ TOON_SETTING_DEFAULTS,
327
+ TOON_SETTING_FIELD_SCHEMA,
328
+ TOON_SETTING_GROUP_METADATA,
329
+ TOON_SETTING_GROUPS,
330
+ validateToonPresetDocument,
331
+ } from './toonSettings.js';
332
+
333
+ export {
334
+ classifyMaterialRole,
335
+ materialRoleName,
336
+ materialRoleValue,
337
+ MATERIAL_ROLE_LABELS,
338
+ MATERIAL_ROLE_NAMES_BY_VALUE,
339
+ MATERIAL_ROLES,
340
+ normalizeMaterialRole,
341
+ normalizeMaterialRoleOverrides,
342
+ roleIsCatchlight,
343
+ roleIsEye,
344
+ roleIsEyeHighlight,
345
+ roleIsIris,
346
+ roleIsPupil,
347
+ roleIsSclera,
348
+ } from '../core/materialRoles.js';
349
+
350
+ export const TOON_DEBUG_OUTPUT_MODES = Object.freeze({
351
+ off: 0,
352
+ final: 0,
353
+ albedo: 1,
354
+ sourceAlbedo: 8,
355
+ rawAlbedo: 8,
356
+ source: 8,
357
+ raw: 8,
358
+ band: 2,
359
+ cel: 2,
360
+ shadow: 3,
361
+ sceneShadow: 3,
362
+ selfShadow: 9,
363
+ directVisibility: 10,
364
+ averageShadow: 10,
365
+ rim: 11,
366
+ rimLight: 11,
367
+ depthRim: 23,
368
+ contactShadow: 24,
369
+ specular: 12,
370
+ specularArea: 12,
371
+ specularMask: 12,
372
+ hairHighlight: 13,
373
+ eyeHighlight: 14,
374
+ normalMap: 15,
375
+ aoMap: 16,
376
+ emissiveMap: 17,
377
+ matcap: 18,
378
+ ramp: 19,
379
+ detailMap: 20,
380
+ roughnessMap: 21,
381
+ metalnessMap: 22,
382
+ shadowColor: 4,
383
+ lit: 5,
384
+ lighting: 5,
385
+ role: 6,
386
+ alpha: 7,
387
+ });
388
+
389
+ export const TOON_DEBUG_OUTPUT_LABELS = Object.freeze({
390
+ off: 'Off',
391
+ albedo: 'Albedo',
392
+ sourceAlbedo: 'Source Albedo',
393
+ band: 'Cel Band',
394
+ shadow: 'Scene Shadow',
395
+ selfShadow: 'Self Shadow',
396
+ directVisibility: 'Direct Visibility',
397
+ rim: 'Rim Light',
398
+ depthRim: 'Depth Rim',
399
+ contactShadow: 'Contact Shadow',
400
+ specular: 'Specular',
401
+ hairHighlight: 'Hair Highlight',
402
+ eyeHighlight: 'Eye Highlight',
403
+ normalMap: 'Normal Map',
404
+ aoMap: 'AO Map',
405
+ emissiveMap: 'Emissive Map',
406
+ matcap: 'MatCap',
407
+ ramp: 'Ramp',
408
+ detailMap: 'Detail Map',
409
+ roughnessMap: 'Roughness Map',
410
+ metalnessMap: 'Metalness Map',
411
+ shadowColor: 'Shadow Color',
412
+ lit: 'Lighting',
413
+ role: 'Material Role',
414
+ alpha: 'Alpha',
415
+ });
416
+
417
+ const fallbackWhiteTexture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
418
+ fallbackWhiteTexture.colorSpace = THREE.SRGBColorSpace;
419
+ fallbackWhiteTexture.needsUpdate = true;
420
+
421
+ const fallbackNormalTexture = new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1);
422
+ fallbackNormalTexture.colorSpace = THREE.NoColorSpace;
423
+ fallbackNormalTexture.needsUpdate = true;
424
+
425
+ const fallbackSkinTexture = new THREE.DataTexture(new Uint8Array([255, 222, 205, 255]), 1, 1);
426
+ fallbackSkinTexture.colorSpace = THREE.SRGBColorSpace;
427
+ fallbackSkinTexture.needsUpdate = true;
428
+
429
+ const COLOR_TEXTURE_KEYS = [
430
+ 'map',
431
+ 'emissiveMap',
432
+ 'envMap',
433
+ 'gradientMap',
434
+ 'matcap',
435
+ 'sheenColorMap',
436
+ 'specularColorMap',
437
+ ];
438
+
439
+ const DATA_TEXTURE_KEYS = [
440
+ 'alphaMap',
441
+ 'aoMap',
442
+ 'bumpMap',
443
+ 'displacementMap',
444
+ 'lightMap',
445
+ 'metalnessMap',
446
+ 'normalMap',
447
+ 'roughnessMap',
448
+ ];
449
+
450
+ const TOON_DEBUG_MODE_ALIASES = Object.freeze({
451
+ '': 'off',
452
+ none: 'off',
453
+ normal: 'off',
454
+ final: 'off',
455
+ off: 'off',
456
+ albedo: 'albedo',
457
+ base: 'albedo',
458
+ basecolor: 'albedo',
459
+ basemap: 'albedo',
460
+ raw: 'sourceAlbedo',
461
+ rawalbedo: 'sourceAlbedo',
462
+ source: 'sourceAlbedo',
463
+ sourcealbedo: 'sourceAlbedo',
464
+ sourcebase: 'sourceAlbedo',
465
+ sourcetexture: 'sourceAlbedo',
466
+ band: 'band',
467
+ cel: 'band',
468
+ celband: 'band',
469
+ shade: 'band',
470
+ shadow: 'shadow',
471
+ sceneshadow: 'shadow',
472
+ shadowvisibility: 'shadow',
473
+ visibility: 'shadow',
474
+ self: 'selfShadow',
475
+ selfshadow: 'selfShadow',
476
+ charactershadow: 'selfShadow',
477
+ characterselfshadow: 'selfShadow',
478
+ averageshadow: 'directVisibility',
479
+ direct: 'directVisibility',
480
+ directvisibility: 'directVisibility',
481
+ combinedvisibility: 'directVisibility',
482
+ finalvisibility: 'directVisibility',
483
+ lightvisibility: 'directVisibility',
484
+ rim: 'rim',
485
+ rimlight: 'rim',
486
+ fresnel: 'rim',
487
+ depthrim: 'depthRim',
488
+ rimdepth: 'depthRim',
489
+ screenrim: 'depthRim',
490
+ contactshadow: 'contactShadow',
491
+ depthshadow: 'contactShadow',
492
+ hairshadow: 'contactShadow',
493
+ spec: 'specular',
494
+ specular: 'specular',
495
+ speculararea: 'specular',
496
+ specularmask: 'specular',
497
+ highlight: 'specular',
498
+ hair: 'hairHighlight',
499
+ hairhighlight: 'hairHighlight',
500
+ strand: 'hairHighlight',
501
+ strandhighlight: 'hairHighlight',
502
+ catchlight: 'eyeHighlight',
503
+ eye: 'eyeHighlight',
504
+ eyeglint: 'eyeHighlight',
505
+ eyegloss: 'eyeHighlight',
506
+ eyehighlight: 'eyeHighlight',
507
+ eyeshine: 'eyeHighlight',
508
+ normalmap: 'normalMap',
509
+ materialnormal: 'normalMap',
510
+ normaltexture: 'normalMap',
511
+ ao: 'aoMap',
512
+ aomap: 'aoMap',
513
+ occlusion: 'aoMap',
514
+ occlusionmap: 'aoMap',
515
+ emissive: 'emissiveMap',
516
+ emissivemap: 'emissiveMap',
517
+ emission: 'emissiveMap',
518
+ emissionmap: 'emissiveMap',
519
+ matcap: 'matcap',
520
+ matcapmap: 'matcap',
521
+ ramp: 'ramp',
522
+ rampmap: 'ramp',
523
+ shaderamp: 'ramp',
524
+ shaderampmap: 'ramp',
525
+ gradient: 'ramp',
526
+ gradientmap: 'ramp',
527
+ detail: 'detailMap',
528
+ detailmap: 'detailMap',
529
+ roughness: 'roughnessMap',
530
+ roughnessmap: 'roughnessMap',
531
+ metalness: 'metalnessMap',
532
+ metalnessmap: 'metalnessMap',
533
+ metallic: 'metalnessMap',
534
+ metallicmap: 'metalnessMap',
535
+ shadowcolor: 'shadowColor',
536
+ finalshadow: 'shadowColor',
537
+ lit: 'lit',
538
+ light: 'lit',
539
+ lighting: 'lit',
540
+ role: 'role',
541
+ materialrole: 'role',
542
+ material: 'role',
543
+ alpha: 'alpha',
544
+ opacity: 'alpha',
545
+ });
546
+
547
+ function normalizeToonDebugKey(value) {
548
+ return String(value ?? 'off')
549
+ .trim()
550
+ .replace(/[\s_-]+/g, '')
551
+ .toLowerCase();
552
+ }
553
+
554
+ export function resolveToonDebugOutputMode(value) {
555
+ if (Number.isFinite(value)) {
556
+ const numericValue = Math.round(value);
557
+ const name = Object.entries(TOON_DEBUG_OUTPUT_MODES)
558
+ .find(([key, mode]) => key in TOON_DEBUG_OUTPUT_LABELS && mode === numericValue)?.[0] ?? 'off';
559
+ return {
560
+ label: TOON_DEBUG_OUTPUT_LABELS[name] ?? TOON_DEBUG_OUTPUT_LABELS.off,
561
+ name,
562
+ value: TOON_DEBUG_OUTPUT_MODES[name] ?? 0,
563
+ };
564
+ }
565
+
566
+ const key = normalizeToonDebugKey(value);
567
+ const name = TOON_DEBUG_MODE_ALIASES[key] ?? 'off';
568
+ return {
569
+ label: TOON_DEBUG_OUTPUT_LABELS[name] ?? TOON_DEBUG_OUTPUT_LABELS.off,
570
+ name,
571
+ value: TOON_DEBUG_OUTPUT_MODES[name] ?? 0,
572
+ };
573
+ }
574
+
575
+ function toMaterialArray(material) {
576
+ return Array.isArray(material) ? material : [material].filter(Boolean);
577
+ }
578
+
579
+ export function waitForTexture(texture) {
580
+ if (!texture) return Promise.resolve(texture);
581
+ if (texture.image && (texture.image.width || texture.image.videoWidth || texture.image.data)) {
582
+ return Promise.resolve(texture);
583
+ }
584
+
585
+ if (Array.isArray(texture.readyCallbacks)) {
586
+ return new Promise((resolve) => {
587
+ const timer = window.setTimeout(() => resolve(texture), 5000);
588
+ texture.readyCallbacks.push(() => {
589
+ window.clearTimeout(timer);
590
+ resolve(texture);
591
+ });
592
+ });
593
+ }
594
+
595
+ return Promise.resolve(texture);
596
+ }
597
+
598
+ function collectTexturesFromMaterial(mat, textures) {
599
+ for (const key of [...COLOR_TEXTURE_KEYS, ...DATA_TEXTURE_KEYS]) {
600
+ if (mat?.[key]?.isTexture) textures.add(mat[key]);
601
+ }
602
+ for (const { texture } of collectMaterialMapTextures(mat)) {
603
+ textures.add(texture);
604
+ }
605
+ }
606
+
607
+ export function waitForObjectTextures(root) {
608
+ const textures = new Set();
609
+ root.traverse((obj) => {
610
+ if (!obj.isMesh || !obj.material) return;
611
+ for (const mat of toMaterialArray(obj.material)) collectTexturesFromMaterial(mat, textures);
612
+ });
613
+ return Promise.all([...textures].map(waitForTexture));
614
+ }
615
+
616
+ export function setObjectTextureColorSpaces(root) {
617
+ root.traverse((obj) => {
618
+ if (!obj.isMesh || !obj.material) return;
619
+ for (const mat of toMaterialArray(obj.material)) {
620
+ for (const key of COLOR_TEXTURE_KEYS) {
621
+ if (mat?.[key]?.isTexture) mat[key].colorSpace = THREE.SRGBColorSpace;
622
+ }
623
+ for (const key of DATA_TEXTURE_KEYS) {
624
+ if (mat?.[key]?.isTexture) mat[key].colorSpace = THREE.NoColorSpace;
625
+ }
626
+ for (const { texture, colorSpace } of collectMaterialMapTextures(mat)) {
627
+ texture.colorSpace = colorSpace;
628
+ }
629
+ }
630
+ });
631
+ }
632
+
633
+ function ensureGeometryAttributes(geometry) {
634
+ const position = geometry?.attributes?.position;
635
+ if (!position) return;
636
+
637
+ if (!geometry.attributes.normal) geometry.computeVertexNormals();
638
+
639
+ if (!geometry.attributes.uv) {
640
+ geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(position.count * 2), 2));
641
+ }
642
+
643
+ if (!geometry.attributes.uv2) {
644
+ geometry.setAttribute('uv2', geometry.attributes.uv.clone());
645
+ }
646
+ }
647
+
648
+ // Averages normals across position-duplicate vertices and stores the result as
649
+ // an outlineSmoothNormal attribute. The inverted-hull outline expands along
650
+ // these instead of the render normals, so hard-edged geometry (split vertices
651
+ // at creases — Rigify mannequin, arbitrary GLB props) keeps a closed hull
652
+ // instead of cracking open at every sharp edge. Smooth-shaded meshes (typical
653
+ // PMX characters) are detected and skipped, costing no extra memory.
654
+ function bakeSmoothedOutlineNormals(geometry) {
655
+ const position = geometry?.attributes?.position;
656
+ const normal = geometry?.attributes?.normal;
657
+ if (!position || !normal || geometry.attributes.outlineSmoothNormal) {
658
+ return Boolean(geometry?.attributes?.outlineSmoothNormal);
659
+ }
660
+
661
+ const groups = new Map();
662
+ for (let i = 0; i < position.count; i++) {
663
+ const key = `${Math.round(position.getX(i) * 1e4)},${Math.round(position.getY(i) * 1e4)},${Math.round(position.getZ(i) * 1e4)}`;
664
+ const entry = groups.get(key);
665
+ if (entry) entry.push(i);
666
+ else groups.set(key, [i]);
667
+ }
668
+
669
+ let hasSplitNormals = false;
670
+ const smoothed = new Float32Array(position.count * 3);
671
+ for (const indices of groups.values()) {
672
+ let x = 0;
673
+ let y = 0;
674
+ let z = 0;
675
+ for (const index of indices) {
676
+ x += normal.getX(index);
677
+ y += normal.getY(index);
678
+ z += normal.getZ(index);
679
+ }
680
+ const length = Math.sqrt(x * x + y * y + z * z) || 1;
681
+ x /= length;
682
+ y /= length;
683
+ z /= length;
684
+ for (const index of indices) {
685
+ smoothed[index * 3] = x;
686
+ smoothed[index * 3 + 1] = y;
687
+ smoothed[index * 3 + 2] = z;
688
+ if (!hasSplitNormals) {
689
+ const dot = x * normal.getX(index) + y * normal.getY(index) + z * normal.getZ(index);
690
+ if (dot < 0.999) hasSplitNormals = true;
691
+ }
692
+ }
693
+ }
694
+
695
+ if (!hasSplitNormals) return false;
696
+ geometry.setAttribute('outlineSmoothNormal', new THREE.BufferAttribute(smoothed, 3));
697
+ return true;
698
+ }
699
+
700
+ function materialDrawOrder(mat, materialRoleOverrides = null, alphaSettings = createAlphaSettings()) {
701
+ const roleInfo = classifyMaterialRole(mat, materialRoleOverrides);
702
+ return materialAlphaDrawOrder(alphaSettings, mat, roleInfo);
703
+ }
704
+
705
+ function promoteOverlayGroups(geometry, materials, materialRoleOverrides = null, alphaSettings = createAlphaSettings()) {
706
+ if (!geometry?.groups?.length || !Array.isArray(materials)) return;
707
+ geometry.groups = geometry.groups
708
+ .map((group, index) => ({ group, index }))
709
+ .sort((a, b) => {
710
+ const aOrder = materialDrawOrder(materials[a.group.materialIndex], materialRoleOverrides, alphaSettings);
711
+ const bOrder = materialDrawOrder(materials[b.group.materialIndex], materialRoleOverrides, alphaSettings);
712
+ return aOrder - bOrder || a.index - b.index;
713
+ })
714
+ .map(({ group }) => group);
715
+ }
716
+
717
+ function materialNumericSuffix(name) {
718
+ const match = String(name ?? '').match(/^(.+?)[_\s-]*(\d{1,4})$/);
719
+ if (!match) return null;
720
+
721
+ const stem = match[1].replace(/[_\s-]+$/g, '').toLowerCase();
722
+ const suffix = Number(match[2]);
723
+ return stem && Number.isFinite(suffix) ? { stem, suffix } : null;
724
+ }
725
+
726
+ function anonymousMaterialIndex(name) {
727
+ const normalized = String(name ?? '').trim().toLowerCase();
728
+ if (normalized === 'material') return 0;
729
+
730
+ const match = normalized.match(/^material[_\s-]+(\d{1,4})$/);
731
+ if (!match) return null;
732
+
733
+ const index = Number(match[1]);
734
+ return Number.isFinite(index) ? index : null;
735
+ }
736
+
737
+ function materialHasReadableGltfTextureName(mat) {
738
+ const source = mat?.userData?.toonSource || {};
739
+ const materialName = String(source.materialName || '').trim();
740
+ const hasReadableMaterialName = materialName && anonymousMaterialIndex(materialName) === null;
741
+ return Boolean(hasReadableMaterialName || source.textureName || source.imageName || source.imageUri);
742
+ }
743
+
744
+ function inferAnonymousGltfAtlasRoles(root, materialRoleOverrides, nextOverrides) {
745
+ const groups = new Map();
746
+
747
+ root.traverse((obj) => {
748
+ if (!obj.isMesh || !obj.material) return;
749
+
750
+ for (const mat of toMaterialArray(obj.material)) {
751
+ if (!mat?.uuid || nextOverrides.byUuid.has(mat.uuid)) continue;
752
+
753
+ const existingRole = classifyMaterialRole(mat, materialRoleOverrides);
754
+ if (existingRole.role !== 'default') continue;
755
+
756
+ const source = mat.userData?.toonSource || {};
757
+ if (source.format !== 'gltf' || materialHasReadableGltfTextureName(mat)) continue;
758
+
759
+ const materialIndex = anonymousMaterialIndex(mat.name);
760
+ if (!Number.isInteger(materialIndex) || !Number.isInteger(source.imageIndex)) continue;
761
+
762
+ const key = source.sourceUrl || 'anonymous-gltf';
763
+ const group = groups.get(key) ?? {
764
+ imageCount: source.imageCount ?? 0,
765
+ materials: [],
766
+ usedImages: new Set(),
767
+ };
768
+ group.imageCount = Math.max(group.imageCount, source.imageCount ?? 0);
769
+ group.materials.push({ imageIndex: source.imageIndex, mat, materialIndex });
770
+ group.usedImages.add(source.imageIndex);
771
+ groups.set(key, group);
772
+ }
773
+ });
774
+
775
+ for (const group of groups.values()) {
776
+ if (group.imageCount < 3 || group.materials.length < 3) continue;
777
+ if (!group.usedImages.has(0) || !group.usedImages.has(1) || !group.usedImages.has(2)) continue;
778
+
779
+ for (const { imageIndex, mat } of group.materials) {
780
+ let role = 'costume';
781
+ if (imageIndex === 0) role = 'face';
782
+ else if (imageIndex === 2) role = 'hair';
783
+
784
+ if (mat.uuid && !nextOverrides.byUuid.has(mat.uuid)) {
785
+ nextOverrides.byUuid.set(mat.uuid, role);
786
+ nextOverrides.sourcesByUuid.set(mat.uuid, 'inferred:anonymous-gltf-atlas');
787
+ }
788
+ }
789
+ }
790
+ }
791
+
792
+ function createInferredMaterialRoleOverrides(root, materialRoleOverrides, {
793
+ inferAnonymousGltfAtlases = true,
794
+ inferPackedTriplets = true,
795
+ } = {}) {
796
+ const nextOverrides = {
797
+ byName: new Map(materialRoleOverrides.byName),
798
+ byUuid: new Map(materialRoleOverrides.byUuid),
799
+ patterns: [...materialRoleOverrides.patterns],
800
+ sourcesByUuid: new Map(materialRoleOverrides.sourcesByUuid ?? []),
801
+ };
802
+
803
+ const groups = new Map();
804
+
805
+ if (inferPackedTriplets) {
806
+ root.traverse((obj) => {
807
+ if (!obj.isMesh || !obj.material) return;
808
+
809
+ for (const mat of toMaterialArray(obj.material)) {
810
+ if (!mat?.uuid || nextOverrides.byUuid.has(mat.uuid)) continue;
811
+
812
+ const existingRole = classifyMaterialRole(mat, materialRoleOverrides);
813
+ if (existingRole.role !== 'default') continue;
814
+
815
+ const suffixInfo = materialNumericSuffix(mat.name);
816
+ if (!suffixInfo) continue;
817
+
818
+ const group = groups.get(suffixInfo.stem) ?? new Map();
819
+ group.set(suffixInfo.suffix, mat);
820
+ groups.set(suffixInfo.stem, group);
821
+ }
822
+ });
823
+
824
+ for (const group of groups.values()) {
825
+ // Common packed game-model export pattern: body/face/hair materials lose
826
+ // texture filenames in GLB but retain the source material numeric suffixes.
827
+ const suffixRoles = [
828
+ [81, 'skin'],
829
+ [82, 'face'],
830
+ [83, 'hair'],
831
+ ];
832
+ if (!suffixRoles.every(([suffix]) => group.has(suffix))) continue;
833
+
834
+ for (const [suffix, role] of suffixRoles) {
835
+ const mat = group.get(suffix);
836
+ if (mat?.uuid && !nextOverrides.byUuid.has(mat.uuid)) {
837
+ nextOverrides.byUuid.set(mat.uuid, role);
838
+ nextOverrides.sourcesByUuid.set(mat.uuid, 'inferred:packed-triplet');
839
+ }
840
+ }
841
+ }
842
+ }
843
+
844
+ if (inferAnonymousGltfAtlases) {
845
+ inferAnonymousGltfAtlasRoles(root, materialRoleOverrides, nextOverrides);
846
+ }
847
+
848
+ return nextOverrides;
849
+ }
850
+
851
+ function addMaterialRoleSummaryEntry(summary, mat, roleInfo) {
852
+ const role = roleInfo.role || 'default';
853
+ summary.total += 1;
854
+ summary.counts[role] = (summary.counts[role] ?? 0) + 1;
855
+ if (summary.materials.length >= 120) return;
856
+
857
+ const source = mat?.userData?.toonSource || {};
858
+ summary.materials.push({
859
+ imageIndex: Number.isInteger(source.imageIndex) ? source.imageIndex : null,
860
+ materialIndex: Number.isInteger(source.materialIndex) ? source.materialIndex : null,
861
+ name: mat?.name ?? '',
862
+ role,
863
+ source: roleInfo.source,
864
+ texture: mat?.map?.name || source.imageName || source.imageUri || source.textureName || '',
865
+ });
866
+ }
867
+
868
+ function colorFromUniformValue(value, fallback = new THREE.Color(1, 1, 1)) {
869
+ return new THREE.Color(
870
+ value?.r ?? value?.x ?? fallback.r,
871
+ value?.g ?? value?.y ?? fallback.g,
872
+ value?.b ?? value?.z ?? fallback.b,
873
+ );
874
+ }
875
+
876
+ function vectorFromUniformValue(value, fallback = [0, 0, 1]) {
877
+ return [
878
+ value?.x ?? fallback[0],
879
+ value?.y ?? fallback[1],
880
+ value?.z ?? fallback[2],
881
+ ];
882
+ }
883
+
884
+ function celShadeSettingsFromAnimeMaterial(mat) {
885
+ return createCelShadeSettings({
886
+ bodyCelMidPoint: mat?.uniforms?.celShadeMidPoint?.value,
887
+ bodyCelSoftness: mat?.uniforms?.celShadeSoftness?.value,
888
+ bodyMainLightIgnoreCelShade: mat?.uniforms?.mainLightIgnoreCelShade?.value,
889
+ edgeAntiAliasStrength: mat?.uniforms?.edgeAntiAliasStrength?.value,
890
+ });
891
+ }
892
+
893
+ function faceLightingSettingsFromAnimeMaterial(mat) {
894
+ return createFaceLightingSettings({
895
+ faceCelMidPoint: mat?.uniforms?.celShadeMidPointForFaceArea?.value,
896
+ faceCelSoftness: mat?.uniforms?.celShadeSoftnessForFaceArea?.value,
897
+ faceLocalLightLift: mat?.uniforms?.faceLocalLightLift?.value,
898
+ faceMainLightIgnoreCelShade: mat?.uniforms?.mainLightIgnoreCelShadeForFaceArea?.value,
899
+ faceNormalProxyBlend: mat?.uniforms?.faceNormalProxyBlend?.value,
900
+ faceProxyNormal: vectorFromUniformValue(
901
+ mat?.uniforms?.faceProxyNormalObject?.value,
902
+ DEFAULT_FACE_LIGHTING_SETTINGS.faceProxyNormal,
903
+ ),
904
+ faceSceneShadowStrength: mat?.uniforms?.faceSceneShadowStrength?.value,
905
+ faceSphereBlend: mat?.uniforms?.faceSphereBlend?.value,
906
+ headSpaceMode: (mat?.uniforms?.faceHeadSpaceMode?.value ?? 1) === 0
907
+ ? FACE_HEAD_SPACE_MODES.static
908
+ : FACE_HEAD_SPACE_MODES.headBone,
909
+ });
910
+ }
911
+
912
+ function shadowColorSettingsFromAnimeMaterial(mat) {
913
+ return createShadowColorSettings({
914
+ enabled: mat?.uniforms?.enableShadowColor?.value,
915
+ lowSaturationFallbackColor: mat?.uniforms?.lowSaturationFallbackColor?.value,
916
+ selfShadowAlbedoMulStrength: mat?.uniforms?.selfShadowAlbedoMulStrength?.value,
917
+ selfShadowAreaHSVStrength: mat?.uniforms?.selfShadowAreaHSVStrength?.value,
918
+ selfShadowAreaHueOffset: mat?.uniforms?.selfShadowAreaHueOffset?.value,
919
+ selfShadowAreaSaturationBoost: mat?.uniforms?.selfShadowAreaSaturationBoost?.value,
920
+ selfShadowAreaValueMul: mat?.uniforms?.selfShadowAreaValueMul?.value,
921
+ selfShadowTintColor: mat?.uniforms?.selfShadowTintColor?.value,
922
+ transitionAreaHueOffset: mat?.uniforms?.litToShadowTransitionAreaHueOffset?.value,
923
+ transitionAreaIntensity: mat?.uniforms?.litToShadowTransitionAreaIntensity?.value,
924
+ transitionAreaSaturationBoost: mat?.uniforms?.litToShadowTransitionAreaSaturationBoost?.value,
925
+ transitionAreaTintColor: mat?.uniforms?.litToShadowTransitionAreaTintColor?.value,
926
+ transitionAreaValueMul: mat?.uniforms?.litToShadowTransitionAreaValueMul?.value,
927
+ });
928
+ }
929
+
930
+ function sceneShadowSettingsFromAnimeMaterial(mat) {
931
+ const strength = mat?.uniforms?.receivedShadowStrength?.value;
932
+ const minLight = mat?.uniforms?.receivedShadowMinLight?.value;
933
+ return createSceneShadowSettings({
934
+ defaultMinLight: minLight,
935
+ defaultStrength: strength,
936
+ eyeMinLight: minLight,
937
+ eyeStrength: strength,
938
+ faceMinLight: minLight,
939
+ faceStrength: strength,
940
+ shadowAreaStrength: mat?.uniforms?.receivedShadowAreaStrength?.value,
941
+ skinMinLight: minLight,
942
+ skinStrength: strength,
943
+ });
944
+ }
945
+
946
+ function selfShadowSettingsFromAnimeMaterial(mat) {
947
+ const strength = mat?.uniforms?.selfShadowStrength?.value;
948
+ const minLight = mat?.uniforms?.selfShadowMinLight?.value;
949
+ return createSelfShadowSettings({
950
+ defaultMinLight: minLight,
951
+ defaultStrength: strength,
952
+ enabled: (mat?.uniforms?.selfShadowSourceMode?.value ?? 0) > 0,
953
+ eyeMinLight: minLight,
954
+ eyeStrength: strength,
955
+ faceMinLight: minLight,
956
+ faceStrength: strength,
957
+ hairMinLight: minLight,
958
+ hairStrength: strength,
959
+ shadowAreaStrength: mat?.uniforms?.selfShadowAreaStrength?.value,
960
+ skinMinLight: minLight,
961
+ skinStrength: strength,
962
+ sourceMode: mat?.uniforms?.selfShadowSourceMode?.value === SELF_SHADOW_SOURCE_MODES.sceneProxy
963
+ ? 'sceneProxy'
964
+ : 'off',
965
+ });
966
+ }
967
+
968
+ function averageShadowSettingsFromAnimeMaterial(mat) {
969
+ const strength = mat?.uniforms?.averageShadowStrength?.value;
970
+ const minLight = mat?.uniforms?.averageShadowMinLight?.value;
971
+ return createAverageShadowSettings({
972
+ defaultMinLight: minLight,
973
+ defaultStrength: strength,
974
+ enabled: strength > 0,
975
+ eyeMinLight: minLight,
976
+ eyeStrength: strength,
977
+ faceMinLight: minLight,
978
+ faceStrength: strength,
979
+ hairMinLight: minLight,
980
+ hairStrength: strength,
981
+ skinMinLight: minLight,
982
+ skinStrength: strength,
983
+ softness: mat?.uniforms?.averageShadowSoftness?.value,
984
+ });
985
+ }
986
+
987
+ function skinToneSettingsFromAnimeMaterial(mat) {
988
+ return createSkinToneSettings({
989
+ faceMaxDirectLight: mat?.uniforms?.faceMaxDirectLight?.value,
990
+ faceMinimumIndirectLight: mat?.uniforms?.faceMinimumIndirectLight?.value,
991
+ faceShadowBrightness: mat?.uniforms?.faceShadowBrightness?.value,
992
+ faceShadowSaturation: mat?.uniforms?.faceShadowSaturation?.value,
993
+ faceShadowTint: colorFromUniformValue(
994
+ mat?.uniforms?.faceShadowTintColor?.value,
995
+ new THREE.Color(...DEFAULT_SKIN_TONE_SETTINGS.faceShadowTint),
996
+ ),
997
+ faceShadowTintStrength: mat?.uniforms?.overrideByFaceShadowTintColor?.value,
998
+ skinMaxDirectLight: mat?.uniforms?.skinMaxDirectLight?.value,
999
+ skinMinimumIndirectLight: mat?.uniforms?.skinMinimumIndirectLight?.value,
1000
+ skinShadowBrightness: mat?.uniforms?.skinShadowBrightness?.value,
1001
+ skinShadowSaturation: mat?.uniforms?.skinShadowSaturation?.value,
1002
+ skinShadowTint: colorFromUniformValue(
1003
+ mat?.uniforms?.skinShadowTintColor?.value,
1004
+ new THREE.Color(...DEFAULT_SKIN_TONE_SETTINGS.skinShadowTint),
1005
+ ),
1006
+ skinShadowTintStrength: mat?.uniforms?.overrideBySkinShadowTintColor?.value,
1007
+ });
1008
+ }
1009
+
1010
+ function localLightSettingsFromAnimeMaterial(mat) {
1011
+ return createLocalLightSettings({
1012
+ defaultIntensity: mat?.uniforms?.localLightIntensity?.value,
1013
+ defaultMaxContribution: mat?.uniforms?.localLightMaxContribution?.value,
1014
+ defaultShadowLift: mat?.uniforms?.localLightShadowLift?.value,
1015
+ eyeIntensity: mat?.uniforms?.localLightIntensity?.value,
1016
+ eyeMaxContribution: mat?.uniforms?.localLightMaxContribution?.value,
1017
+ eyeShadowLift: mat?.uniforms?.localLightShadowLift?.value,
1018
+ faceIntensity: mat?.uniforms?.localLightIntensity?.value,
1019
+ faceMaxContribution: mat?.uniforms?.localLightMaxContribution?.value,
1020
+ faceShadowLift: mat?.uniforms?.localLightShadowLift?.value,
1021
+ hairIntensity: mat?.uniforms?.localLightIntensity?.value,
1022
+ hairMaxContribution: mat?.uniforms?.localLightMaxContribution?.value,
1023
+ hairShadowLift: mat?.uniforms?.localLightShadowLift?.value,
1024
+ skinIntensity: mat?.uniforms?.localLightIntensity?.value,
1025
+ skinMaxContribution: mat?.uniforms?.localLightMaxContribution?.value,
1026
+ skinShadowLift: mat?.uniforms?.localLightShadowLift?.value,
1027
+ });
1028
+ }
1029
+
1030
+ function indirectLightSettingsFromAnimeMaterial(mat) {
1031
+ const intensity = mat?.uniforms?.indirectLightIntensity?.value;
1032
+ const minimumIndirectLight = mat?.uniforms?.minimumIndirectLight?.value;
1033
+ return createIndirectLightSettings({
1034
+ ambientTint: colorFromUniformValue(
1035
+ mat?.uniforms?.ambientTint?.value,
1036
+ new THREE.Color(...DEFAULT_INDIRECT_LIGHT_SETTINGS.ambientTint),
1037
+ ),
1038
+ defaultIntensity: intensity,
1039
+ defaultMinimumIndirectLight: minimumIndirectLight,
1040
+ environmentIndirectLight: mat?.uniforms?.environmentIndirectLight?.value,
1041
+ eyeIntensity: intensity,
1042
+ eyeMinimumIndirectLight: minimumIndirectLight,
1043
+ faceIntensity: intensity,
1044
+ faceMinimumIndirectLight: minimumIndirectLight,
1045
+ hairIntensity: intensity,
1046
+ hairMinimumIndirectLight: minimumIndirectLight,
1047
+ hemisphereLightIntensity: mat?.uniforms?.hemisphereLightIntensity?.value,
1048
+ skinIntensity: intensity,
1049
+ skinMinimumIndirectLight: minimumIndirectLight,
1050
+ });
1051
+ }
1052
+
1053
+ function rimLightSettingsFromAnimeMaterial(mat) {
1054
+ return createRimLightSettings({
1055
+ blockByShadow: mat?.uniforms?.rimBlockByShadow?.value,
1056
+ defaultIntensity: mat?.uniforms?.rimIntensity?.value,
1057
+ depthCloseWidthReduce: mat?.uniforms?.rimDepthCloseWidthReduce?.value,
1058
+ depthDottedLineFix: mat?.uniforms?.rimDepthDottedLineFix?.value,
1059
+ depthFadeEndDistance: mat?.uniforms?.rimDepthFadeEndDistance?.value,
1060
+ depthFadeRange: mat?.uniforms?.rimDepthFadeRange?.value,
1061
+ depthFadeStartDistance: mat?.uniforms?.rimDepthFadeStartDistance?.value,
1062
+ depthMask3D: mat?.uniforms?.rimDepthMask3D?.value,
1063
+ depthSafeDistance: mat?.uniforms?.rimDepthSafeDistance?.value,
1064
+ depthThresholdOffset: mat?.uniforms?.rimDepthThresholdOffset?.value,
1065
+ depthWidth: mat?.uniforms?.rimDepthWidth?.value,
1066
+ mode: (mat?.uniforms?.rimLightMode?.value ?? 1) === 0
1067
+ ? RIM_LIGHT_MODES.fresnel
1068
+ : RIM_LIGHT_MODES.depthTexture,
1069
+ defaultTintColor: colorFromUniformValue(
1070
+ mat?.uniforms?.rimTintColor?.value,
1071
+ new THREE.Color(...DEFAULT_RIM_LIGHT_SETTINGS.defaultTintColor),
1072
+ ),
1073
+ eyeIntensity: mat?.uniforms?.rimIntensity?.value,
1074
+ faceIntensity: mat?.uniforms?.rimIntensity?.value,
1075
+ hairIntensity: mat?.uniforms?.rimIntensity?.value,
1076
+ midPoint: mat?.uniforms?.rimMidPoint?.value,
1077
+ mixWithBaseMapColor: mat?.uniforms?.rimMixWithBaseMapColor?.value,
1078
+ skinIntensity: mat?.uniforms?.rimIntensity?.value,
1079
+ softness: mat?.uniforms?.rimSoftness?.value,
1080
+ });
1081
+ }
1082
+
1083
+ function specularSettingsFromAnimeMaterial(mat) {
1084
+ return createSpecularSettings({
1085
+ defaultColor: colorFromUniformValue(
1086
+ mat?.uniforms?.specularColor?.value,
1087
+ new THREE.Color(...DEFAULT_SPECULAR_SETTINGS.defaultColor),
1088
+ ),
1089
+ directionMode: (mat?.uniforms?.specularDirectionMode?.value ?? 0) === 1 ? 'view' : 'light',
1090
+ defaultIntensity: mat?.uniforms?.specularIntensity?.value,
1091
+ defaultMidPoint: mat?.uniforms?.specularAreaRemapMidPoint?.value,
1092
+ defaultPower: mat?.uniforms?.specularPower?.value,
1093
+ defaultRange: mat?.uniforms?.specularAreaRemapRange?.value,
1094
+ defaultShowInShadowArea: mat?.uniforms?.specularShowInShadowArea?.value,
1095
+ eyeIntensity: mat?.uniforms?.specularIntensity?.value,
1096
+ faceIntensity: mat?.uniforms?.specularIntensity?.value,
1097
+ hairIntensity: mat?.uniforms?.specularIntensity?.value,
1098
+ hairPower: mat?.uniforms?.specularPower?.value,
1099
+ maskChannel: mat?.uniforms?.specularMaskChannel?.value,
1100
+ maskStrength: mat?.uniforms?.specularMaskStrength?.value,
1101
+ metalIntensity: mat?.uniforms?.specularIntensity?.value,
1102
+ skinIntensity: mat?.uniforms?.specularIntensity?.value,
1103
+ });
1104
+ }
1105
+
1106
+ function hairHighlightSettingsFromAnimeMaterial(mat) {
1107
+ return createHairHighlightSettings({
1108
+ direction: vectorFromUniformValue(
1109
+ mat?.uniforms?.hairHighlightDirection?.value,
1110
+ DEFAULT_HAIR_HIGHLIGHT_SETTINGS.direction,
1111
+ ),
1112
+ enabled: mat?.uniforms?.useHairHighlight?.value ?? true,
1113
+ intensity: mat?.uniforms?.hairHighlightIntensity?.value,
1114
+ maskChannel: mat?.uniforms?.hairHighlightMaskChannel?.value,
1115
+ maskMap: mat?.uniforms?.hairHighlightMaskMap?.value ?? null,
1116
+ maskStrength: mat?.uniforms?.hairHighlightMaskStrength?.value,
1117
+ mode: mat?.uniforms?.hairHighlightMode?.value === HAIR_HIGHLIGHT_MODE_VALUES.anisotropic
1118
+ ? HAIR_HIGHLIGHT_MODES.anisotropic
1119
+ : HAIR_HIGHLIGHT_MODES.legacy,
1120
+ shadowFloor: mat?.uniforms?.hairHighlightShadowFloor?.value,
1121
+ sideBandPower: mat?.uniforms?.hairHighlightSideBandPower?.value,
1122
+ strandPower: mat?.uniforms?.hairHighlightStrandPower?.value,
1123
+ uvBandAxis: mat?.uniforms?.hairHighlightUvBandAxis?.value,
1124
+ uvBandCenter: mat?.uniforms?.hairHighlightUvBandCenter?.value,
1125
+ uvBandHalfWidth: mat?.uniforms?.hairHighlightUvBandHalfWidth?.value,
1126
+ });
1127
+ }
1128
+
1129
+ function eyeHighlightSettingsFromAnimeMaterial(mat) {
1130
+ return createEyeHighlightSettings({
1131
+ color: colorFromUniformValue(
1132
+ mat?.uniforms?.eyeHighlightColor?.value,
1133
+ new THREE.Color(...DEFAULT_EYE_HIGHLIGHT_SETTINGS.color),
1134
+ ),
1135
+ enabled: mat?.uniforms?.useEyeHighlight?.value ?? true,
1136
+ intensity: mat?.uniforms?.eyeHighlightIntensity?.value,
1137
+ maskChannel: mat?.uniforms?.eyeHighlightMaskChannel?.value,
1138
+ maskMap: mat?.uniforms?.eyeHighlightMaskMap?.value ?? null,
1139
+ maskStrength: mat?.uniforms?.eyeHighlightMaskStrength?.value,
1140
+ power: mat?.uniforms?.eyeHighlightPower?.value,
1141
+ showInShadowArea: mat?.uniforms?.eyeHighlightShowInShadowArea?.value,
1142
+ });
1143
+ }
1144
+
1145
+ function sourceEyeHighlightMaskMap(mat, eyeHighlightSettings) {
1146
+ if (eyeHighlightSettings.sourceMaskMode !== EYE_HIGHLIGHT_SOURCE_MASK_MODES.source) return null;
1147
+ return mat?.userData?.toonEyeHighlightMaskMap ??
1148
+ mat?.userData?.eyeHighlightMaskMap ??
1149
+ mat?.userData?.toonCatchlightMaskMap ??
1150
+ mat?.userData?.catchlightMaskMap ??
1151
+ mat?.userData?.highlightMaskMap ??
1152
+ null;
1153
+ }
1154
+
1155
+ function sourceHairHighlightMaskMap(mat, hairHighlightSettings) {
1156
+ if (hairHighlightSettings.sourceMaskMode !== HAIR_HIGHLIGHT_SOURCE_MASK_MODES.source) return null;
1157
+ return mat?.userData?.toonHairHighlightMaskMap ??
1158
+ mat?.userData?.hairHighlightMaskMap ??
1159
+ mat?.userData?.highlightMaskMap ??
1160
+ null;
1161
+ }
1162
+
1163
+ function sourceSpecularMaskMap(mat, specularSettings) {
1164
+ if (specularSettings.sourceMaskMode !== 'source') return null;
1165
+ return mat?.userData?.toonSpecularMaskMap ??
1166
+ mat?.userData?.specularMaskMap ??
1167
+ mat?.specularIntensityMap ??
1168
+ mat?.specularMap ??
1169
+ mat?.specularColorMap ??
1170
+ null;
1171
+ }
1172
+
1173
+ function createAnimeMaterial({
1174
+ alphaBlend = false,
1175
+ alphaTest = -1.0,
1176
+ averageShadowSettings = createAverageShadowSettings(),
1177
+ base = null,
1178
+ baseColor = new THREE.Color(1, 1, 1),
1179
+ baseMapSaturation = 1,
1180
+ celShadeSettings = createCelShadeSettings(),
1181
+ contactShadowSettings = createContactShadowSettings(),
1182
+ debugOutputMode = 0,
1183
+ depthWrite = null,
1184
+ eyeHighlightSettings = createEyeHighlightSettings(),
1185
+ faceLightingSettings = createFaceLightingSettings(),
1186
+ glitterSettings = createGlitterSettings(),
1187
+ hairHighlightSettings = createHairHighlightSettings(),
1188
+ isEye = false,
1189
+ isFace = false,
1190
+ isHair = false,
1191
+ isMetal = false,
1192
+ isOutline = false,
1193
+ isSkin = false,
1194
+ isTransparentOverlay = false,
1195
+ indirectLightSettings = createIndirectLightSettings(),
1196
+ localLightSettings = createLocalLightSettings(),
1197
+ perspectiveRemovalSettings = createPerspectiveRemovalSettings(),
1198
+ stickerSettings = createStickerSettings(),
1199
+ materialMaps = null,
1200
+ materialRole = MATERIAL_ROLES.default,
1201
+ opacity = 1,
1202
+ outlineSettings = createOutlineSettings(),
1203
+ rimLightSettings = createRimLightSettings(),
1204
+ sceneShadowSettings = createSceneShadowSettings(),
1205
+ selfShadowSettings = createSelfShadowSettings(),
1206
+ shadowColorSettings = createShadowColorSettings(),
1207
+ side = THREE.DoubleSide,
1208
+ sourceBaseColor = new THREE.Color(1, 1, 1),
1209
+ sourceMaterial = null,
1210
+ specularMask = null,
1211
+ specularSettings = createSpecularSettings(),
1212
+ hairHighlightMask = null,
1213
+ eyeHighlightMask = null,
1214
+ skinToneSettings = createSkinToneSettings(),
1215
+ transparent = false,
1216
+ } = {}) {
1217
+ const resolvedMaterialMaps = materialMaps ?? resolveMaterialMapsForMaterial(createMaterialMapSettings(), sourceMaterial);
1218
+ const outline = isOutline
1219
+ ? resolveOutlineForMaterial(outlineSettings, {
1220
+ alphaTest,
1221
+ isEye,
1222
+ isFace,
1223
+ isHair,
1224
+ isMetal,
1225
+ isSkin,
1226
+ isTransparentOverlay,
1227
+ })
1228
+ : resolveOutlineForMaterial(createOutlineSettings(false));
1229
+ const averageShadow = resolveAverageShadowForMaterial(averageShadowSettings, { isEye, isFace, isHair, isSkin });
1230
+ const indirectLight = resolveIndirectLightForMaterial(indirectLightSettings, {
1231
+ faceMinimumIndirectLightFallback: skinToneSettings.faceMinimumIndirectLight,
1232
+ isEye,
1233
+ isFace,
1234
+ isHair,
1235
+ isSkin,
1236
+ skinMinimumIndirectLightFallback: skinToneSettings.skinMinimumIndirectLight,
1237
+ });
1238
+ const localLight = resolveLocalLightForMaterial(localLightSettings, { isEye, isFace, isHair, isSkin });
1239
+ const eyeHighlight = resolveEyeHighlightForMaterial(eyeHighlightSettings, {
1240
+ isEye,
1241
+ isOutline,
1242
+ maskMap: eyeHighlightMask,
1243
+ });
1244
+ const hairHighlight = resolveHairHighlightForMaterial(hairHighlightSettings, {
1245
+ isHair,
1246
+ isOutline,
1247
+ maskMap: hairHighlightMask,
1248
+ material: sourceMaterial,
1249
+ });
1250
+ const rimLight = resolveRimLightForMaterial(rimLightSettings, { isEye, isFace, isHair, isOutline, isSkin });
1251
+ const specular = resolveSpecularForMaterial(specularSettings, {
1252
+ isEye,
1253
+ isFace,
1254
+ isHair,
1255
+ isMetal,
1256
+ isOutline,
1257
+ isSkin,
1258
+ maskMap: specularMask,
1259
+ });
1260
+ const sceneShadow = resolveSceneShadowForMaterial(sceneShadowSettings, { isEye, isFace, isSkin });
1261
+ const selfShadow = resolveSelfShadowForMaterial(selfShadowSettings, { isEye, isFace, isHair, isSkin });
1262
+ const contactShadow = resolveContactShadowForMaterial(contactShadowSettings, { isEye, isFace, isOutline });
1263
+ const glitter = resolveGlitterForMaterial(glitterSettings, { isEye, isFace, isHair, isOutline, isSkin });
1264
+ const sticker = resolveStickerForMaterial(stickerSettings, { isOutline, sourceMaterial });
1265
+
1266
+ // TSL backend: same resolved settings, node-material assembly. The
1267
+ // returned material exposes `.uniforms` under the GLSL names so every
1268
+ // adapter write-through below (and the settings panels) works unchanged.
1269
+ return createAnimeNodeMaterial({
1270
+ alphaBlend,
1271
+ alphaTest,
1272
+ averageShadow,
1273
+ averageShadowSettings,
1274
+ base,
1275
+ baseColor,
1276
+ baseMapSaturation,
1277
+ celShadeSettings,
1278
+ contactShadow,
1279
+ debugOutputMode,
1280
+ depthWrite,
1281
+ eyeHighlight,
1282
+ faceHeadSpaceModeValue: FACE_HEAD_SPACE_MODE_VALUES[faceLightingSettings.headSpaceMode] ?? 1,
1283
+ faceLightingSettings,
1284
+ glitter,
1285
+ hairHighlight,
1286
+ indirectLight,
1287
+ indirectLightSettings,
1288
+ isEye,
1289
+ isFace,
1290
+ isHair,
1291
+ isMetal,
1292
+ isOutline,
1293
+ isSkin,
1294
+ isTransparentOverlay,
1295
+ localLight,
1296
+ materialRole,
1297
+ opacity,
1298
+ outline,
1299
+ perspectiveRemovalSettings,
1300
+ resolvedMaterialMaps,
1301
+ rimLight,
1302
+ sceneShadow,
1303
+ sceneShadowSettings,
1304
+ selfShadow,
1305
+ selfShadowSettings,
1306
+ shadowColorSettings,
1307
+ side,
1308
+ skinToneSettings,
1309
+ sourceBaseColor,
1310
+ sourceMaterial,
1311
+ specular,
1312
+ sticker,
1313
+ transparent,
1314
+ });
1315
+ }
1316
+
1317
+ function createAnimeMaterialFromOriginalMaterial(mat, {
1318
+ alphaSettings = createAlphaSettings(),
1319
+ averageShadowSettings = createAverageShadowSettings(),
1320
+ baseTextureSettings = createBaseTextureSettings(),
1321
+ celShadeSettings = createCelShadeSettings(),
1322
+ contactShadowSettings = createContactShadowSettings(),
1323
+ debugOutputMode = 0,
1324
+ eyeHighlightSettings = createEyeHighlightSettings(),
1325
+ faceLightingSettings = createFaceLightingSettings(),
1326
+ glitterSettings = createGlitterSettings(),
1327
+ hairHighlightSettings = createHairHighlightSettings(),
1328
+ indirectLightSettings = createIndirectLightSettings(),
1329
+ localLightSettings = createLocalLightSettings(),
1330
+ materialMapSettings = createMaterialMapSettings(),
1331
+ materialRoleOverrides = null,
1332
+ outlineSettings = createOutlineSettings(),
1333
+ perspectiveRemovalSettings = createPerspectiveRemovalSettings(),
1334
+ rimLightSettings = createRimLightSettings(),
1335
+ sceneShadowSettings = createSceneShadowSettings(),
1336
+ selfShadowSettings = createSelfShadowSettings(),
1337
+ shadowColorSettings = createShadowColorSettings(),
1338
+ specularSettings = createSpecularSettings(),
1339
+ skinToneSettings = createSkinToneSettings(),
1340
+ stickerSettings = createStickerSettings(),
1341
+ } = {}) {
1342
+ const roleInfo = classifyMaterialRole(mat, materialRoleOverrides);
1343
+ const isSkin = roleIsSkin(roleInfo);
1344
+ const isFace = roleIsFace(roleInfo);
1345
+ const isEye = roleIsEye(roleInfo);
1346
+ const isHair = roleIsHair(roleInfo);
1347
+ const isMetal = Boolean(roleInfo?.isMetal);
1348
+ const isTransparentOverlay = roleIsTransparentOverlay(roleInfo);
1349
+ const alpha = resolveAlphaForMaterial(alphaSettings, mat, roleInfo);
1350
+ const sourceBaseColor = getSourceMaterialColor(mat);
1351
+ const eyeHighlightMask = sourceEyeHighlightMaskMap(mat, eyeHighlightSettings);
1352
+ const hairHighlightMask = sourceHairHighlightMaskMap(mat, hairHighlightSettings);
1353
+ const specularMask = sourceSpecularMaskMap(mat, specularSettings);
1354
+ const materialMaps = resolveMaterialMapsForMaterial(materialMapSettings, mat);
1355
+ const material = createAnimeMaterial({
1356
+ alphaBlend: alpha.alphaBlend,
1357
+ alphaTest: alpha.alphaTest,
1358
+ averageShadowSettings,
1359
+ base: mat?.map ?? (isSkin ? fallbackSkinTexture : null),
1360
+ baseColor: resolveBaseMaterialColor(mat, baseTextureSettings),
1361
+ baseMapSaturation: resolveBaseMapSaturation({ isFace, isSkin }, baseTextureSettings),
1362
+ celShadeSettings,
1363
+ contactShadowSettings,
1364
+ debugOutputMode,
1365
+ depthWrite: alpha.depthWrite,
1366
+ eyeHighlightSettings,
1367
+ faceLightingSettings,
1368
+ glitterSettings,
1369
+ hairHighlightSettings,
1370
+ isSkin,
1371
+ isFace,
1372
+ isEye,
1373
+ isHair,
1374
+ isMetal,
1375
+ indirectLightSettings,
1376
+ isTransparentOverlay,
1377
+ localLightSettings,
1378
+ materialMaps,
1379
+ materialRole: roleInfo.roleValue,
1380
+ opacity: alpha.opacity,
1381
+ outlineSettings,
1382
+ perspectiveRemovalSettings,
1383
+ rimLightSettings,
1384
+ stickerSettings,
1385
+ sceneShadowSettings,
1386
+ selfShadowSettings,
1387
+ shadowColorSettings,
1388
+ side: THREE.DoubleSide,
1389
+ sourceBaseColor,
1390
+ sourceMaterial: mat,
1391
+ eyeHighlightMask,
1392
+ hairHighlightMask,
1393
+ specularMask,
1394
+ specularSettings,
1395
+ skinToneSettings,
1396
+ transparent: alpha.transparent,
1397
+ });
1398
+
1399
+ material.name = mat?.name ?? '';
1400
+ material.visible = mat?.visible ?? true;
1401
+ material.uniforms.ditherOpacity.value = alphaSettings.ditherOpacity ?? 1;
1402
+ material.userData.materialRole = roleInfo.role;
1403
+ material.userData.materialRoleLabel = roleInfo.roleLabel;
1404
+ material.userData.materialRoleSource = roleInfo.source;
1405
+ material.userData.sourceMaterialName = mat?.name ?? '';
1406
+ material.userData.sourceMaterialUuid = mat?.uuid ?? '';
1407
+
1408
+ if (isEye || roleIsEyeHighlight(roleInfo)) {
1409
+ material.polygonOffset = true;
1410
+ material.polygonOffsetFactor = roleIsEyeHighlight(roleInfo) ? -2 : -1;
1411
+ material.polygonOffsetUnits = roleIsEyeHighlight(roleInfo) ? -2 : -1;
1412
+ }
1413
+
1414
+ return material;
1415
+ }
1416
+
1417
+ function createDebugMaterial(mat, shaderMode, {
1418
+ alphaSettings = createAlphaSettings(),
1419
+ baseTextureSettings = createBaseTextureSettings(),
1420
+ materialRoleOverrides = null,
1421
+ } = {}) {
1422
+ const roleInfo = classifyMaterialRole(mat, materialRoleOverrides);
1423
+ if (shaderMode === 'normal') {
1424
+ return new THREE.MeshNormalMaterial({ side: THREE.DoubleSide });
1425
+ }
1426
+
1427
+ const alpha = resolveAlphaForMaterial(alphaSettings, mat, roleInfo);
1428
+ const params = {
1429
+ alphaMap: mat?.alphaMap ?? null,
1430
+ alphaTest: alpha.alphaCutout ? alpha.alphaTest : mat?.alphaTest ?? 0,
1431
+ color: resolveBaseMaterialColor(mat, baseTextureSettings),
1432
+ map: mat?.map ?? null,
1433
+ opacity: alpha.opacity,
1434
+ side: THREE.DoubleSide,
1435
+ transparent: alpha.alphaBlend,
1436
+ depthWrite: alpha.depthWrite,
1437
+ };
1438
+
1439
+ if (shaderMode === 'toon') return new THREE.MeshToonMaterial(params);
1440
+ return new THREE.MeshBasicMaterial(params);
1441
+ }
1442
+
1443
+ function createBodyMaterial(mat, shaderMode, {
1444
+ alphaSettings = createAlphaSettings(),
1445
+ averageShadowSettings = createAverageShadowSettings(),
1446
+ baseTextureSettings = createBaseTextureSettings(),
1447
+ celShadeSettings = createCelShadeSettings(),
1448
+ contactShadowSettings = createContactShadowSettings(),
1449
+ debugOutputMode = 0,
1450
+ eyeHighlightSettings = createEyeHighlightSettings(),
1451
+ faceLightingSettings = createFaceLightingSettings(),
1452
+ glitterSettings = createGlitterSettings(),
1453
+ hairHighlightSettings = createHairHighlightSettings(),
1454
+ indirectLightSettings = createIndirectLightSettings(),
1455
+ localLightSettings = createLocalLightSettings(),
1456
+ materialMapSettings = createMaterialMapSettings(),
1457
+ materialRoleOverrides = null,
1458
+ outlineSettings = createOutlineSettings(),
1459
+ perspectiveRemovalSettings = createPerspectiveRemovalSettings(),
1460
+ rimLightSettings = createRimLightSettings(),
1461
+ sceneShadowSettings = createSceneShadowSettings(),
1462
+ selfShadowSettings = createSelfShadowSettings(),
1463
+ shadowColorSettings = createShadowColorSettings(),
1464
+ specularSettings = createSpecularSettings(),
1465
+ skinToneSettings = createSkinToneSettings(),
1466
+ stickerSettings = createStickerSettings(),
1467
+ } = {}) {
1468
+ if (shaderMode === 'anime') {
1469
+ return createAnimeMaterialFromOriginalMaterial(mat, {
1470
+ alphaSettings,
1471
+ averageShadowSettings,
1472
+ baseTextureSettings,
1473
+ celShadeSettings,
1474
+ contactShadowSettings,
1475
+ debugOutputMode,
1476
+ eyeHighlightSettings,
1477
+ faceLightingSettings,
1478
+ glitterSettings,
1479
+ hairHighlightSettings,
1480
+ indirectLightSettings,
1481
+ localLightSettings,
1482
+ materialMapSettings,
1483
+ materialRoleOverrides,
1484
+ outlineSettings,
1485
+ perspectiveRemovalSettings,
1486
+ rimLightSettings,
1487
+ sceneShadowSettings,
1488
+ selfShadowSettings,
1489
+ shadowColorSettings,
1490
+ specularSettings,
1491
+ skinToneSettings,
1492
+ stickerSettings,
1493
+ });
1494
+ }
1495
+ return createDebugMaterial(mat, shaderMode, { alphaSettings, baseTextureSettings, materialRoleOverrides });
1496
+ }
1497
+
1498
+ function createOutlineMaterialFromAnimeMaterial(mat, outlineSettings = createOutlineSettings()) {
1499
+ const materialRole = mat?.uniforms?.materialRole?.value ?? MATERIAL_ROLES.default;
1500
+ const isTransparentOverlay = [
1501
+ MATERIAL_ROLES.blush,
1502
+ MATERIAL_ROLES.catchlight,
1503
+ MATERIAL_ROLES.eyeHighlight,
1504
+ MATERIAL_ROLES.transparentOverlay,
1505
+ ].includes(materialRole);
1506
+ const material = createAnimeMaterial({
1507
+ alphaBlend: false,
1508
+ alphaTest: mat?.uniforms?.aCutoff?.value ?? 0.01,
1509
+ averageShadowSettings: averageShadowSettingsFromAnimeMaterial(mat),
1510
+ base: mat?.uniforms?.base?.value ?? fallbackWhiteTexture,
1511
+ baseColor: new THREE.Color(
1512
+ mat?.uniforms?.baseColor?.value?.x ?? 1,
1513
+ mat?.uniforms?.baseColor?.value?.y ?? 1,
1514
+ mat?.uniforms?.baseColor?.value?.z ?? 1,
1515
+ ),
1516
+ baseMapSaturation: mat?.uniforms?.baseMapSaturation?.value ?? 1,
1517
+ celShadeSettings: celShadeSettingsFromAnimeMaterial(mat),
1518
+ debugOutputMode: mat?.uniforms?.debugOutputMode?.value ?? 0,
1519
+ eyeHighlightSettings: eyeHighlightSettingsFromAnimeMaterial(mat),
1520
+ faceLightingSettings: faceLightingSettingsFromAnimeMaterial(mat),
1521
+ hairHighlightSettings: hairHighlightSettingsFromAnimeMaterial(mat),
1522
+ isOutline: true,
1523
+ isSkin: mat?.uniforms?.isSkin?.value ?? false,
1524
+ isFace: mat?.uniforms?.isFace?.value ?? false,
1525
+ isEye: mat?.uniforms?.isEye?.value ?? false,
1526
+ isHair: mat?.uniforms?.isHair?.value ?? false,
1527
+ isMetal: materialRole === MATERIAL_ROLES.metal,
1528
+ isTransparentOverlay,
1529
+ indirectLightSettings: indirectLightSettingsFromAnimeMaterial(mat),
1530
+ localLightSettings: localLightSettingsFromAnimeMaterial(mat),
1531
+ materialRole,
1532
+ opacity: 1,
1533
+ outlineSettings,
1534
+ perspectiveRemovalSettings: createPerspectiveRemovalSettings({
1535
+ amount: mat?.uniforms?.perspectiveRemovalAmount?.value,
1536
+ endHeight: mat?.uniforms?.perspectiveRemovalEndHeight?.value,
1537
+ radius: mat?.uniforms?.perspectiveRemovalRadius?.value,
1538
+ startHeight: mat?.uniforms?.perspectiveRemovalStartHeight?.value,
1539
+ }),
1540
+ rimLightSettings: rimLightSettingsFromAnimeMaterial(mat),
1541
+ sceneShadowSettings: sceneShadowSettingsFromAnimeMaterial(mat),
1542
+ selfShadowSettings: selfShadowSettingsFromAnimeMaterial(mat),
1543
+ shadowColorSettings: shadowColorSettingsFromAnimeMaterial(mat),
1544
+ side: THREE.BackSide,
1545
+ sourceBaseColor: new THREE.Color(
1546
+ mat?.uniforms?.sourceBaseColor?.value?.x ?? 1,
1547
+ mat?.uniforms?.sourceBaseColor?.value?.y ?? 1,
1548
+ mat?.uniforms?.sourceBaseColor?.value?.z ?? 1,
1549
+ ),
1550
+ sourceMaterial: {
1551
+ name: mat?.userData?.sourceMaterialName ?? mat?.name ?? '',
1552
+ uuid: mat?.userData?.sourceMaterialUuid ?? '',
1553
+ userData: mat?.userData ?? {},
1554
+ },
1555
+ eyeHighlightMask: mat?.uniforms?.eyeHighlightMaskMap?.value ?? null,
1556
+ hairHighlightMask: mat?.uniforms?.hairHighlightMaskMap?.value ?? null,
1557
+ specularMask: mat?.uniforms?.specularMaskMap?.value ?? null,
1558
+ specularSettings: specularSettingsFromAnimeMaterial(mat),
1559
+ skinToneSettings: skinToneSettingsFromAnimeMaterial(mat),
1560
+ transparent: false,
1561
+ });
1562
+ material.name = mat?.name ?? '';
1563
+ material.visible = Boolean(mat?.visible ?? true) && (material.uniforms?.outlineThickness?.value ?? 0) > 0;
1564
+ material.userData.materialRole = 'outline';
1565
+ material.userData.materialRoleLabel = 'Outline';
1566
+ material.userData.materialRoleSource = mat?.userData?.materialRoleSource ?? 'outline';
1567
+ return material;
1568
+ }
1569
+
1570
+ export function setToonDebugOutput(root, debugOutputMode = 'off') {
1571
+ const resolved = resolveToonDebugOutputMode(debugOutputMode);
1572
+ root?.traverse((obj) => {
1573
+ if (!obj.isMesh || !obj.material) return;
1574
+ for (const mat of toMaterialArray(obj.material)) {
1575
+ if (!mat?.uniforms?.debugOutputMode) continue;
1576
+ mat.uniforms.debugOutputMode.value = resolved.value;
1577
+ if (resolved.value > 0 && !mat.defines?.TOON_DEBUG_VIEWS) {
1578
+ mat.defines = { ...mat.defines, TOON_DEBUG_VIEWS: 1 };
1579
+ }
1580
+ mat.needsUpdate = true;
1581
+ }
1582
+ });
1583
+ return resolved;
1584
+ }
1585
+
1586
+ // Bayer screen-door fade for a whole character (1 = opaque, 0 = gone).
1587
+ // Works with cutout, outlines, and depth writes; also freezes the screen-space
1588
+ // depth effects while fading so they cannot sparkle against stale depth.
1589
+ export function setToonDitherOpacity(target, opacity = 1) {
1590
+ const value = Math.min(1, Math.max(0, Number(opacity) ?? 1));
1591
+ let updatedMaterialCount = 0;
1592
+
1593
+ const updateMaterial = (material) => {
1594
+ for (const mat of toMaterialArray(material)) {
1595
+ if (!mat?.uniforms?.ditherOpacity) continue;
1596
+ mat.uniforms.ditherOpacity.value = value;
1597
+ updatedMaterialCount += 1;
1598
+ }
1599
+ };
1600
+
1601
+ if (target?.isMaterial || Array.isArray(target)) {
1602
+ updateMaterial(target);
1603
+ } else {
1604
+ target?.traverse?.((obj) => {
1605
+ if (!obj.isMesh || !obj.material) return;
1606
+ updateMaterial(obj.material);
1607
+ });
1608
+ }
1609
+
1610
+ return updatedMaterialCount;
1611
+ }
1612
+
1613
+ function copyUniformValue(uniform, value) {
1614
+ if (!uniform || value === undefined) return;
1615
+ const current = uniform.value;
1616
+ if (current?.isColor && value?.isColor && current.copy) {
1617
+ current.copy(value);
1618
+ return;
1619
+ }
1620
+ if (current?.isVector2 && value?.isVector2 && current.copy) {
1621
+ current.copy(value);
1622
+ return;
1623
+ }
1624
+ if (current?.isVector3 && value?.isVector3 && current.copy) {
1625
+ current.copy(value);
1626
+ return;
1627
+ }
1628
+ if (current?.isVector4 && value?.isVector4 && current.copy) {
1629
+ current.copy(value);
1630
+ return;
1631
+ }
1632
+ uniform.value = value?.clone ? value.clone() : value;
1633
+ }
1634
+
1635
+ function setToonUniform(uniforms, name, value) {
1636
+ copyUniformValue(uniforms?.[name], value);
1637
+ }
1638
+
1639
+ function textureOrNull(texture, fallbackTexture = fallbackWhiteTexture) {
1640
+ return texture?.isTexture && texture !== fallbackTexture ? texture : null;
1641
+ }
1642
+
1643
+ function roleFlagsFromAnimeMaterial(mat) {
1644
+ const uniforms = mat?.uniforms || {};
1645
+ const materialRole = uniforms.materialRole?.value ?? MATERIAL_ROLES.default;
1646
+ return {
1647
+ isEye: Boolean(uniforms.isEye?.value),
1648
+ isFace: Boolean(uniforms.isFace?.value),
1649
+ isHair: Boolean(uniforms.isHair?.value),
1650
+ isMetal: materialRole === MATERIAL_ROLES.metal,
1651
+ isOutline: Boolean(uniforms.isOutline?.value),
1652
+ isSkin: Boolean(uniforms.isSkin?.value),
1653
+ isTransparentOverlay: [
1654
+ MATERIAL_ROLES.blush,
1655
+ MATERIAL_ROLES.catchlight,
1656
+ MATERIAL_ROLES.eyeHighlight,
1657
+ MATERIAL_ROLES.transparentOverlay,
1658
+ ].includes(materialRole),
1659
+ materialRole,
1660
+ };
1661
+ }
1662
+
1663
+ function applyToonSettingsToAnimeMaterial(mat, settings) {
1664
+ const uniforms = mat?.uniforms;
1665
+ if (!uniforms?.materialRole) return false;
1666
+
1667
+ const flags = roleFlagsFromAnimeMaterial(mat);
1668
+ const sceneShadow = resolveSceneShadowForMaterial(settings.sceneShadow, flags);
1669
+ const selfShadow = resolveSelfShadowForMaterial(settings.selfShadow, flags);
1670
+ const averageShadow = resolveAverageShadowForMaterial(settings.averageShadow, flags);
1671
+ const indirectLight = resolveIndirectLightForMaterial(settings.indirectLight, flags);
1672
+ const localLight = resolveLocalLightForMaterial(settings.localLights, flags);
1673
+ const rimLight = resolveRimLightForMaterial(settings.rimLight, flags);
1674
+ const specularMask = textureOrNull(uniforms.specularMaskMap?.value);
1675
+ const specular = resolveSpecularForMaterial(settings.specular, {
1676
+ ...flags,
1677
+ maskMap: specularMask,
1678
+ });
1679
+ const hairHighlightMask = textureOrNull(uniforms.hairHighlightMaskMap?.value);
1680
+ const hairHighlight = resolveHairHighlightForMaterial(settings.hairHighlight, {
1681
+ isHair: flags.isHair,
1682
+ isOutline: flags.isOutline,
1683
+ maskMap: hairHighlightMask,
1684
+ material: mat,
1685
+ });
1686
+ const eyeHighlightMask = textureOrNull(uniforms.eyeHighlightMaskMap?.value);
1687
+ const eyeHighlight = resolveEyeHighlightForMaterial(settings.eyeHighlight, {
1688
+ isEye: flags.isEye,
1689
+ isOutline: flags.isOutline,
1690
+ maskMap: eyeHighlightMask,
1691
+ });
1692
+ const outline = resolveOutlineForMaterial(settings.outline, {
1693
+ alphaTest: uniforms.aCutoff?.value ?? -1,
1694
+ ...flags,
1695
+ });
1696
+ const contactShadow = resolveContactShadowForMaterial(settings.contactShadow ?? createContactShadowSettings(), flags);
1697
+ const glitter = resolveGlitterForMaterial(settings.glitter ?? createGlitterSettings(), flags);
1698
+ const sticker = resolveStickerForMaterial(settings.sticker ?? createStickerSettings(), {
1699
+ isOutline: flags.isOutline,
1700
+ sourceMaterial: { userData: mat.userData ?? {} },
1701
+ });
1702
+ const stickerMapResolved = sticker.map ?? textureOrNull(uniforms.stickerMap?.value);
1703
+ const perspectiveRemoval = settings.perspectiveRemoval ?? createPerspectiveRemovalSettings();
1704
+ const fur = settings.fur ?? createFurSettings();
1705
+
1706
+ setToonUniform(uniforms, 'enableShadowColor', settings.shadowColor.enabled);
1707
+ setToonUniform(uniforms, 'celShadeMidPoint', settings.celShade.bodyCelMidPoint);
1708
+ setToonUniform(uniforms, 'celShadeSoftness', settings.celShade.bodyCelSoftness);
1709
+ setToonUniform(uniforms, 'mainLightIgnoreCelShade', settings.celShade.bodyMainLightIgnoreCelShade);
1710
+ setToonUniform(uniforms, 'edgeAntiAliasStrength', settings.celShade.edgeAntiAliasStrength);
1711
+ setToonUniform(uniforms, 'celShadeMidPointForFaceArea', settings.faceLighting.faceCelMidPoint);
1712
+ setToonUniform(uniforms, 'celShadeSoftnessForFaceArea', settings.faceLighting.faceCelSoftness);
1713
+ setToonUniform(uniforms, 'mainLightIgnoreCelShadeForFaceArea', settings.faceLighting.faceMainLightIgnoreCelShade);
1714
+ setToonUniform(uniforms, 'faceSceneShadowStrength', settings.faceLighting.faceSceneShadowStrength);
1715
+ setToonUniform(uniforms, 'faceLocalLightLift', settings.faceLighting.faceLocalLightLift);
1716
+ setToonUniform(uniforms, 'faceNormalProxyBlend', settings.faceLighting.faceNormalProxyBlend);
1717
+ setToonUniform(uniforms, 'faceSphereBlend', settings.faceLighting.faceSphereBlend);
1718
+ setToonUniform(
1719
+ uniforms,
1720
+ 'faceHeadSpaceMode',
1721
+ FACE_HEAD_SPACE_MODE_VALUES[settings.faceLighting.headSpaceMode] ?? 1,
1722
+ );
1723
+ setToonUniform(uniforms, 'contactShadowStrength', contactShadow.strength);
1724
+ setToonUniform(uniforms, 'contactShadowWidth', contactShadow.width);
1725
+ setToonUniform(uniforms, 'contactShadowThresholdOffset', contactShadow.thresholdOffset);
1726
+ setToonUniform(uniforms, 'contactShadowFadeRange', contactShadow.fadeRange);
1727
+ setToonUniform(uniforms, 'contactShadowFaceHeadUpBlend', contactShadow.faceHeadUpBlend);
1728
+ setToonUniform(uniforms, 'ditherOpacity', settings.alpha.ditherOpacity);
1729
+ setToonUniform(
1730
+ uniforms,
1731
+ 'faceProxyNormalObject',
1732
+ new THREE.Vector3(...settings.faceLighting.faceProxyNormal).normalize(),
1733
+ );
1734
+
1735
+ setToonUniform(uniforms, 'selfShadowTintColor', settings.shadowColor.selfShadowTintColor);
1736
+ setToonUniform(uniforms, 'selfShadowAreaHSVStrength', settings.shadowColor.selfShadowAreaHSVStrength);
1737
+ setToonUniform(uniforms, 'selfShadowAreaHueOffset', settings.shadowColor.selfShadowAreaHueOffset);
1738
+ setToonUniform(uniforms, 'selfShadowAreaSaturationBoost', settings.shadowColor.selfShadowAreaSaturationBoost);
1739
+ setToonUniform(uniforms, 'selfShadowAreaValueMul', settings.shadowColor.selfShadowAreaValueMul);
1740
+ setToonUniform(uniforms, 'selfShadowAlbedoMulStrength', settings.shadowColor.selfShadowAlbedoMulStrength);
1741
+ setToonUniform(uniforms, 'litToShadowTransitionAreaIntensity', settings.shadowColor.transitionAreaIntensity);
1742
+ setToonUniform(uniforms, 'litToShadowTransitionAreaTintColor', settings.shadowColor.transitionAreaTintColor);
1743
+ setToonUniform(uniforms, 'litToShadowTransitionAreaHueOffset', settings.shadowColor.transitionAreaHueOffset);
1744
+ setToonUniform(uniforms, 'litToShadowTransitionAreaSaturationBoost', settings.shadowColor.transitionAreaSaturationBoost);
1745
+ setToonUniform(uniforms, 'litToShadowTransitionAreaValueMul', settings.shadowColor.transitionAreaValueMul);
1746
+ setToonUniform(uniforms, 'lowSaturationFallbackColor', settings.shadowColor.lowSaturationFallbackColor);
1747
+
1748
+ setToonUniform(uniforms, 'overrideBySkinShadowTintColor', settings.skinTone.skinShadowTintStrength);
1749
+ setToonUniform(uniforms, 'skinShadowTintColor', settings.skinTone.skinShadowTint);
1750
+ setToonUniform(uniforms, 'skinShadowBrightness', settings.skinTone.skinShadowBrightness);
1751
+ setToonUniform(uniforms, 'skinShadowSaturation', settings.skinTone.skinShadowSaturation);
1752
+ setToonUniform(uniforms, 'overrideByFaceShadowTintColor', settings.skinTone.faceShadowTintStrength);
1753
+ setToonUniform(uniforms, 'faceShadowTintColor', settings.skinTone.faceShadowTint);
1754
+ setToonUniform(uniforms, 'faceShadowBrightness', settings.skinTone.faceShadowBrightness);
1755
+ setToonUniform(uniforms, 'faceShadowSaturation', settings.skinTone.faceShadowSaturation);
1756
+ setToonUniform(uniforms, 'skinMinimumIndirectLight', settings.skinTone.skinMinimumIndirectLight);
1757
+ setToonUniform(uniforms, 'faceMinimumIndirectLight', settings.skinTone.faceMinimumIndirectLight);
1758
+ setToonUniform(uniforms, 'skinMaxDirectLight', settings.skinTone.skinMaxDirectLight);
1759
+ setToonUniform(uniforms, 'faceMaxDirectLight', settings.skinTone.faceMaxDirectLight);
1760
+
1761
+ setToonUniform(uniforms, 'ambientTint', settings.indirectLight.ambientTint);
1762
+ setToonUniform(uniforms, 'indirectLightIntensity', indirectLight.intensity);
1763
+ setToonUniform(uniforms, 'minimumIndirectLight', indirectLight.minimumIndirectLight);
1764
+ setToonUniform(uniforms, 'environmentIndirectLight', settings.indirectLight.environmentIndirectLight);
1765
+ setToonUniform(uniforms, 'hemisphereLightIntensity', settings.indirectLight.hemisphereLightIntensity);
1766
+ setToonUniform(uniforms, 'localLightIntensity', localLight.intensity);
1767
+ setToonUniform(uniforms, 'localLightMaxContribution', localLight.maxContribution);
1768
+ setToonUniform(uniforms, 'localLightShadowLift', localLight.shadowLift);
1769
+
1770
+ setToonUniform(uniforms, 'receivedShadowStrength', sceneShadow.strength);
1771
+ setToonUniform(uniforms, 'receivedShadowMinLight', sceneShadow.minLight);
1772
+ setToonUniform(uniforms, 'receivedShadowAreaStrength', settings.sceneShadow.shadowAreaStrength);
1773
+ setToonUniform(uniforms, 'selfShadowSourceMode', settings.selfShadow.sourceMode);
1774
+ setToonUniform(uniforms, 'selfShadowStrength', selfShadow.strength);
1775
+ setToonUniform(uniforms, 'selfShadowMinLight', selfShadow.minLight);
1776
+ setToonUniform(uniforms, 'selfShadowAreaStrength', settings.selfShadow.shadowAreaStrength);
1777
+ setToonUniform(uniforms, 'averageShadowStrength', averageShadow.strength);
1778
+ setToonUniform(uniforms, 'averageShadowMinLight', averageShadow.minLight);
1779
+ setToonUniform(uniforms, 'averageShadowSoftness', settings.averageShadow.softness);
1780
+ setToonUniform(uniforms, 'averageShadowMeasuredBlend', settings.averageShadow.measuredBlend);
1781
+
1782
+ setToonUniform(uniforms, 'useSpecular', specular.enabled);
1783
+ setToonUniform(uniforms, 'specularIntensity', specular.intensity);
1784
+ setToonUniform(uniforms, 'specularColor', specular.color);
1785
+ setToonUniform(uniforms, 'specularAreaRemapMidPoint', specular.midPoint);
1786
+ setToonUniform(uniforms, 'specularAreaRemapRange', specular.range);
1787
+ setToonUniform(uniforms, 'specularPower', specular.power);
1788
+ setToonUniform(uniforms, 'specularShowInShadowArea', specular.showInShadowArea);
1789
+ setToonUniform(uniforms, 'specularDirectionMode', specular.directionModeValue);
1790
+ setToonUniform(uniforms, 'specularMaskMap', specular.maskMap ?? fallbackWhiteTexture);
1791
+ setToonUniform(uniforms, 'useSpecularMask', specular.useMask);
1792
+ setToonUniform(uniforms, 'specularMaskStrength', specular.maskStrength);
1793
+ setToonUniform(uniforms, 'specularMaskChannel', specular.maskChannel);
1794
+
1795
+ setToonUniform(uniforms, 'useRimLight', rimLight.enabled);
1796
+ setToonUniform(uniforms, 'rimTintColor', rimLight.tintColor);
1797
+ setToonUniform(uniforms, 'rimIntensity', rimLight.intensity);
1798
+ setToonUniform(uniforms, 'rimMidPoint', rimLight.midPoint);
1799
+ setToonUniform(uniforms, 'rimSoftness', rimLight.softness);
1800
+ setToonUniform(uniforms, 'rimMixWithBaseMapColor', rimLight.mixWithBaseMapColor);
1801
+ setToonUniform(uniforms, 'rimBlockByShadow', rimLight.blockByShadow);
1802
+ setToonUniform(uniforms, 'rimLightMode', rimLight.modeValue);
1803
+ setToonUniform(uniforms, 'rimDepthWidth', rimLight.depthWidth);
1804
+ setToonUniform(uniforms, 'rimDepthThresholdOffset', rimLight.depthThresholdOffset);
1805
+ setToonUniform(uniforms, 'rimDepthFadeRange', rimLight.depthFadeRange);
1806
+ setToonUniform(uniforms, 'rimDepthSafeDistance', rimLight.depthSafeDistance);
1807
+ setToonUniform(uniforms, 'rimDepthCloseWidthReduce', rimLight.depthCloseWidthReduce);
1808
+ setToonUniform(uniforms, 'rimDepthDottedLineFix', rimLight.depthDottedLineFix);
1809
+ setToonUniform(uniforms, 'rimDepthMask3D', rimLight.depthMask3D);
1810
+ setToonUniform(uniforms, 'rimDepthFadeStartDistance', rimLight.depthFadeStartDistance);
1811
+ setToonUniform(uniforms, 'rimDepthFadeEndDistance', rimLight.depthFadeEndDistance);
1812
+
1813
+ setToonUniform(uniforms, 'useHairHighlight', hairHighlight.enabled);
1814
+ setToonUniform(uniforms, 'hairHighlightDirection', new THREE.Vector3(...hairHighlight.direction));
1815
+ setToonUniform(uniforms, 'hairHighlightIntensity', hairHighlight.intensity);
1816
+ setToonUniform(uniforms, 'hairHighlightMaskMap', hairHighlight.maskMap ?? fallbackWhiteTexture);
1817
+ setToonUniform(uniforms, 'hairHighlightMaskChannel', hairHighlight.maskChannel);
1818
+ setToonUniform(uniforms, 'hairHighlightMaskStrength', hairHighlight.maskStrength);
1819
+ setToonUniform(uniforms, 'hairHighlightMode', hairHighlight.modeValue);
1820
+ setToonUniform(uniforms, 'hairHighlightShadowFloor', hairHighlight.shadowFloor);
1821
+ setToonUniform(uniforms, 'hairHighlightSideBandPower', hairHighlight.sideBandPower);
1822
+ setToonUniform(uniforms, 'hairHighlightStrandPower', hairHighlight.strandPower);
1823
+ setToonUniform(uniforms, 'hairHighlightUvBandAxis', hairHighlight.uvBandAxis);
1824
+ setToonUniform(uniforms, 'hairHighlightUvBandCenter', hairHighlight.uvBandCenter);
1825
+ setToonUniform(uniforms, 'hairHighlightUvBandHalfWidth', hairHighlight.uvBandHalfWidth);
1826
+ setToonUniform(uniforms, 'useHairHighlightMask', hairHighlight.useMask);
1827
+
1828
+ setToonUniform(uniforms, 'useEyeHighlight', eyeHighlight.enabled);
1829
+ setToonUniform(uniforms, 'eyeHighlightMaskMap', eyeHighlight.maskMap ?? fallbackWhiteTexture);
1830
+ setToonUniform(uniforms, 'useEyeHighlightMask', eyeHighlight.useMask);
1831
+ setToonUniform(uniforms, 'eyeHighlightColor', eyeHighlight.color);
1832
+ setToonUniform(uniforms, 'eyeHighlightIntensity', eyeHighlight.intensity);
1833
+ setToonUniform(uniforms, 'eyeHighlightMaskChannel', eyeHighlight.maskChannel);
1834
+ setToonUniform(uniforms, 'eyeHighlightMaskStrength', eyeHighlight.maskStrength);
1835
+ setToonUniform(uniforms, 'eyeHighlightPower', eyeHighlight.power);
1836
+ setToonUniform(uniforms, 'eyeHighlightShowInShadowArea', eyeHighlight.showInShadowArea);
1837
+
1838
+ setToonUniform(uniforms, 'baseMapSaturation', resolveBaseMapSaturation(flags, settings.baseTexture));
1839
+ setToonUniform(uniforms, 'materialAoStrength', settings.materialMaps.aoStrength);
1840
+ setToonUniform(uniforms, 'materialDetailRepeat', settings.materialMaps.detailRepeat);
1841
+ setToonUniform(uniforms, 'materialDetailStrength', settings.materialMaps.detailStrength);
1842
+ setToonUniform(uniforms, 'materialEmissiveColor', settings.materialMaps.emissiveColor);
1843
+ setToonUniform(uniforms, 'materialEmissiveStrength', settings.materialMaps.emissiveStrength);
1844
+ setToonUniform(uniforms, 'materialMatcapStrength', settings.materialMaps.matcapStrength);
1845
+ setToonUniform(uniforms, 'materialMetalnessStrength', settings.materialMaps.metalnessStrength);
1846
+ setToonUniform(uniforms, 'materialNormalScale', settings.materialMaps.normalScale);
1847
+ setToonUniform(uniforms, 'materialNormalStrength', settings.materialMaps.normalStrength);
1848
+ setToonUniform(uniforms, 'useMaterialNormalMap', Boolean(uniforms.hasMaterialNormalMap?.value) && settings.materialMaps.normalStrength > 0);
1849
+ setToonUniform(uniforms, 'materialRampStrength', settings.materialMaps.rampStrength);
1850
+ setToonUniform(uniforms, 'materialRoughnessStrength', settings.materialMaps.roughnessStrength);
1851
+ setToonUniform(uniforms, 'materialSpecularColorStrength', settings.materialMaps.specularColorStrength);
1852
+
1853
+ setToonUniform(uniforms, 'outlineTintColor', outline.tintColor);
1854
+ setToonUniform(uniforms, 'outlineTintColorSkinAreaOverride', outline.skinTintColor);
1855
+ setToonUniform(uniforms, 'outlineSkinAreaOverrideStrength', outline.skinTintStrength);
1856
+ setToonUniform(uniforms, 'outlineLightingMix', outline.lightingMix);
1857
+ setToonUniform(uniforms, 'outlineMinBrightness', outline.minBrightness);
1858
+ setToonUniform(uniforms, 'outlineMaxBrightness', outline.maxBrightness);
1859
+ setToonUniform(uniforms, 'outlineDepthOffset', outline.depthOffset);
1860
+ setToonUniform(uniforms, 'outlineThickness', outline.width);
1861
+ setToonUniform(uniforms, 'outlineScreenSpaceFix', outline.screenSpaceWidth);
1862
+ setToonUniform(uniforms, 'outlineReferenceDistance', outline.referenceDistance);
1863
+ setToonUniform(
1864
+ uniforms,
1865
+ 'outlineReferenceProjection11',
1866
+ 1 / Math.tan(THREE.MathUtils.degToRad((outline.referenceFov ?? 40) / 2)),
1867
+ );
1868
+ setToonUniform(uniforms, 'outlineWidthFadeDistance', outline.widthFadeDistance);
1869
+ mat.visible = flags.isOutline ? outline.enabled : mat.visible;
1870
+
1871
+ setToonUniform(uniforms, 'useGlitter', glitter.enabled);
1872
+ setToonUniform(uniforms, 'glitterIntensity', glitter.intensity);
1873
+ setToonUniform(uniforms, 'glitterDensity', glitter.density);
1874
+ setToonUniform(uniforms, 'glitterSize', glitter.size);
1875
+ setToonUniform(uniforms, 'glitterRandomNormalStrength', glitter.randomNormalStrength);
1876
+ setToonUniform(uniforms, 'glitterShowInShadowArea', glitter.showInShadowArea);
1877
+ setToonUniform(uniforms, 'glitterUvChannel', glitter.uvChannel);
1878
+
1879
+ setToonUniform(uniforms, 'useSticker', sticker.enabled || (Boolean(stickerMapResolved) && settings.sticker?.enabled === true));
1880
+ setToonUniform(uniforms, 'stickerMap', stickerMapResolved ?? fallbackWhiteTexture);
1881
+ setToonUniform(uniforms, 'stickerBlendMode', sticker.blendModeValue);
1882
+ setToonUniform(uniforms, 'stickerStrength', sticker.strength);
1883
+ setToonUniform(uniforms, 'stickerRepeat', new THREE.Vector2(...sticker.repeat));
1884
+ setToonUniform(uniforms, 'stickerOffset', new THREE.Vector2(...sticker.offset));
1885
+ setToonUniform(uniforms, 'stickerUvChannel', sticker.uvChannel);
1886
+
1887
+ // Outline and fur-shell materials get the same amount so they stay attached
1888
+ // to the flattened body.
1889
+ setToonUniform(uniforms, 'perspectiveRemovalAmount', perspectiveRemoval.amount);
1890
+ setToonUniform(uniforms, 'perspectiveRemovalRadius', perspectiveRemoval.radius);
1891
+ setToonUniform(uniforms, 'perspectiveRemovalStartHeight', perspectiveRemoval.startHeight);
1892
+ setToonUniform(uniforms, 'perspectiveRemovalEndHeight', perspectiveRemoval.endHeight);
1893
+
1894
+ // Fur shells created at conversion time follow live global fur tuning
1895
+ // (furLayer stays per-shell; density/shape are shared).
1896
+ if (mat.userData?.toonFlags?.isFurShell) {
1897
+ setToonUniform(uniforms, 'furLength', fur.length);
1898
+ setToonUniform(uniforms, 'furGravity', fur.gravity);
1899
+ setToonUniform(uniforms, 'furDensity', fur.density);
1900
+ setToonUniform(uniforms, 'furRootOffset', fur.rootOffset);
1901
+ setToonUniform(uniforms, 'furRootShade', fur.rootShade);
1902
+ }
1903
+
1904
+ mat.needsUpdate = true;
1905
+ return true;
1906
+ }
1907
+
1908
+ export function applyToonSettingsToMaterial(target, settingsInput = {}) {
1909
+ const settings = createToonSettings(settingsInput);
1910
+ let updatedMaterialCount = 0;
1911
+
1912
+ const updateMaterial = (material) => {
1913
+ const materials = toMaterialArray(material);
1914
+ for (const mat of materials) {
1915
+ if (applyToonSettingsToAnimeMaterial(mat, settings)) updatedMaterialCount += 1;
1916
+ }
1917
+ };
1918
+
1919
+ if (target?.isMaterial || Array.isArray(target)) {
1920
+ updateMaterial(target);
1921
+ } else {
1922
+ target?.traverse?.((obj) => {
1923
+ if (!obj.isMesh || !obj.material) return;
1924
+ updateMaterial(obj.material);
1925
+ });
1926
+ }
1927
+
1928
+ return {
1929
+ settings,
1930
+ updatedMaterialCount,
1931
+ };
1932
+ }
1933
+
1934
+ function addOutlinePass(mesh, outlineSettings = createOutlineSettings()) {
1935
+ const outline = mesh.clone(false);
1936
+ outline.geometry = mesh.geometry;
1937
+ outline.material = Array.isArray(mesh.material)
1938
+ ? mesh.material.map((m) => createOutlineMaterialFromAnimeMaterial(m, outlineSettings))
1939
+ : createOutlineMaterialFromAnimeMaterial(mesh.material, outlineSettings);
1940
+
1941
+ if (outlineSettings.smoothNormals !== false && bakeSmoothedOutlineNormals(mesh.geometry)) {
1942
+ // Node materials have no GLSL defines: give the hull its own geometry
1943
+ // that shares every buffer with the source but swaps the normal attribute
1944
+ // for the baked smoothed one.
1945
+ const source = mesh.geometry;
1946
+ const hullGeometry = new THREE.BufferGeometry();
1947
+ hullGeometry.index = source.index;
1948
+ for (const name of Object.keys(source.attributes)) {
1949
+ hullGeometry.setAttribute(name, source.attributes[name]);
1950
+ }
1951
+ hullGeometry.setAttribute('normal', source.getAttribute('outlineSmoothNormal'));
1952
+ hullGeometry.morphAttributes = source.morphAttributes;
1953
+ hullGeometry.morphTargetsRelative = source.morphTargetsRelative;
1954
+ hullGeometry.groups = source.groups;
1955
+ hullGeometry.drawRange = source.drawRange;
1956
+ hullGeometry.boundingSphere = source.boundingSphere;
1957
+ hullGeometry.boundingBox = source.boundingBox;
1958
+ outline.geometry = hullGeometry;
1959
+ }
1960
+
1961
+ outline.userData.isToonOutline = true;
1962
+ outline.renderOrder = (mesh.renderOrder ?? 0) + 1;
1963
+ outline.frustumCulled = false;
1964
+ outline.castShadow = false;
1965
+ outline.receiveShadow = false;
1966
+ outline.position.set(0, 0, 0);
1967
+ outline.quaternion.identity();
1968
+ outline.scale.set(1, 1, 1);
1969
+ outline.updateMatrix();
1970
+ outline.updateMatrixWorld(true);
1971
+
1972
+ if (mesh.isSkinnedMesh && outline.isSkinnedMesh) {
1973
+ outline.bindMode = mesh.bindMode;
1974
+ outline.bindMatrix.copy(mesh.bindMatrix);
1975
+ outline.bindMatrixInverse.copy(mesh.bindMatrixInverse);
1976
+ outline.skeleton = mesh.skeleton;
1977
+ }
1978
+
1979
+ mesh.add(outline);
1980
+ }
1981
+
1982
+ const hiddenFurSlotMaterial = new THREE.MeshBasicMaterial({ name: 'ToonFurHiddenSlot', visible: false });
1983
+
1984
+ // Shell fur: N sibling meshes sharing the geometry and skeleton; each shell
1985
+ // gets its own node material so per-shell uniform nodes are not shared.
1986
+ function addFurShells(mesh, furMatchesByIndex, furSettings) {
1987
+ const baseMaterials = toMaterialArray(mesh.material);
1988
+ if (!furMatchesByIndex.some(Boolean)) return;
1989
+
1990
+ for (let shellIndex = 0; shellIndex < furSettings.shellCount; shellIndex++) {
1991
+ const layer = (shellIndex + 1) / furSettings.shellCount;
1992
+ const shellMaterials = baseMaterials.map((mat, materialIndex) => {
1993
+ if (!furMatchesByIndex[materialIndex] || !mat?.uniforms) return hiddenFurSlotMaterial;
1994
+ if (typeof mat.userData?.createFurShellVariant === 'function') {
1995
+ const shellMat = mat.userData.createFurShellVariant(furSettings, layer);
1996
+ shellMat.name = `${mat.name} (fur shell ${shellIndex + 1})`;
1997
+ return shellMat;
1998
+ }
1999
+ return hiddenFurSlotMaterial;
2000
+ });
2001
+
2002
+ const shell = mesh.clone(false);
2003
+ shell.geometry = mesh.geometry;
2004
+ shell.material = Array.isArray(mesh.material) ? shellMaterials : shellMaterials[0];
2005
+ shell.userData.isToonFurShell = true;
2006
+ shell.renderOrder = (mesh.renderOrder ?? 0) + 1;
2007
+ shell.frustumCulled = false;
2008
+ shell.castShadow = false;
2009
+ shell.receiveShadow = false;
2010
+ shell.position.set(0, 0, 0);
2011
+ shell.quaternion.identity();
2012
+ shell.scale.set(1, 1, 1);
2013
+ shell.updateMatrix();
2014
+
2015
+ if (mesh.isSkinnedMesh && shell.isSkinnedMesh) {
2016
+ shell.bindMode = mesh.bindMode;
2017
+ shell.bindMatrix.copy(mesh.bindMatrix);
2018
+ shell.bindMatrixInverse.copy(mesh.bindMatrixInverse);
2019
+ shell.skeleton = mesh.skeleton;
2020
+ }
2021
+
2022
+ mesh.add(shell);
2023
+ }
2024
+ }
2025
+
2026
+ export function findPrimarySkinnedMesh(root) {
2027
+ let bestMesh = null;
2028
+ let bestVertexCount = -1;
2029
+
2030
+ root.traverse((obj) => {
2031
+ if (!obj.isSkinnedMesh || !obj.skeleton) return;
2032
+ const vertexCount = obj.geometry?.attributes?.position?.count ?? 0;
2033
+ if (vertexCount > bestVertexCount) {
2034
+ bestMesh = obj;
2035
+ bestVertexCount = vertexCount;
2036
+ }
2037
+ });
2038
+
2039
+ return bestMesh;
2040
+ }
2041
+
2042
+ function compactToonSettingsOptions(options) {
2043
+ return Object.fromEntries(
2044
+ Object.entries(options).filter(([, value]) => value !== undefined && value !== null),
2045
+ );
2046
+ }
2047
+
2048
+ export function applyToonShader(root, {
2049
+ alpha = null,
2050
+ averageShadow = null,
2051
+ baseTexture = null,
2052
+ celShade = null,
2053
+ contactShadow = null,
2054
+ debugOutputMode = 'off',
2055
+ eyeHighlight = null,
2056
+ faceLighting = null,
2057
+ fur = null,
2058
+ glitter = null,
2059
+ hairHighlight = null,
2060
+ indirectLight = null,
2061
+ localLights = null,
2062
+ materialMaps = null,
2063
+ materialRoles = null,
2064
+ outline = undefined,
2065
+ perspectiveRemoval = null,
2066
+ preset = null,
2067
+ rimLight = null,
2068
+ sceneShadow = null,
2069
+ selfShadow = null,
2070
+ settings = null,
2071
+ shadowColor = null,
2072
+ shaderMode = 'anime',
2073
+ specular = null,
2074
+ skinTone = null,
2075
+ sticker = null,
2076
+ } = {}) {
2077
+ const settingsInput = typeof settings === 'string' ? { preset: settings } : settings || {};
2078
+ const toonSettings = createToonSettings({
2079
+ ...settingsInput,
2080
+ ...compactToonSettingsOptions({
2081
+ alpha,
2082
+ averageShadow,
2083
+ baseTexture,
2084
+ celShade,
2085
+ contactShadow,
2086
+ eyeHighlight,
2087
+ faceLighting,
2088
+ fur,
2089
+ glitter,
2090
+ hairHighlight,
2091
+ indirectLight,
2092
+ localLights,
2093
+ materialMaps,
2094
+ materialRoles,
2095
+ outline,
2096
+ perspectiveRemoval,
2097
+ preset,
2098
+ rimLight,
2099
+ sceneShadow,
2100
+ selfShadow,
2101
+ shadowColor,
2102
+ specular,
2103
+ skinTone,
2104
+ sticker,
2105
+ }),
2106
+ });
2107
+ const normalizedShaderMode = ['anime', 'basic', 'toon', 'normal'].includes(shaderMode)
2108
+ ? shaderMode
2109
+ : 'anime';
2110
+ const resolvedDebugOutputMode = resolveToonDebugOutputMode(debugOutputMode);
2111
+ const alphaSettings = toonSettings.alpha;
2112
+ const averageShadowSettings = toonSettings.averageShadow;
2113
+ const baseTextureSettings = toonSettings.baseTexture;
2114
+ const celShadeSettings = toonSettings.celShade;
2115
+ const contactShadowSettings = toonSettings.contactShadow;
2116
+ const furSettings = toonSettings.fur;
2117
+ const glitterSettings = toonSettings.glitter;
2118
+ const perspectiveRemovalSettings = toonSettings.perspectiveRemoval;
2119
+ const stickerSettings = toonSettings.sticker;
2120
+ const eyeHighlightSettings = toonSettings.eyeHighlight;
2121
+ const faceLightingSettings = toonSettings.faceLighting;
2122
+ const hairHighlightSettings = toonSettings.hairHighlight;
2123
+ const indirectLightSettings = toonSettings.indirectLight;
2124
+ const localLightSettings = toonSettings.localLights;
2125
+ const materialMapSettings = toonSettings.materialMaps;
2126
+ const outlineSettings = toonSettings.outline;
2127
+ const normalizedMaterialRoleOverrides = normalizeMaterialRoleOverrides(toonSettings.materialRoles);
2128
+ const materialRoleOverrides = createInferredMaterialRoleOverrides(
2129
+ root,
2130
+ normalizedMaterialRoleOverrides,
2131
+ { inferPackedTriplets: toonSettings.materialRoles?.inferPackedTriplets !== false },
2132
+ );
2133
+ const rimLightSettings = toonSettings.rimLight;
2134
+ const sceneShadowSettings = toonSettings.sceneShadow;
2135
+ const selfShadowSettings = toonSettings.selfShadow;
2136
+ const shadowColorSettings = toonSettings.shadowColor;
2137
+ const specularSettings = toonSettings.specular;
2138
+ const skinToneSettings = toonSettings.skinTone;
2139
+ let convertedMeshCount = 0;
2140
+ const materialRoleSummary = { counts: {}, materials: [], total: 0 };
2141
+
2142
+ root.traverse((obj) => {
2143
+ if (obj.userData?.isToonOutline || obj.userData?.isToonFurShell || !obj.isMesh || !obj.geometry || !obj.material) return;
2144
+
2145
+ const originalMaterials = toMaterialArray(obj.material);
2146
+ ensureGeometryAttributes(obj.geometry);
2147
+ promoteOverlayGroups(obj.geometry, originalMaterials, materialRoleOverrides, alphaSettings);
2148
+
2149
+ // Fur opt-in is decided against the source materials (names/userData);
2150
+ // remembered by slot index for shell creation after conversion.
2151
+ const furMatchesByIndex = originalMaterials.map((mat) => (
2152
+ materialUsesFur(furSettings, mat, classifyMaterialRole(mat, materialRoleOverrides))
2153
+ ));
2154
+
2155
+ for (const mat of originalMaterials) {
2156
+ addMaterialRoleSummaryEntry(
2157
+ materialRoleSummary,
2158
+ mat,
2159
+ classifyMaterialRole(mat, materialRoleOverrides),
2160
+ );
2161
+ }
2162
+
2163
+ obj.material = Array.isArray(obj.material)
2164
+ ? originalMaterials.map((mat) => createBodyMaterial(mat, normalizedShaderMode, {
2165
+ alphaSettings,
2166
+ averageShadowSettings,
2167
+ baseTextureSettings,
2168
+ celShadeSettings,
2169
+ contactShadowSettings,
2170
+ debugOutputMode: resolvedDebugOutputMode.value,
2171
+ eyeHighlightSettings,
2172
+ faceLightingSettings,
2173
+ glitterSettings,
2174
+ hairHighlightSettings,
2175
+ indirectLightSettings,
2176
+ localLightSettings,
2177
+ materialMapSettings,
2178
+ materialRoleOverrides,
2179
+ outlineSettings,
2180
+ perspectiveRemovalSettings,
2181
+ rimLightSettings,
2182
+ sceneShadowSettings,
2183
+ selfShadowSettings,
2184
+ shadowColorSettings,
2185
+ specularSettings,
2186
+ skinToneSettings,
2187
+ stickerSettings,
2188
+ }))
2189
+ : createBodyMaterial(originalMaterials[0], normalizedShaderMode, {
2190
+ alphaSettings,
2191
+ averageShadowSettings,
2192
+ baseTextureSettings,
2193
+ celShadeSettings,
2194
+ contactShadowSettings,
2195
+ debugOutputMode: resolvedDebugOutputMode.value,
2196
+ eyeHighlightSettings,
2197
+ faceLightingSettings,
2198
+ glitterSettings,
2199
+ hairHighlightSettings,
2200
+ indirectLightSettings,
2201
+ localLightSettings,
2202
+ materialMapSettings,
2203
+ materialRoleOverrides,
2204
+ outlineSettings,
2205
+ perspectiveRemovalSettings,
2206
+ rimLightSettings,
2207
+ sceneShadowSettings,
2208
+ selfShadowSettings,
2209
+ shadowColorSettings,
2210
+ specularSettings,
2211
+ skinToneSettings,
2212
+ stickerSettings,
2213
+ });
2214
+
2215
+ obj.renderOrder = obj.renderOrder ?? 0;
2216
+ obj.frustumCulled = false;
2217
+ obj.castShadow = true;
2218
+ obj.receiveShadow = true;
2219
+ obj.onBeforeRender = toonNodeLightSync;
2220
+ convertedMeshCount += 1;
2221
+
2222
+ if (outlineSettings.enabled && normalizedShaderMode === 'anime') addOutlinePass(obj, outlineSettings);
2223
+ if (furSettings.enabled && normalizedShaderMode === 'anime') addFurShells(obj, furMatchesByIndex, furSettings);
2224
+ });
2225
+
2226
+ return {
2227
+ convertedMeshCount,
2228
+ debugOutputMode: resolvedDebugOutputMode,
2229
+ materialRoleSummary,
2230
+ primarySkinnedMesh: findPrimarySkinnedMesh(root),
2231
+ settings: toonSettings,
2232
+ shaderMode: normalizedShaderMode,
2233
+ toonPreset: toonSettings.preset,
2234
+ };
2235
+ }