@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,147 @@
1
+ export const DEFAULT_SCENE_SHADOW_SETTINGS = Object.freeze({
2
+ defaultMinLight: 0.24,
3
+ defaultStrength: 0.76,
4
+ enabled: true,
5
+ eyeMinLight: 0.42,
6
+ eyeStrength: 0.05,
7
+ faceMinLight: 0.42,
8
+ faceStrength: 0.46,
9
+ shadowAreaStrength: 0.65,
10
+ skinMinLight: 0.34,
11
+ skinStrength: 0.62,
12
+ });
13
+
14
+ function firstDefined(source, keys) {
15
+ for (const key of keys) {
16
+ if (source?.[key] !== undefined) return source[key];
17
+ }
18
+ return undefined;
19
+ }
20
+
21
+ function numberOption(value, fallback, { min = -Infinity, max = Infinity } = {}) {
22
+ const nextValue = Number(value);
23
+ if (!Number.isFinite(nextValue)) return fallback;
24
+ return Math.min(max, Math.max(min, nextValue));
25
+ }
26
+
27
+ export function createSceneShadowSettings(options = null) {
28
+ const source = options || {};
29
+ const enabled = source.enabled !== false;
30
+
31
+ const defaultStrength = enabled
32
+ ? numberOption(
33
+ firstDefined(source, [
34
+ 'defaultStrength',
35
+ 'strength',
36
+ 'receiveStrength',
37
+ 'receivedShadowStrength',
38
+ ]),
39
+ DEFAULT_SCENE_SHADOW_SETTINGS.defaultStrength,
40
+ { min: 0, max: 1 },
41
+ )
42
+ : 0;
43
+ const defaultMinLight = enabled
44
+ ? numberOption(
45
+ firstDefined(source, [
46
+ 'defaultMinLight',
47
+ 'minLight',
48
+ 'receiveMinLight',
49
+ 'receivedShadowMinLight',
50
+ ]),
51
+ DEFAULT_SCENE_SHADOW_SETTINGS.defaultMinLight,
52
+ { min: 0, max: 1 },
53
+ )
54
+ : 1;
55
+
56
+ return {
57
+ defaultMinLight,
58
+ defaultStrength,
59
+ enabled,
60
+ eyeMinLight: enabled
61
+ ? numberOption(
62
+ firstDefined(source, ['eyeMinLight', 'eyeReceivedShadowMinLight']),
63
+ DEFAULT_SCENE_SHADOW_SETTINGS.eyeMinLight,
64
+ { min: 0, max: 1 },
65
+ )
66
+ : 1,
67
+ eyeStrength: enabled
68
+ ? numberOption(
69
+ firstDefined(source, ['eyeStrength', 'eyeReceiveStrength', 'eyeReceivedShadowStrength']),
70
+ DEFAULT_SCENE_SHADOW_SETTINGS.eyeStrength,
71
+ { min: 0, max: 1 },
72
+ )
73
+ : 0,
74
+ faceMinLight: enabled
75
+ ? numberOption(
76
+ firstDefined(source, ['faceMinLight', 'faceReceivedShadowMinLight']),
77
+ DEFAULT_SCENE_SHADOW_SETTINGS.faceMinLight,
78
+ { min: 0, max: 1 },
79
+ )
80
+ : 1,
81
+ faceStrength: enabled
82
+ ? numberOption(
83
+ firstDefined(source, ['faceStrength', 'faceReceiveStrength', 'faceReceivedShadowStrength']),
84
+ DEFAULT_SCENE_SHADOW_SETTINGS.faceStrength,
85
+ { min: 0, max: 1 },
86
+ )
87
+ : 0,
88
+ shadowAreaStrength: enabled
89
+ ? numberOption(
90
+ firstDefined(source, [
91
+ 'shadowAreaStrength',
92
+ 'directVisibilityStrength',
93
+ 'sceneShadowBlend',
94
+ 'shadowBlend',
95
+ ]),
96
+ DEFAULT_SCENE_SHADOW_SETTINGS.shadowAreaStrength,
97
+ { min: 0, max: 1 },
98
+ )
99
+ : 0,
100
+ skinMinLight: enabled
101
+ ? numberOption(
102
+ firstDefined(source, ['skinMinLight', 'skinReceivedShadowMinLight']),
103
+ DEFAULT_SCENE_SHADOW_SETTINGS.skinMinLight,
104
+ { min: 0, max: 1 },
105
+ )
106
+ : 1,
107
+ skinStrength: enabled
108
+ ? numberOption(
109
+ firstDefined(source, ['skinStrength', 'skinReceiveStrength', 'skinReceivedShadowStrength']),
110
+ DEFAULT_SCENE_SHADOW_SETTINGS.skinStrength,
111
+ { min: 0, max: 1 },
112
+ )
113
+ : 0,
114
+ };
115
+ }
116
+
117
+ export function resolveSceneShadowForMaterial(settings, {
118
+ isEye = false,
119
+ isFace = false,
120
+ isSkin = false,
121
+ } = {}) {
122
+ if (isEye) {
123
+ return {
124
+ minLight: settings.eyeMinLight,
125
+ strength: settings.eyeStrength,
126
+ };
127
+ }
128
+
129
+ if (isFace) {
130
+ return {
131
+ minLight: settings.faceMinLight,
132
+ strength: settings.faceStrength,
133
+ };
134
+ }
135
+
136
+ if (isSkin) {
137
+ return {
138
+ minLight: settings.skinMinLight,
139
+ strength: settings.skinStrength,
140
+ };
141
+ }
142
+
143
+ return {
144
+ minLight: settings.defaultMinLight,
145
+ strength: settings.defaultStrength,
146
+ };
147
+ }
@@ -0,0 +1,221 @@
1
+ export const SELF_SHADOW_SOURCE_MODES = Object.freeze({
2
+ off: 0,
3
+ sceneProxy: 1,
4
+ // Dedicated character-only orthographic shadow map rendered by
5
+ // characterRenderPasses.js. Materials fall back to "no self shadow" when the
6
+ // runtime pass is not active, so this is safe as a default source.
7
+ characterPass: 2,
8
+ });
9
+
10
+ export const DEFAULT_SELF_SHADOW_SETTINGS = Object.freeze({
11
+ defaultMinLight: 0.62,
12
+ defaultStrength: 0.22,
13
+ enabled: true,
14
+ eyeMinLight: 1,
15
+ eyeStrength: 0,
16
+ faceMinLight: 1,
17
+ faceStrength: 0,
18
+ hairMinLight: 0.58,
19
+ hairStrength: 0.26,
20
+ shadowAreaStrength: 0.5,
21
+ skinMinLight: 0.72,
22
+ skinStrength: 0.16,
23
+ sourceMode: SELF_SHADOW_SOURCE_MODES.characterPass,
24
+ });
25
+
26
+ function firstDefined(source, keys) {
27
+ for (const key of keys) {
28
+ if (source?.[key] !== undefined) return source[key];
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ function numberOption(value, fallback, { min = -Infinity, max = Infinity } = {}) {
34
+ const nextValue = Number(value);
35
+ if (!Number.isFinite(nextValue)) return fallback;
36
+ return Math.min(max, Math.max(min, nextValue));
37
+ }
38
+
39
+ function normalizeSourceMode(value, enabled) {
40
+ if (value === SELF_SHADOW_SOURCE_MODES.sceneProxy) return SELF_SHADOW_SOURCE_MODES.sceneProxy;
41
+ if (value === SELF_SHADOW_SOURCE_MODES.characterPass) return SELF_SHADOW_SOURCE_MODES.characterPass;
42
+
43
+ const key = String(value ?? (enabled ? 'characterPass' : 'off'))
44
+ .trim()
45
+ .replace(/[\s_-]+/g, '')
46
+ .toLowerCase();
47
+
48
+ if (
49
+ key === 'scene' ||
50
+ key === 'sceneshadow' ||
51
+ key === 'sceneproxy' ||
52
+ key === 'shadowmask'
53
+ ) {
54
+ return SELF_SHADOW_SOURCE_MODES.sceneProxy;
55
+ }
56
+
57
+ if (
58
+ key === 'characterpass' ||
59
+ key === 'character' ||
60
+ key === 'charpass' ||
61
+ key === 'pass' ||
62
+ key === 'shadowmap' ||
63
+ key === 'selfshadowmap'
64
+ ) {
65
+ return SELF_SHADOW_SOURCE_MODES.characterPass;
66
+ }
67
+
68
+ return SELF_SHADOW_SOURCE_MODES.off;
69
+ }
70
+
71
+ function normalizeSelfShadowOptions(options) {
72
+ if (options === false) return { enabled: false };
73
+ if (options === true) return { enabled: true };
74
+ if (typeof options === 'string') {
75
+ const key = options.trim().toLowerCase();
76
+ return {
77
+ enabled: key !== 'off' && key !== 'none' && key !== 'false' && key !== '0',
78
+ sourceMode: options,
79
+ };
80
+ }
81
+ return options || {};
82
+ }
83
+
84
+ export function createSelfShadowSettings(options = null) {
85
+ const source = normalizeSelfShadowOptions(options);
86
+ const requestedSourceMode = firstDefined(source, ['sourceMode', 'source', 'mode', 'maskSource']);
87
+ const enabled = source.enabled === true ||
88
+ (source.enabled !== false &&
89
+ (requestedSourceMode !== undefined || DEFAULT_SELF_SHADOW_SETTINGS.enabled));
90
+ const sourceMode = enabled
91
+ ? normalizeSourceMode(requestedSourceMode, enabled)
92
+ : SELF_SHADOW_SOURCE_MODES.off;
93
+ const active = enabled && sourceMode !== SELF_SHADOW_SOURCE_MODES.off;
94
+
95
+ return {
96
+ defaultMinLight: active
97
+ ? numberOption(
98
+ firstDefined(source, ['defaultMinLight', 'minLight', 'selfShadowMinLight']),
99
+ DEFAULT_SELF_SHADOW_SETTINGS.defaultMinLight,
100
+ { min: 0, max: 1 },
101
+ )
102
+ : 1,
103
+ defaultStrength: active
104
+ ? numberOption(
105
+ firstDefined(source, ['defaultStrength', 'strength', 'selfShadowStrength']),
106
+ DEFAULT_SELF_SHADOW_SETTINGS.defaultStrength,
107
+ { min: 0, max: 1 },
108
+ )
109
+ : 0,
110
+ enabled: active,
111
+ eyeMinLight: active
112
+ ? numberOption(
113
+ firstDefined(source, ['eyeMinLight', 'eyeSelfShadowMinLight']),
114
+ DEFAULT_SELF_SHADOW_SETTINGS.eyeMinLight,
115
+ { min: 0, max: 1 },
116
+ )
117
+ : 1,
118
+ eyeStrength: active
119
+ ? numberOption(
120
+ firstDefined(source, ['eyeStrength', 'eyeSelfShadowStrength']),
121
+ DEFAULT_SELF_SHADOW_SETTINGS.eyeStrength,
122
+ { min: 0, max: 1 },
123
+ )
124
+ : 0,
125
+ faceMinLight: active
126
+ ? numberOption(
127
+ firstDefined(source, ['faceMinLight', 'faceSelfShadowMinLight']),
128
+ DEFAULT_SELF_SHADOW_SETTINGS.faceMinLight,
129
+ { min: 0, max: 1 },
130
+ )
131
+ : 1,
132
+ faceStrength: active
133
+ ? numberOption(
134
+ firstDefined(source, ['faceStrength', 'faceSelfShadowStrength']),
135
+ DEFAULT_SELF_SHADOW_SETTINGS.faceStrength,
136
+ { min: 0, max: 1 },
137
+ )
138
+ : 0,
139
+ hairMinLight: active
140
+ ? numberOption(
141
+ firstDefined(source, ['hairMinLight', 'hairSelfShadowMinLight']),
142
+ DEFAULT_SELF_SHADOW_SETTINGS.hairMinLight,
143
+ { min: 0, max: 1 },
144
+ )
145
+ : 1,
146
+ hairStrength: active
147
+ ? numberOption(
148
+ firstDefined(source, ['hairStrength', 'hairSelfShadowStrength']),
149
+ DEFAULT_SELF_SHADOW_SETTINGS.hairStrength,
150
+ { min: 0, max: 1 },
151
+ )
152
+ : 0,
153
+ shadowAreaStrength: active
154
+ ? numberOption(
155
+ firstDefined(source, [
156
+ 'shadowAreaStrength',
157
+ 'selfShadowAreaStrength',
158
+ 'areaStrength',
159
+ 'blend',
160
+ ]),
161
+ DEFAULT_SELF_SHADOW_SETTINGS.shadowAreaStrength,
162
+ { min: 0, max: 1 },
163
+ )
164
+ : 0,
165
+ skinMinLight: active
166
+ ? numberOption(
167
+ firstDefined(source, ['skinMinLight', 'skinSelfShadowMinLight']),
168
+ DEFAULT_SELF_SHADOW_SETTINGS.skinMinLight,
169
+ { min: 0, max: 1 },
170
+ )
171
+ : 1,
172
+ skinStrength: active
173
+ ? numberOption(
174
+ firstDefined(source, ['skinStrength', 'skinSelfShadowStrength']),
175
+ DEFAULT_SELF_SHADOW_SETTINGS.skinStrength,
176
+ { min: 0, max: 1 },
177
+ )
178
+ : 0,
179
+ sourceMode,
180
+ };
181
+ }
182
+
183
+ export function resolveSelfShadowForMaterial(settings, {
184
+ isEye = false,
185
+ isFace = false,
186
+ isHair = false,
187
+ isSkin = false,
188
+ } = {}) {
189
+ if (isEye) {
190
+ return {
191
+ minLight: settings.eyeMinLight,
192
+ strength: settings.eyeStrength,
193
+ };
194
+ }
195
+
196
+ if (isFace) {
197
+ return {
198
+ minLight: settings.faceMinLight,
199
+ strength: settings.faceStrength,
200
+ };
201
+ }
202
+
203
+ if (isSkin) {
204
+ return {
205
+ minLight: settings.skinMinLight,
206
+ strength: settings.skinStrength,
207
+ };
208
+ }
209
+
210
+ if (isHair) {
211
+ return {
212
+ minLight: settings.hairMinLight,
213
+ strength: settings.hairStrength,
214
+ };
215
+ }
216
+
217
+ return {
218
+ minLight: settings.defaultMinLight,
219
+ strength: settings.defaultStrength,
220
+ };
221
+ }
@@ -0,0 +1,238 @@
1
+ import * as THREE from 'three';
2
+
3
+ export const DEFAULT_SHADOW_COLOR_SETTINGS = Object.freeze({
4
+ enabled: true,
5
+ lowSaturationFallbackColor: [0.3764706, 0.4141177, 0.5019608, 0],
6
+ selfShadowAlbedoMulStrength: 0,
7
+ selfShadowAreaHSVStrength: 1,
8
+ selfShadowAreaHueOffset: 0,
9
+ selfShadowAreaSaturationBoost: 0.2,
10
+ selfShadowAreaValueMul: 0.68,
11
+ selfShadowTintColor: [1, 1, 1],
12
+ transitionAreaHueOffset: 0.01,
13
+ transitionAreaIntensity: 1,
14
+ transitionAreaSaturationBoost: 0.36,
15
+ transitionAreaTintColor: [1, 1, 1],
16
+ transitionAreaValueMul: 1,
17
+ });
18
+
19
+ export const REFERENCE_SHADOW_COLOR_SETTINGS = Object.freeze({
20
+ ...DEFAULT_SHADOW_COLOR_SETTINGS,
21
+ selfShadowAreaValueMul: 0.7,
22
+ transitionAreaSaturationBoost: 0.5,
23
+ });
24
+
25
+ export const SHADOW_COLOR_PRESETS = Object.freeze({
26
+ baseline: DEFAULT_SHADOW_COLOR_SETTINGS,
27
+ reference: REFERENCE_SHADOW_COLOR_SETTINGS,
28
+ });
29
+
30
+ function firstDefined(source, keys) {
31
+ for (const key of keys) {
32
+ if (source?.[key] !== undefined) return source[key];
33
+ }
34
+ return undefined;
35
+ }
36
+
37
+ function numberOption(value, fallback, { min = -Infinity, max = Infinity } = {}) {
38
+ const nextValue = Number(value);
39
+ if (!Number.isFinite(nextValue)) return fallback;
40
+ return THREE.MathUtils.clamp(nextValue, min, max);
41
+ }
42
+
43
+ function colorOption(value, fallback) {
44
+ if (value?.isColor) return value.clone();
45
+
46
+ if (Array.isArray(value) && value.length >= 3) {
47
+ return new THREE.Color(
48
+ numberOption(value[0], fallback[0] ?? fallback.r ?? 1),
49
+ numberOption(value[1], fallback[1] ?? fallback.g ?? 1),
50
+ numberOption(value[2], fallback[2] ?? fallback.b ?? 1),
51
+ );
52
+ }
53
+
54
+ if (typeof value === 'object' && value !== null) {
55
+ return new THREE.Color(
56
+ numberOption(value.r, fallback[0] ?? fallback.r ?? 1),
57
+ numberOption(value.g, fallback[1] ?? fallback.g ?? 1),
58
+ numberOption(value.b, fallback[2] ?? fallback.b ?? 1),
59
+ );
60
+ }
61
+
62
+ if (typeof value === 'string' || typeof value === 'number') {
63
+ try {
64
+ return new THREE.Color(value);
65
+ } catch {
66
+ // Fall through to the normalized fallback below.
67
+ }
68
+ }
69
+
70
+ return Array.isArray(fallback)
71
+ ? new THREE.Color(fallback[0], fallback[1], fallback[2])
72
+ : fallback.clone();
73
+ }
74
+
75
+ function vector4Option(value, fallback) {
76
+ if (value?.isVector4) return value.clone();
77
+
78
+ if (Array.isArray(value) && value.length >= 4) {
79
+ return new THREE.Vector4(
80
+ numberOption(value[0], fallback[0] ?? fallback.x ?? 0),
81
+ numberOption(value[1], fallback[1] ?? fallback.y ?? 0),
82
+ numberOption(value[2], fallback[2] ?? fallback.z ?? 0),
83
+ numberOption(value[3], fallback[3] ?? fallback.w ?? 0),
84
+ );
85
+ }
86
+
87
+ if (typeof value === 'object' && value !== null) {
88
+ return new THREE.Vector4(
89
+ numberOption(value.x ?? value.r, fallback[0] ?? fallback.x ?? 0),
90
+ numberOption(value.y ?? value.g, fallback[1] ?? fallback.y ?? 0),
91
+ numberOption(value.z ?? value.b, fallback[2] ?? fallback.z ?? 0),
92
+ numberOption(value.w ?? value.a, fallback[3] ?? fallback.w ?? 0),
93
+ );
94
+ }
95
+
96
+ return Array.isArray(fallback)
97
+ ? new THREE.Vector4(fallback[0], fallback[1], fallback[2], fallback[3])
98
+ : fallback.clone();
99
+ }
100
+
101
+ function resolvePresetDefaults(source) {
102
+ const presetName = String(firstDefined(source, ['preset', 'profile']) || 'baseline').toLowerCase();
103
+ return SHADOW_COLOR_PRESETS[presetName] || DEFAULT_SHADOW_COLOR_SETTINGS;
104
+ }
105
+
106
+ export function createShadowColorSettings(options = null) {
107
+ const source = typeof options === 'string' ? { preset: options } : options || {};
108
+ const presetDefaults = resolvePresetDefaults(source);
109
+ const enabled = source.enabled !== false;
110
+
111
+ return {
112
+ enabled,
113
+ lowSaturationFallbackColor: vector4Option(
114
+ firstDefined(source, [
115
+ 'lowSaturationFallbackColor',
116
+ 'lowSaturationFallback',
117
+ 'fallbackColor',
118
+ ]),
119
+ presetDefaults.lowSaturationFallbackColor,
120
+ ),
121
+ selfShadowAlbedoMulStrength: enabled
122
+ ? numberOption(
123
+ firstDefined(source, [
124
+ 'selfShadowAlbedoMulStrength',
125
+ 'shadowAlbedoMulStrength',
126
+ 'albedoMulStrength',
127
+ ]),
128
+ presetDefaults.selfShadowAlbedoMulStrength,
129
+ { min: 0, max: 1 },
130
+ )
131
+ : presetDefaults.selfShadowAlbedoMulStrength,
132
+ selfShadowAreaHSVStrength: enabled
133
+ ? numberOption(
134
+ firstDefined(source, [
135
+ 'selfShadowAreaHSVStrength',
136
+ 'shadowHSVStrength',
137
+ 'hsvStrength',
138
+ ]),
139
+ presetDefaults.selfShadowAreaHSVStrength,
140
+ { min: 0, max: 1 },
141
+ )
142
+ : 0,
143
+ selfShadowAreaHueOffset: enabled
144
+ ? numberOption(
145
+ firstDefined(source, [
146
+ 'selfShadowAreaHueOffset',
147
+ 'shadowHueOffset',
148
+ 'hueOffset',
149
+ ]),
150
+ presetDefaults.selfShadowAreaHueOffset,
151
+ { min: -1, max: 1 },
152
+ )
153
+ : presetDefaults.selfShadowAreaHueOffset,
154
+ selfShadowAreaSaturationBoost: enabled
155
+ ? numberOption(
156
+ firstDefined(source, [
157
+ 'selfShadowAreaSaturationBoost',
158
+ 'shadowSaturationBoost',
159
+ 'saturationBoost',
160
+ ]),
161
+ presetDefaults.selfShadowAreaSaturationBoost,
162
+ { min: 0, max: 1 },
163
+ )
164
+ : 0,
165
+ selfShadowAreaValueMul: enabled
166
+ ? numberOption(
167
+ firstDefined(source, [
168
+ 'selfShadowAreaValueMul',
169
+ 'shadowValueMul',
170
+ 'valueMul',
171
+ ]),
172
+ presetDefaults.selfShadowAreaValueMul,
173
+ { min: 0, max: 1 },
174
+ )
175
+ : 1,
176
+ selfShadowTintColor: colorOption(
177
+ firstDefined(source, [
178
+ 'selfShadowTintColor',
179
+ 'shadowTintColor',
180
+ 'shadowTint',
181
+ ]),
182
+ presetDefaults.selfShadowTintColor,
183
+ ),
184
+ transitionAreaHueOffset: enabled
185
+ ? numberOption(
186
+ firstDefined(source, [
187
+ 'transitionAreaHueOffset',
188
+ 'litToShadowTransitionAreaHueOffset',
189
+ 'transitionHueOffset',
190
+ ]),
191
+ presetDefaults.transitionAreaHueOffset,
192
+ { min: -1, max: 1 },
193
+ )
194
+ : presetDefaults.transitionAreaHueOffset,
195
+ transitionAreaIntensity: enabled
196
+ ? numberOption(
197
+ firstDefined(source, [
198
+ 'transitionAreaIntensity',
199
+ 'litToShadowTransitionAreaIntensity',
200
+ 'transitionIntensity',
201
+ ]),
202
+ presetDefaults.transitionAreaIntensity,
203
+ { min: 0, max: 32 },
204
+ )
205
+ : 0,
206
+ transitionAreaSaturationBoost: enabled
207
+ ? numberOption(
208
+ firstDefined(source, [
209
+ 'transitionAreaSaturationBoost',
210
+ 'litToShadowTransitionAreaSaturationBoost',
211
+ 'transitionSaturationBoost',
212
+ ]),
213
+ presetDefaults.transitionAreaSaturationBoost,
214
+ { min: 0, max: 1 },
215
+ )
216
+ : 0,
217
+ transitionAreaTintColor: colorOption(
218
+ firstDefined(source, [
219
+ 'transitionAreaTintColor',
220
+ 'litToShadowTransitionAreaTintColor',
221
+ 'transitionTintColor',
222
+ 'transitionTint',
223
+ ]),
224
+ presetDefaults.transitionAreaTintColor,
225
+ ),
226
+ transitionAreaValueMul: enabled
227
+ ? numberOption(
228
+ firstDefined(source, [
229
+ 'transitionAreaValueMul',
230
+ 'litToShadowTransitionAreaValueMul',
231
+ 'transitionValueMul',
232
+ ]),
233
+ presetDefaults.transitionAreaValueMul,
234
+ { min: 0, max: 1 },
235
+ )
236
+ : 1,
237
+ };
238
+ }