@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.
- package/ATTRIBUTION.md +40 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/package.json +97 -0
- package/src/character/characterRig.js +353 -0
- package/src/character/freestyleSwimClip.js +541 -0
- package/src/character/index.js +4 -0
- package/src/character/modelLoader.js +433 -0
- package/src/core/materialRoles.js +438 -0
- package/src/core/presetDocuments.js +110 -0
- package/src/core/shaderBackend.js +24 -0
- package/src/debrisgen/debrisFields.js +544 -0
- package/src/debrisgen/debrisGenerator.js +1929 -0
- package/src/debrisgen/debrisPalettes.js +71 -0
- package/src/debrisgen/debrisPhysics.js +198 -0
- package/src/debrisgen/debrisPresets.js +155 -0
- package/src/debrisgen/debrisSettings.js +333 -0
- package/src/debrisgen/debrisTextures.js +380 -0
- package/src/debrisgen/index.js +5 -0
- package/src/debug/fieldValues.js +95 -0
- package/src/debug/index.js +7 -0
- package/src/debug/settingsPanel.js +170 -0
- package/src/environment/environmentAmbientProbe.js +160 -0
- package/src/environment/environmentMaterialAdapter.js +336 -0
- package/src/environment/environmentMaterialClassifier.js +178 -0
- package/src/environment/environmentPlanarReflection.js +201 -0
- package/src/environment/environmentPresets.js +443 -0
- package/src/environment/environmentRigs.js +684 -0
- package/src/environment/environmentSettings.js +504 -0
- package/src/environment/environmentShaderMaterials.js +318 -0
- package/src/environment/environmentSunShadowPass.js +268 -0
- package/src/environment/environmentTextureResolver.js +161 -0
- package/src/environment/environmentTimeOfDay.js +167 -0
- package/src/environment/environmentVertexAo.js +188 -0
- package/src/environment/index.js +8 -0
- package/src/environment/scanAssetStylize.js +103 -0
- package/src/index.js +21 -0
- package/src/loaders/index.js +1 -0
- package/src/post/index.js +2 -0
- package/src/post/postProcessing.js +1087 -0
- package/src/rockgen/export/glbExport.js +177 -0
- package/src/rockgen/heightfield/heightfieldErosion.js +7 -0
- package/src/rockgen/heightfield/heightfieldPatch.js +183 -0
- package/src/rockgen/heightfield/stylizedErosionSim.js +356 -0
- package/src/rockgen/index.js +14 -0
- package/src/rockgen/mesh/meshAttributes.js +374 -0
- package/src/rockgen/mesh/meshDocument.js +158 -0
- package/src/rockgen/mesh/surfaceNets.js +329 -0
- package/src/rockgen/noise/cellularNoise3.js +92 -0
- package/src/rockgen/noise/prng.js +62 -0
- package/src/rockgen/noise/simplexNoise3.js +89 -0
- package/src/rockgen/noise/valueNoise3.js +87 -0
- package/src/rockgen/rockDocument.js +255 -0
- package/src/rockgen/rockHelpers.js +8 -0
- package/src/rockgen/rockgenPresets.js +559 -0
- package/src/rockgen/rockgenSettings.js +756 -0
- package/src/rockgen/sdf/fieldCompiler.js +544 -0
- package/src/rockgen/sdf/sculptEdits.js +66 -0
- package/src/rockgen/sdf/sdfModifiers.js +45 -0
- package/src/rockgen/sdf/sdfOps.js +37 -0
- package/src/rockgen/sdf/sdfPrimitives.js +88 -0
- package/src/shaders-tsl/anime.js +963 -0
- package/src/shaders-tsl/chunks/character-color.js +64 -0
- package/src/shaders-tsl/chunks/character-highlights.js +174 -0
- package/src/shaders-tsl/chunks/character-lighting.js +309 -0
- package/src/shaders-tsl/chunks/character-material-maps.js +146 -0
- package/src/shaders-tsl/chunks/character-roles.js +52 -0
- package/src/shaders-tsl/chunks/character-scene-lights.js +270 -0
- package/src/shaders-tsl/chunks/character-shadow-color.js +61 -0
- package/src/shaders-tsl/chunks/character-skinning.js +140 -0
- package/src/shaders-tsl/chunks/environment-color.js +47 -0
- package/src/shaders-tsl/chunks/environment-debug.js +79 -0
- package/src/shaders-tsl/chunks/environment-lighting.js +260 -0
- package/src/shaders-tsl/chunks/environment-sun-shadow.js +87 -0
- package/src/shaders-tsl/chunks/foliage-fog.js +60 -0
- package/src/shaders-tsl/chunks/pass-depth-color.js +89 -0
- package/src/shaders-tsl/chunks/stylized-cloud-shadow.js +70 -0
- package/src/shaders-tsl/chunks/water-color.js +190 -0
- package/src/shaders-tsl/chunks/water-common.js +124 -0
- package/src/shaders-tsl/chunks/water-foam.js +93 -0
- package/src/shaders-tsl/chunks/water-lighting.js +116 -0
- package/src/shaders-tsl/chunks/water-ripple.js +64 -0
- package/src/shaders-tsl/chunks/water-waves.js +100 -0
- package/src/shaders-tsl/environment-ao-overlay.js +60 -0
- package/src/shaders-tsl/environment.js +680 -0
- package/src/shaders-tsl/flower.js +267 -0
- package/src/shaders-tsl/grass.js +193 -0
- package/src/shaders-tsl/post-composite.js +461 -0
- package/src/shaders-tsl/sky.js +175 -0
- package/src/shaders-tsl/tree-leaf.js +264 -0
- package/src/shaders-tsl/water-breaker.js +423 -0
- package/src/shaders-tsl/water-kelp.js +171 -0
- package/src/shaders-tsl/water-rain.js +107 -0
- package/src/shaders-tsl/water-simulation.js +140 -0
- package/src/shaders-tsl/water-splash.js +298 -0
- package/src/shaders-tsl/water.js +509 -0
- package/src/sky/index.js +2 -0
- package/src/sky/stylizedSky.js +341 -0
- package/src/toon/characterRenderPasses.js +646 -0
- package/src/toon/index.js +4 -0
- package/src/toon/settings/alphaSettings.js +170 -0
- package/src/toon/settings/averageShadowSettings.js +181 -0
- package/src/toon/settings/baseTextureSettings.js +133 -0
- package/src/toon/settings/celShadeSettings.js +96 -0
- package/src/toon/settings/contactShadowSettings.js +114 -0
- package/src/toon/settings/eyeHighlightSettings.js +187 -0
- package/src/toon/settings/faceLightingSettings.js +135 -0
- package/src/toon/settings/furSettings.js +112 -0
- package/src/toon/settings/glitterSettings.js +102 -0
- package/src/toon/settings/hairHighlightSettings.js +325 -0
- package/src/toon/settings/indirectLightSettings.js +244 -0
- package/src/toon/settings/localLightSettings.js +176 -0
- package/src/toon/settings/materialMapSettings.js +299 -0
- package/src/toon/settings/outlineSettings.js +263 -0
- package/src/toon/settings/perspectiveRemovalSettings.js +60 -0
- package/src/toon/settings/rimLightSettings.js +268 -0
- package/src/toon/settings/sceneShadowSettings.js +147 -0
- package/src/toon/settings/selfShadowSettings.js +221 -0
- package/src/toon/settings/shadowColorSettings.js +238 -0
- package/src/toon/settings/skinToneSettings.js +150 -0
- package/src/toon/settings/specularSettings.js +314 -0
- package/src/toon/settings/stickerSettings.js +102 -0
- package/src/toon/toonMaterialAdapter.js +2235 -0
- package/src/toon/toonSettings.js +860 -0
- package/src/vegetation/flowerSpecies.js +260 -0
- package/src/vegetation/index.js +10 -0
- package/src/vegetation/stylizedBush.js +136 -0
- package/src/vegetation/stylizedFlower.js +213 -0
- package/src/vegetation/stylizedFlowers.js +297 -0
- package/src/vegetation/stylizedGrass.js +491 -0
- package/src/vegetation/stylizedTree.js +2449 -0
- package/src/vegetation/stylizedTreeFoliage.js +691 -0
- package/src/vegetation/treeExport.js +300 -0
- package/src/vegetation/treeRecipe.js +647 -0
- package/src/water/index.js +2 -0
- package/src/water/water.js +20 -0
- package/src/water/waterBreakerSystem.js +591 -0
- package/src/water/waterInteraction.js +193 -0
- package/src/water/waterMaterial.js +263 -0
- package/src/water/waterRain.js +136 -0
- package/src/water/waterRippleSimulation.js +227 -0
- package/src/water/waterScenePasses.js +415 -0
- package/src/water/waterSettings.js +1138 -0
- package/src/water/waterSplashSystem.js +251 -0
- package/src/water/waterSurface.js +475 -0
- package/src/water/waterVegetation.js +83 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
export const DEFAULT_HAIR_HIGHLIGHT_SETTINGS = Object.freeze({
|
|
2
|
+
direction: [0, 1, 0.15],
|
|
3
|
+
enabled: true,
|
|
4
|
+
intensity: 0.14,
|
|
5
|
+
maskChannel: 0,
|
|
6
|
+
maskMap: null,
|
|
7
|
+
maskStrength: 1,
|
|
8
|
+
mode: 'legacy',
|
|
9
|
+
shadowFloor: 0.35,
|
|
10
|
+
sideBandPower: 2,
|
|
11
|
+
sourceMaskMode: 'off',
|
|
12
|
+
strandPower: 7,
|
|
13
|
+
uvBandAxis: 0,
|
|
14
|
+
uvBandCenter: 0.5,
|
|
15
|
+
uvBandHalfWidth: 0.5,
|
|
16
|
+
uvPreset: 'center',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const HAIR_HIGHLIGHT_MASK_CHANNELS = Object.freeze({
|
|
20
|
+
r: 0,
|
|
21
|
+
red: 0,
|
|
22
|
+
x: 0,
|
|
23
|
+
g: 1,
|
|
24
|
+
green: 1,
|
|
25
|
+
y: 1,
|
|
26
|
+
b: 2,
|
|
27
|
+
blue: 2,
|
|
28
|
+
z: 2,
|
|
29
|
+
a: 3,
|
|
30
|
+
alpha: 3,
|
|
31
|
+
w: 3,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const HAIR_HIGHLIGHT_MODES = Object.freeze({
|
|
35
|
+
anisotropic: 'anisotropic',
|
|
36
|
+
legacy: 'legacy',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const HAIR_HIGHLIGHT_MODE_VALUES = Object.freeze({
|
|
40
|
+
[HAIR_HIGHLIGHT_MODES.legacy]: 0,
|
|
41
|
+
[HAIR_HIGHLIGHT_MODES.anisotropic]: 1,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const HAIR_HIGHLIGHT_SOURCE_MASK_MODES = Object.freeze({
|
|
45
|
+
off: 'off',
|
|
46
|
+
source: 'source',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const UV_PRESETS = Object.freeze({
|
|
50
|
+
center: Object.freeze({ uvBandAxis: 0, uvBandCenter: 0.5, uvBandHalfWidth: 0.5 }),
|
|
51
|
+
full: Object.freeze({ uvBandAxis: 0, uvBandCenter: 0.5, uvBandHalfWidth: 2.0 }),
|
|
52
|
+
left: Object.freeze({ uvBandAxis: 0, uvBandCenter: 0.25, uvBandHalfWidth: 0.35 }),
|
|
53
|
+
right: Object.freeze({ uvBandAxis: 0, uvBandCenter: 0.75, uvBandHalfWidth: 0.35 }),
|
|
54
|
+
vertical: Object.freeze({ uvBandAxis: 1, uvBandCenter: 0.5, uvBandHalfWidth: 0.5 }),
|
|
55
|
+
wide: Object.freeze({ uvBandAxis: 0, uvBandCenter: 0.5, uvBandHalfWidth: 0.75 }),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function firstDefined(source, keys) {
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
if (source?.[key] !== undefined) return source[key];
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function numberOption(value, fallback, { min = -Infinity, max = Infinity } = {}) {
|
|
66
|
+
const nextValue = Number(value);
|
|
67
|
+
if (!Number.isFinite(nextValue)) return fallback;
|
|
68
|
+
return Math.min(max, Math.max(min, nextValue));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function vectorOption(value, fallback) {
|
|
72
|
+
if (Array.isArray(value) && value.length >= 3) {
|
|
73
|
+
return [
|
|
74
|
+
numberOption(value[0], fallback[0]),
|
|
75
|
+
numberOption(value[1], fallback[1]),
|
|
76
|
+
numberOption(value[2], fallback[2]),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof value === 'object' && value !== null) {
|
|
81
|
+
return [
|
|
82
|
+
numberOption(value.x ?? value.r, fallback[0]),
|
|
83
|
+
numberOption(value.y ?? value.g, fallback[1]),
|
|
84
|
+
numberOption(value.z ?? value.b, fallback[2]),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return [...fallback];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function enabledOption(value) {
|
|
92
|
+
if (typeof value === 'string') {
|
|
93
|
+
const normalized = value.trim().toLowerCase();
|
|
94
|
+
return normalized !== 'off' && normalized !== 'none' && normalized !== 'false' && normalized !== '0';
|
|
95
|
+
}
|
|
96
|
+
return value !== false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeHairHighlightOptions(options) {
|
|
100
|
+
if (options === false) return { enabled: false };
|
|
101
|
+
if (options === true) return { enabled: true };
|
|
102
|
+
if (typeof options === 'string') return { enabled: enabledOption(options) };
|
|
103
|
+
return options || {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeMaskChannel(value, fallback = DEFAULT_HAIR_HIGHLIGHT_SETTINGS.maskChannel) {
|
|
107
|
+
if (Number.isFinite(value)) return Math.min(3, Math.max(0, Math.round(value)));
|
|
108
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
109
|
+
return HAIR_HIGHLIGHT_MASK_CHANNELS[normalized] ?? fallback;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function normalizeMode(value, fallback = DEFAULT_HAIR_HIGHLIGHT_SETTINGS.mode) {
|
|
113
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
114
|
+
if (
|
|
115
|
+
normalized === 'aniso' ||
|
|
116
|
+
normalized === 'anisotropic' ||
|
|
117
|
+
normalized === 'kajiyakay' ||
|
|
118
|
+
normalized === 'kajiya-kay' ||
|
|
119
|
+
normalized === 'strand' ||
|
|
120
|
+
normalized === 'strand-highlight' ||
|
|
121
|
+
normalized === 'strand_highlight'
|
|
122
|
+
) {
|
|
123
|
+
return HAIR_HIGHLIGHT_MODES.anisotropic;
|
|
124
|
+
}
|
|
125
|
+
return HAIR_HIGHLIGHT_MODES.legacy;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizeSourceMaskMode(value, fallback = DEFAULT_HAIR_HIGHLIGHT_SETTINGS.sourceMaskMode) {
|
|
129
|
+
if (value === true) return HAIR_HIGHLIGHT_SOURCE_MASK_MODES.source;
|
|
130
|
+
if (value === false) return HAIR_HIGHLIGHT_SOURCE_MASK_MODES.off;
|
|
131
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
132
|
+
if (normalized === 'material' || normalized === 'map' || normalized === 'source') {
|
|
133
|
+
return HAIR_HIGHLIGHT_SOURCE_MASK_MODES.source;
|
|
134
|
+
}
|
|
135
|
+
return HAIR_HIGHLIGHT_SOURCE_MASK_MODES.off;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function normalizeUvAxis(value, fallback = DEFAULT_HAIR_HIGHLIGHT_SETTINGS.uvBandAxis) {
|
|
139
|
+
if (Number.isFinite(value)) return Math.min(1, Math.max(0, Math.round(value)));
|
|
140
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
141
|
+
if (normalized === 'v' || normalized === 'y' || normalized === 'vertical') return 1;
|
|
142
|
+
if (normalized === 'u' || normalized === 'x' || normalized === 'horizontal') return 0;
|
|
143
|
+
return fallback;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function normalizeUvPreset(value, fallback = DEFAULT_HAIR_HIGHLIGHT_SETTINGS.uvPreset) {
|
|
147
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
148
|
+
return UV_PRESETS[normalized] ? normalized : fallback;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeMaterialPresets(value) {
|
|
152
|
+
const source = value || {};
|
|
153
|
+
return {
|
|
154
|
+
byName: source.byName || {},
|
|
155
|
+
byUuid: source.byUuid || {},
|
|
156
|
+
patterns: Array.isArray(source.patterns) ? source.patterns : [],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeCoreSettings(source, fallback = DEFAULT_HAIR_HIGHLIGHT_SETTINGS) {
|
|
161
|
+
const enabled = source.enabled === undefined ? fallback.enabled : enabledOption(source.enabled);
|
|
162
|
+
const uvPreset = normalizeUvPreset(firstDefined(source, ['uvPreset', 'preset']), fallback.uvPreset);
|
|
163
|
+
const presetValues = UV_PRESETS[uvPreset] || UV_PRESETS.center;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
direction: vectorOption(
|
|
167
|
+
firstDefined(source, ['direction', 'highlightDirection', 'strandDirection']),
|
|
168
|
+
fallback.direction,
|
|
169
|
+
),
|
|
170
|
+
enabled,
|
|
171
|
+
maskChannel: normalizeMaskChannel(
|
|
172
|
+
firstDefined(source, ['maskChannel', 'hairHighlightMaskChannel']),
|
|
173
|
+
fallback.maskChannel,
|
|
174
|
+
),
|
|
175
|
+
maskMap: firstDefined(source, ['maskMap', 'hairHighlightMaskMap']) ?? fallback.maskMap,
|
|
176
|
+
maskStrength: enabled
|
|
177
|
+
? numberOption(
|
|
178
|
+
firstDefined(source, ['maskStrength', 'hairHighlightMaskStrength']),
|
|
179
|
+
fallback.maskStrength,
|
|
180
|
+
{ min: 0, max: 1 },
|
|
181
|
+
)
|
|
182
|
+
: 0,
|
|
183
|
+
mode: normalizeMode(firstDefined(source, ['mode', 'highlightMode', 'strandMode']), fallback.mode),
|
|
184
|
+
intensity: enabled
|
|
185
|
+
? numberOption(
|
|
186
|
+
firstDefined(source, ['intensity', 'hairIntensity', 'hairHighlightIntensity']),
|
|
187
|
+
fallback.intensity,
|
|
188
|
+
{ min: 0, max: 8 },
|
|
189
|
+
)
|
|
190
|
+
: 0,
|
|
191
|
+
shadowFloor: enabled
|
|
192
|
+
? numberOption(
|
|
193
|
+
firstDefined(source, ['shadowFloor', 'shadowLift', 'minimumShadowLight']),
|
|
194
|
+
fallback.shadowFloor,
|
|
195
|
+
{ min: 0, max: 1 },
|
|
196
|
+
)
|
|
197
|
+
: fallback.shadowFloor,
|
|
198
|
+
sideBandPower: enabled
|
|
199
|
+
? numberOption(
|
|
200
|
+
firstDefined(source, ['sideBandPower', 'uvBandPower', 'bandPower']),
|
|
201
|
+
fallback.sideBandPower,
|
|
202
|
+
{ min: 0.001, max: 128 },
|
|
203
|
+
)
|
|
204
|
+
: fallback.sideBandPower,
|
|
205
|
+
sourceMaskMode: normalizeSourceMaskMode(
|
|
206
|
+
firstDefined(source, ['sourceMaskMode', 'sourceMask', 'useSourceMask', 'useSourceHairHighlightMask']),
|
|
207
|
+
fallback.sourceMaskMode,
|
|
208
|
+
),
|
|
209
|
+
strandPower: enabled
|
|
210
|
+
? numberOption(
|
|
211
|
+
firstDefined(source, ['strandPower', 'power', 'highlightPower']),
|
|
212
|
+
fallback.strandPower,
|
|
213
|
+
{ min: 0.001, max: 512 },
|
|
214
|
+
)
|
|
215
|
+
: fallback.strandPower,
|
|
216
|
+
uvBandAxis: normalizeUvAxis(
|
|
217
|
+
firstDefined(source, ['uvBandAxis', 'uvAxis', 'bandAxis']),
|
|
218
|
+
presetValues.uvBandAxis ?? fallback.uvBandAxis,
|
|
219
|
+
),
|
|
220
|
+
uvBandCenter: enabled
|
|
221
|
+
? numberOption(
|
|
222
|
+
firstDefined(source, ['uvBandCenter', 'bandCenter']),
|
|
223
|
+
presetValues.uvBandCenter ?? fallback.uvBandCenter,
|
|
224
|
+
{ min: -2, max: 3 },
|
|
225
|
+
)
|
|
226
|
+
: presetValues.uvBandCenter ?? fallback.uvBandCenter,
|
|
227
|
+
uvBandHalfWidth: enabled
|
|
228
|
+
? numberOption(
|
|
229
|
+
firstDefined(source, ['uvBandHalfWidth', 'uvBandWidth', 'bandHalfWidth', 'bandWidth']),
|
|
230
|
+
presetValues.uvBandHalfWidth ?? fallback.uvBandHalfWidth,
|
|
231
|
+
{ min: 0.001, max: 4 },
|
|
232
|
+
)
|
|
233
|
+
: presetValues.uvBandHalfWidth ?? fallback.uvBandHalfWidth,
|
|
234
|
+
uvPreset,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function optionForMaterial(settings, material) {
|
|
239
|
+
if (!material) return null;
|
|
240
|
+
const materialName = material.name ?? material.sourceMaterialName ?? '';
|
|
241
|
+
const materialUuid = material.uuid ?? material.sourceMaterialUuid ?? '';
|
|
242
|
+
const text = [
|
|
243
|
+
materialName,
|
|
244
|
+
materialUuid,
|
|
245
|
+
material.userData?.sourceMaterialName,
|
|
246
|
+
material.userData?.materialRole,
|
|
247
|
+
material.userData?.materialRoleLabel,
|
|
248
|
+
].filter(Boolean).join(' ');
|
|
249
|
+
const presets = settings.materialPresets;
|
|
250
|
+
let option = null;
|
|
251
|
+
|
|
252
|
+
for (const entry of presets.patterns) {
|
|
253
|
+
const pattern = entry?.pattern;
|
|
254
|
+
if (!pattern) continue;
|
|
255
|
+
if (
|
|
256
|
+
(pattern instanceof RegExp && pattern.test(text)) ||
|
|
257
|
+
(typeof pattern === 'string' && text.toLowerCase().includes(pattern.toLowerCase()))
|
|
258
|
+
) {
|
|
259
|
+
option = { ...option, ...entry };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (materialName && presets.byName[materialName]) option = { ...option, ...presets.byName[materialName] };
|
|
264
|
+
if (materialUuid && presets.byUuid[materialUuid]) option = { ...option, ...presets.byUuid[materialUuid] };
|
|
265
|
+
|
|
266
|
+
const userDataOption = material.userData?.toonHairHighlight ??
|
|
267
|
+
material.userData?.hairHighlight ??
|
|
268
|
+
material.userData?.hairHighlightSettings;
|
|
269
|
+
if (userDataOption) option = { ...option, ...userDataOption };
|
|
270
|
+
|
|
271
|
+
return option;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function sourceMaskMapForMaterial(material) {
|
|
275
|
+
return material?.userData?.toonHairHighlightMaskMap ??
|
|
276
|
+
material?.userData?.hairHighlightMaskMap ??
|
|
277
|
+
material?.userData?.highlightMaskMap ??
|
|
278
|
+
null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function createHairHighlightSettings(options = null) {
|
|
282
|
+
const source = normalizeHairHighlightOptions(options);
|
|
283
|
+
return {
|
|
284
|
+
...normalizeCoreSettings(source),
|
|
285
|
+
materialPresets: normalizeMaterialPresets(
|
|
286
|
+
firstDefined(source, ['materialPresets', 'perMaterial', 'overrides']),
|
|
287
|
+
),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function resolveHairHighlightForMaterial(settings, {
|
|
292
|
+
isHair = false,
|
|
293
|
+
isOutline = false,
|
|
294
|
+
maskMap = null,
|
|
295
|
+
material = null,
|
|
296
|
+
} = {}) {
|
|
297
|
+
const materialOption = optionForMaterial(settings, material);
|
|
298
|
+
const resolvedSettings = materialOption
|
|
299
|
+
? normalizeCoreSettings(materialOption, settings)
|
|
300
|
+
: settings;
|
|
301
|
+
const materialSourceMask = resolvedSettings.sourceMaskMode === HAIR_HIGHLIGHT_SOURCE_MASK_MODES.source
|
|
302
|
+
? sourceMaskMapForMaterial(material)
|
|
303
|
+
: null;
|
|
304
|
+
const resolvedMaskMap = resolvedSettings.maskMap ?? maskMap ?? materialSourceMask;
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
direction: resolvedSettings.direction,
|
|
308
|
+
enabled: resolvedSettings.enabled && isHair && !isOutline && resolvedSettings.intensity > 0,
|
|
309
|
+
intensity: resolvedSettings.intensity,
|
|
310
|
+
maskChannel: resolvedSettings.maskChannel,
|
|
311
|
+
maskMap: resolvedMaskMap,
|
|
312
|
+
maskStrength: resolvedSettings.maskStrength,
|
|
313
|
+
mode: resolvedSettings.mode,
|
|
314
|
+
modeValue: HAIR_HIGHLIGHT_MODE_VALUES[resolvedSettings.mode] ?? HAIR_HIGHLIGHT_MODE_VALUES.legacy,
|
|
315
|
+
shadowFloor: resolvedSettings.shadowFloor,
|
|
316
|
+
sideBandPower: resolvedSettings.sideBandPower,
|
|
317
|
+
sourceMaskMode: resolvedSettings.sourceMaskMode,
|
|
318
|
+
strandPower: resolvedSettings.strandPower,
|
|
319
|
+
useMask: Boolean(resolvedMaskMap) && resolvedSettings.maskStrength > 0,
|
|
320
|
+
uvBandAxis: resolvedSettings.uvBandAxis,
|
|
321
|
+
uvBandCenter: resolvedSettings.uvBandCenter,
|
|
322
|
+
uvBandHalfWidth: resolvedSettings.uvBandHalfWidth,
|
|
323
|
+
uvPreset: resolvedSettings.uvPreset,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_INDIRECT_LIGHT_SETTINGS = Object.freeze({
|
|
4
|
+
ambientTint: [0.86, 0.91, 1.0],
|
|
5
|
+
defaultIntensity: 0.35,
|
|
6
|
+
defaultMinimumIndirectLight: 0.35,
|
|
7
|
+
enabled: true,
|
|
8
|
+
environmentIndirectLight: 0.56,
|
|
9
|
+
eyeIntensity: 0.35,
|
|
10
|
+
eyeMinimumIndirectLight: 0.35,
|
|
11
|
+
faceIntensity: 0.35,
|
|
12
|
+
faceMinimumIndirectLight: null,
|
|
13
|
+
hairIntensity: 0.35,
|
|
14
|
+
hairMinimumIndirectLight: 0.35,
|
|
15
|
+
hemisphereLightIntensity: 0.42,
|
|
16
|
+
skinIntensity: 0.35,
|
|
17
|
+
skinMinimumIndirectLight: null,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function firstDefined(source, keys) {
|
|
21
|
+
for (const key of keys) {
|
|
22
|
+
if (source?.[key] !== undefined) return source[key];
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function numberOption(value, fallback, { min = -Infinity, max = Infinity } = {}) {
|
|
28
|
+
const nextValue = Number(value);
|
|
29
|
+
if (!Number.isFinite(nextValue)) return fallback;
|
|
30
|
+
return Math.min(max, Math.max(min, nextValue));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nullableNumberOption(value, fallback, bounds) {
|
|
34
|
+
if (value === null) return null;
|
|
35
|
+
if (value === undefined) return fallback;
|
|
36
|
+
return numberOption(value, fallback, bounds);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function colorOption(value, fallback) {
|
|
40
|
+
if (value?.isColor) return value.clone();
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(value) && value.length >= 3) {
|
|
43
|
+
return new THREE.Color(
|
|
44
|
+
numberOption(value[0], fallback[0] ?? fallback.r ?? 1),
|
|
45
|
+
numberOption(value[1], fallback[1] ?? fallback.g ?? 1),
|
|
46
|
+
numberOption(value[2], fallback[2] ?? fallback.b ?? 1),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof value === 'object' && value !== null) {
|
|
51
|
+
return new THREE.Color(
|
|
52
|
+
numberOption(value.r ?? value.x, fallback[0] ?? fallback.r ?? 1),
|
|
53
|
+
numberOption(value.g ?? value.y, fallback[1] ?? fallback.g ?? 1),
|
|
54
|
+
numberOption(value.b ?? value.z, fallback[2] ?? fallback.b ?? 1),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
59
|
+
try {
|
|
60
|
+
return new THREE.Color(value);
|
|
61
|
+
} catch {
|
|
62
|
+
// Fall through to fallback.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return Array.isArray(fallback)
|
|
67
|
+
? new THREE.Color(fallback[0], fallback[1], fallback[2])
|
|
68
|
+
: fallback.clone();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function enabledOption(value) {
|
|
72
|
+
if (typeof value === 'string') {
|
|
73
|
+
const normalized = value.trim().toLowerCase();
|
|
74
|
+
return normalized !== 'off' && normalized !== 'none' && normalized !== 'false' && normalized !== '0';
|
|
75
|
+
}
|
|
76
|
+
return value !== false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeIndirectLightOptions(options) {
|
|
80
|
+
if (options === false) return { enabled: false };
|
|
81
|
+
if (options === true) return { enabled: true };
|
|
82
|
+
if (typeof options === 'string') return { enabled: enabledOption(options) };
|
|
83
|
+
return options || {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function roleNumber(source, role, property, fallback, aliases = []) {
|
|
87
|
+
const rolePrefix = role ? `${role}${property[0].toUpperCase()}${property.slice(1)}` : property;
|
|
88
|
+
return numberOption(
|
|
89
|
+
firstDefined(source, [rolePrefix, ...aliases]),
|
|
90
|
+
fallback,
|
|
91
|
+
{ min: 0, max: property === 'minimumIndirectLight' ? 1 : 8 },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function nullableRoleNumber(source, role, property, fallback, aliases = []) {
|
|
96
|
+
const rolePrefix = role ? `${role}${property[0].toUpperCase()}${property.slice(1)}` : property;
|
|
97
|
+
return nullableNumberOption(
|
|
98
|
+
firstDefined(source, [rolePrefix, ...aliases]),
|
|
99
|
+
fallback,
|
|
100
|
+
{ min: 0, max: property === 'minimumIndirectLight' ? 1 : 8 },
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createIndirectLightSettings(options = null) {
|
|
105
|
+
const source = normalizeIndirectLightOptions(options);
|
|
106
|
+
const enabled = enabledOption(source.enabled);
|
|
107
|
+
const explicitDefaultMinimum = firstDefined(source, [
|
|
108
|
+
'defaultMinimumIndirectLight',
|
|
109
|
+
'minimumIndirectLight',
|
|
110
|
+
'minIndirectLight',
|
|
111
|
+
'indirectLightFloor',
|
|
112
|
+
]) !== undefined;
|
|
113
|
+
const defaultIntensity = enabled
|
|
114
|
+
? roleNumber(
|
|
115
|
+
source,
|
|
116
|
+
'default',
|
|
117
|
+
'intensity',
|
|
118
|
+
DEFAULT_INDIRECT_LIGHT_SETTINGS.defaultIntensity,
|
|
119
|
+
['intensity', 'indirectLightIntensity'],
|
|
120
|
+
)
|
|
121
|
+
: 0;
|
|
122
|
+
const defaultMinimumIndirectLight = enabled
|
|
123
|
+
? roleNumber(
|
|
124
|
+
source,
|
|
125
|
+
'default',
|
|
126
|
+
'minimumIndirectLight',
|
|
127
|
+
DEFAULT_INDIRECT_LIGHT_SETTINGS.defaultMinimumIndirectLight,
|
|
128
|
+
['minimumIndirectLight', 'minIndirectLight', 'indirectLightFloor'],
|
|
129
|
+
)
|
|
130
|
+
: 0;
|
|
131
|
+
const skinFaceMinimumFallback = explicitDefaultMinimum ? defaultMinimumIndirectLight : null;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
ambientTint: colorOption(
|
|
135
|
+
firstDefined(source, ['ambientTint', 'indirectTint', 'indirectLightTint']),
|
|
136
|
+
DEFAULT_INDIRECT_LIGHT_SETTINGS.ambientTint,
|
|
137
|
+
),
|
|
138
|
+
defaultIntensity,
|
|
139
|
+
defaultMinimumIndirectLight,
|
|
140
|
+
enabled,
|
|
141
|
+
environmentIndirectLight: enabled
|
|
142
|
+
? numberOption(
|
|
143
|
+
firstDefined(source, ['environmentIndirectLight', 'environmentLight', 'indirectEnvironmentLight']),
|
|
144
|
+
DEFAULT_INDIRECT_LIGHT_SETTINGS.environmentIndirectLight,
|
|
145
|
+
{ min: 0, max: 8 },
|
|
146
|
+
)
|
|
147
|
+
: 0,
|
|
148
|
+
eyeIntensity: enabled
|
|
149
|
+
? roleNumber(source, 'eye', 'intensity', defaultIntensity)
|
|
150
|
+
: 0,
|
|
151
|
+
eyeMinimumIndirectLight: enabled
|
|
152
|
+
? nullableRoleNumber(
|
|
153
|
+
source,
|
|
154
|
+
'eye',
|
|
155
|
+
'minimumIndirectLight',
|
|
156
|
+
defaultMinimumIndirectLight,
|
|
157
|
+
)
|
|
158
|
+
: 0,
|
|
159
|
+
faceIntensity: enabled
|
|
160
|
+
? roleNumber(source, 'face', 'intensity', defaultIntensity)
|
|
161
|
+
: 0,
|
|
162
|
+
faceMinimumIndirectLight: enabled
|
|
163
|
+
? nullableRoleNumber(
|
|
164
|
+
source,
|
|
165
|
+
'face',
|
|
166
|
+
'minimumIndirectLight',
|
|
167
|
+
skinFaceMinimumFallback,
|
|
168
|
+
['faceMinIndirectLight'],
|
|
169
|
+
)
|
|
170
|
+
: 0,
|
|
171
|
+
hairIntensity: enabled
|
|
172
|
+
? roleNumber(source, 'hair', 'intensity', defaultIntensity)
|
|
173
|
+
: 0,
|
|
174
|
+
hairMinimumIndirectLight: enabled
|
|
175
|
+
? nullableRoleNumber(
|
|
176
|
+
source,
|
|
177
|
+
'hair',
|
|
178
|
+
'minimumIndirectLight',
|
|
179
|
+
defaultMinimumIndirectLight,
|
|
180
|
+
)
|
|
181
|
+
: 0,
|
|
182
|
+
hemisphereLightIntensity: enabled
|
|
183
|
+
? numberOption(
|
|
184
|
+
firstDefined(source, ['hemisphereLightIntensity', 'hemisphereIntensity', 'hemiIntensity']),
|
|
185
|
+
DEFAULT_INDIRECT_LIGHT_SETTINGS.hemisphereLightIntensity,
|
|
186
|
+
{ min: 0, max: 8 },
|
|
187
|
+
)
|
|
188
|
+
: 0,
|
|
189
|
+
skinIntensity: enabled
|
|
190
|
+
? roleNumber(source, 'skin', 'intensity', defaultIntensity)
|
|
191
|
+
: 0,
|
|
192
|
+
skinMinimumIndirectLight: enabled
|
|
193
|
+
? nullableRoleNumber(
|
|
194
|
+
source,
|
|
195
|
+
'skin',
|
|
196
|
+
'minimumIndirectLight',
|
|
197
|
+
skinFaceMinimumFallback,
|
|
198
|
+
['skinMinIndirectLight'],
|
|
199
|
+
)
|
|
200
|
+
: 0,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function resolveIndirectLightForMaterial(settings, {
|
|
205
|
+
faceMinimumIndirectLightFallback = DEFAULT_INDIRECT_LIGHT_SETTINGS.defaultMinimumIndirectLight,
|
|
206
|
+
isEye = false,
|
|
207
|
+
isFace = false,
|
|
208
|
+
isHair = false,
|
|
209
|
+
isSkin = false,
|
|
210
|
+
skinMinimumIndirectLightFallback = DEFAULT_INDIRECT_LIGHT_SETTINGS.defaultMinimumIndirectLight,
|
|
211
|
+
} = {}) {
|
|
212
|
+
if (isEye) {
|
|
213
|
+
return {
|
|
214
|
+
intensity: settings.eyeIntensity,
|
|
215
|
+
minimumIndirectLight: settings.eyeMinimumIndirectLight ?? settings.defaultMinimumIndirectLight,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (isFace) {
|
|
220
|
+
return {
|
|
221
|
+
intensity: settings.faceIntensity,
|
|
222
|
+
minimumIndirectLight: settings.faceMinimumIndirectLight ?? faceMinimumIndirectLightFallback,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (isSkin) {
|
|
227
|
+
return {
|
|
228
|
+
intensity: settings.skinIntensity,
|
|
229
|
+
minimumIndirectLight: settings.skinMinimumIndirectLight ?? skinMinimumIndirectLightFallback,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (isHair) {
|
|
234
|
+
return {
|
|
235
|
+
intensity: settings.hairIntensity,
|
|
236
|
+
minimumIndirectLight: settings.hairMinimumIndirectLight ?? settings.defaultMinimumIndirectLight,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
intensity: settings.defaultIntensity,
|
|
242
|
+
minimumIndirectLight: settings.defaultMinimumIndirectLight,
|
|
243
|
+
};
|
|
244
|
+
}
|