@call-me-sensei/toonlab 0.4.19 → 0.4.21
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/NPM-LIBRARY.md +230 -0
- package/README.md +78 -36
- package/agents/PROMPTS.md +91 -0
- package/agents/README.md +9 -14
- package/agents/claude/CLAUDE.md +8 -0
- package/agents/codex/AGENTS.md +161 -0
- package/agents/cursor/toonlab.mdc +37 -0
- package/agents/references/mcp-asset-discovery.md +12 -9
- package/agents/references/runtime-entry-points.md +5 -4
- package/agents/skills/claude/asset-sourcing/SKILL.md +9 -6
- package/agents/skills/claude/rock-ground-shaders/SKILL.md +16 -0
- package/agents/skills/claude/vegetation-sky/SKILL.md +7 -4
- package/agents/skills/codex/asset-sourcing/SKILL.md +9 -6
- package/agents/skills/codex/rock-ground-shaders/SKILL.md +16 -0
- package/agents/skills/codex/vegetation-sky/SKILL.md +7 -4
- package/cli/toonlab.mjs +3 -2
- package/database/fal-patina.mjs +131 -0
- package/database/generation-service.mjs +108 -7
- package/database/migrations/0007_creation_revisions.sql +80 -0
- package/database/migrations/0008_creation_revision_hardening.sql +98 -0
- package/database/migrations/0009_creation_revision_delete_integrity.sql +11 -0
- package/database/migrations/0010_external_catalog_assets.sql +28 -0
- package/database/providers.mjs +195 -16
- package/database/repository.mjs +518 -70
- package/database/seeds/catalog/0003_2026-08-open-assets.sql +159086 -0
- package/database/seeds/catalog/0004_2026-08-c7-rocks.sql +39405 -0
- package/mcp/public-catalog.mjs +4 -52
- package/mcp/server.mjs +175 -12
- package/mcp/vite-plugin.mjs +78 -2
- package/package.json +32 -12
- package/scripts/generate-catalog-seed.mjs +34 -7
- package/src/asset-policy/catalogLicenses.js +64 -6
- package/src/assetlib/loadImported.js +15 -1
- package/src/catalog/officialCatalogAssetRuntime.js +13 -2
- package/src/catalog/officialCatalogLod.js +34 -1
- package/src/catalog/officialCatalogPlacement.js +29 -14
- package/src/character/animationRetarget.js +2 -0
- package/src/character/characterRig.js +6 -1
- package/src/character/characterRuntime.js +175 -7
- package/src/environment/environmentMaterialAdapter.js +30 -0
- package/src/environment/environmentPresets.js +90 -0
- package/src/environment/environmentSunShadowPass.js +42 -0
- package/src/environment/toonLabSurfaceLighting.js +108 -10
- package/src/environment/urbanPropMaterial.js +104 -2
- package/src/lighting/lightingSystem.js +65 -10
- package/src/renderer/index.js +7 -0
- package/src/renderer/styleComparison.js +985 -0
- package/src/rock-shader/index.js +5 -0
- package/src/rock-shader/rockGeometryDetail.js +701 -0
- package/src/rock-shader/rockMaterial.js +782 -39
- package/src/rock-shader/rockRegionRuntime.js +185 -0
- package/src/rock-shader/rockSemanticMaterialRuntime.js +337 -0
- package/src/rock-shader/rockShaderRuntime.js +354 -67
- package/src/rock-shader/rockShaderSettings.js +246 -16
- package/src/rock-shader/rockTangentIntegrity.js +136 -0
- package/src/rock-shader/rockTextureIntegrity.js +115 -0
- package/src/rockgen/index.js +1 -0
- package/src/rockgen/rockDocument.js +27 -3
- package/src/rockgen/surface/c7GeologySurface.js +372 -0
- package/src/shaders-tsl/water.js +8 -0
- package/src/sky/cloudShadow.js +25 -1
- package/src/sky/skySystem.js +9 -0
- package/src/styles/index.js +23 -0
- package/src/styles/neutralStylePresets.js +712 -0
- package/src/styles/sceneStyleRuntime.js +51 -4
- package/src/styles/styleAdapters.js +13 -2
- package/src/styles/styleBundle.js +1 -1
- package/src/texgen/evaluateTexture.js +30 -3
- package/src/texgen/textureGenerators.js +63 -31
- package/src/texgen/textureSettings.js +25 -8
- package/src/toon/toonSettings.js +96 -2
- package/src/vegetation/branchTree.js +327 -42
- package/src/vegetation/index.js +24 -2
- package/src/vegetation/scatter.js +372 -1
- package/src/vegetation/stylizedTree.js +312 -126
- package/src/vegetation/stylizedTreeFoliage.js +229 -7
- package/src/vegetation/treeSurfaceTextures.js +328 -0
- package/src/version.js +1 -1
- package/src/water/waterSettings.js +15 -7
- package/types/asset-policy/catalogLicenses.d.ts +2 -0
- package/types/asset-policy/index.d.ts +2 -0
- package/types/assetlib/loadImported.d.ts +3 -1
- package/types/catalog/officialCatalogLod.d.ts +63 -5
- package/types/character/characterRig.d.ts +59 -0
- package/types/character/characterRuntime.d.ts +1 -0
- package/types/environment/environmentMaterialAdapter.d.ts +3 -1
- package/types/environment/toonLabSurfaceLighting.d.ts +20 -3
- package/types/index.d.ts +89 -7
- package/types/lighting/lightingSystem.d.ts +7 -0
- package/types/react/index.d.ts +20 -0
- package/types/renderer/index.d.ts +1 -0
- package/types/renderer/styleComparison.d.ts +409 -0
- package/types/rock-shader/index.d.ts +5 -0
- package/types/rock-shader/rockGeometryDetail.d.ts +300 -0
- package/types/rock-shader/rockMaterial.d.ts +3 -0
- package/types/rock-shader/rockRegionRuntime.d.ts +86 -0
- package/types/rock-shader/rockSemanticMaterialRuntime.d.ts +29 -0
- package/types/rock-shader/rockShaderRuntime.d.ts +2 -0
- package/types/rock-shader/rockShaderSettings.d.ts +19 -5
- package/types/rock-shader/rockTangentIntegrity.d.ts +51 -0
- package/types/rock-shader/rockTextureIntegrity.d.ts +61 -0
- package/types/rockgen/index.d.ts +1 -0
- package/types/rockgen/rockDocument.d.ts +29 -1
- package/types/rockgen/surface/c7GeologySurface.d.ts +8 -0
- package/types/sky/cloudShadow.d.ts +1 -0
- package/types/styles/index.d.ts +1 -0
- package/types/styles/neutralStylePresets.d.ts +944 -0
- package/types/styles/sceneStyleRuntime.d.ts +20 -0
- package/types/vegetation/branchTree.d.ts +4 -0
- package/types/vegetation/index.d.ts +9 -7
- package/types/vegetation/scatter.d.ts +1 -0
- package/types/vegetation/stylizedTree.d.ts +1 -2
- package/types/vegetation/stylizedTreeFoliage.d.ts +1 -0
- package/types/version.d.ts +1 -1
- package/agents/references/geology-playbook.md +0 -118
- package/agents/skills/claude/karst-cliff-construction/SKILL.md +0 -258
- package/agents/skills/claude/outdoor-world/SKILL.md +0 -492
- package/agents/skills/claude/outdoor-world/references/coastal-landform-formation.md +0 -100
- package/agents/skills/codex/karst-cliff-construction/SKILL.md +0 -258
- package/agents/skills/codex/outdoor-world/SKILL.md +0 -492
- package/agents/skills/codex/outdoor-world/references/coastal-landform-formation.md +0 -100
- package/src/vegetation/legacyTreePresets.js +0 -120
- package/types/vegetation/legacyTreePresets.d.ts +0 -22
|
@@ -13,6 +13,11 @@ import { createSkyParams } from '../sky/skyParams.js';
|
|
|
13
13
|
import { environmentCloudShadow } from '../sky/cloudShadow.js';
|
|
14
14
|
import { PRESETS as SKY_PRESETS } from '../sky/skyPresets.js';
|
|
15
15
|
import { resolveSkySystemStyleSnapshot } from '../sky/skyStyleSnapshots.js';
|
|
16
|
+
import {
|
|
17
|
+
azimuthOf,
|
|
18
|
+
elevationOf,
|
|
19
|
+
sunDirectionAt,
|
|
20
|
+
} from '../sky/sunDriver.js';
|
|
16
21
|
import { applyStyleBundle } from './styleApplication.js';
|
|
17
22
|
import { applySceneQualityProfile } from './sceneQualityApplication.js';
|
|
18
23
|
import { createStyleTarget } from './styleAdapters.js';
|
|
@@ -51,6 +56,22 @@ function sunDirectionFromFrame(frame) {
|
|
|
51
56
|
return [x / length, y / length, z / length];
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
function alignSkyCloudShadowToLighting(sky, direction) {
|
|
60
|
+
sky?.setCloudShadowDirection?.(direction);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function skySunPoseForFrame(skyParams, frame) {
|
|
64
|
+
const direction = sunDirectionAt(
|
|
65
|
+
frame.hour / 24,
|
|
66
|
+
skyParams.time?.latitude,
|
|
67
|
+
skyParams.time?.azimuth,
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
azimuth: azimuthOf(direction),
|
|
71
|
+
elevation: elevationOf(direction),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
async function applySkySystemBundle(
|
|
55
76
|
sky,
|
|
56
77
|
settings,
|
|
@@ -62,7 +83,12 @@ async function applySkySystemBundle(
|
|
|
62
83
|
const coordinated = {
|
|
63
84
|
sun: {
|
|
64
85
|
...current.sun,
|
|
65
|
-
|
|
86
|
+
// The SkySystem's sun must follow its real celestial arc so its Dawn,
|
|
87
|
+
// Day, Sunset, and Night palette weights can see the sun cross the
|
|
88
|
+
// horizon. The gameplay lighting frame deliberately keeps a blue night
|
|
89
|
+
// key above the ground; feeding that direction into the sky made every
|
|
90
|
+
// visible dome render as daytime cyan.
|
|
91
|
+
...skySunPoseForFrame(current, lightingFrame),
|
|
66
92
|
},
|
|
67
93
|
time: {
|
|
68
94
|
...current.time,
|
|
@@ -87,6 +113,7 @@ async function applySkySystemBundle(
|
|
|
87
113
|
...(snapshot ? { style: snapshot.cloudStyle } : {}),
|
|
88
114
|
},
|
|
89
115
|
}));
|
|
116
|
+
alignSkyCloudShadowToLighting(sky, sunDirectionFromFrame(lightingFrame));
|
|
90
117
|
}
|
|
91
118
|
|
|
92
119
|
function lightingRequestsShadows(settings) {
|
|
@@ -341,7 +368,14 @@ export function createSceneStyleRuntime({
|
|
|
341
368
|
shadows: {
|
|
342
369
|
enabled: shadowsEnabled,
|
|
343
370
|
native: renderer?.shadowMap?.enabled === true,
|
|
344
|
-
sharedPass
|
|
371
|
+
// `sharedPass` used to be `Boolean(shadowPass?.shadowTexture)`, which
|
|
372
|
+
// is true as soon as the pass allocates a target — so a scene with no
|
|
373
|
+
// shadow-casting sun, or no caster, reported healthy shadows and
|
|
374
|
+
// shipped with none (D19-041). It now reflects whether the pass has
|
|
375
|
+
// actually drawn casters into a published map, and `sharedPassHealth`
|
|
376
|
+
// says why not.
|
|
377
|
+
sharedPass: shadowPass?.health?.ok === true,
|
|
378
|
+
sharedPassHealth: shadowPass?.health ?? null,
|
|
345
379
|
updatesPerSecond: qualityProfile.quality.shadows.maxUpdatesPerSecond,
|
|
346
380
|
},
|
|
347
381
|
sky: activeSky ? {
|
|
@@ -815,12 +849,25 @@ export function createSceneStyleRuntime({
|
|
|
815
849
|
if (isSkySystem(activeSky)) {
|
|
816
850
|
activeSky.setTimeOfDay(frame.hour, {
|
|
817
851
|
autoAdvanceSecondsPerDay: 0,
|
|
818
|
-
sunDirection: sunDirectionFromFrame(frame),
|
|
819
852
|
});
|
|
853
|
+
alignSkyCloudShadowToLighting(activeSky, sunDirectionFromFrame(frame));
|
|
820
854
|
}
|
|
821
855
|
return frame;
|
|
822
856
|
},
|
|
823
857
|
|
|
858
|
+
setSunDirection(direction = null) {
|
|
859
|
+
const resolved = lighting.setSunDirection(direction);
|
|
860
|
+
if (isSkySystem(activeSky)) {
|
|
861
|
+
activeSky.setTimeOfDay(lighting.frame.hour, {
|
|
862
|
+
autoAdvanceSecondsPerDay: 0,
|
|
863
|
+
sunDirection: resolved,
|
|
864
|
+
});
|
|
865
|
+
alignSkyCloudShadowToLighting(activeSky, resolved);
|
|
866
|
+
}
|
|
867
|
+
shadowPass?.invalidate?.();
|
|
868
|
+
return resolved;
|
|
869
|
+
},
|
|
870
|
+
|
|
824
871
|
/**
|
|
825
872
|
* Replaces the physical SkySystem condition while preserving the active
|
|
826
873
|
* style bundle's sky/cloud presentation and coordinated lighting frame.
|
|
@@ -842,8 +889,8 @@ export function createSceneStyleRuntime({
|
|
|
842
889
|
} else {
|
|
843
890
|
activeSky.setTimeOfDay(frame.hour, {
|
|
844
891
|
autoAdvanceSecondsPerDay: 0,
|
|
845
|
-
sunDirection: sunDirectionFromFrame(frame),
|
|
846
892
|
});
|
|
893
|
+
alignSkyCloudShadowToLighting(activeSky, sunDirectionFromFrame(frame));
|
|
847
894
|
}
|
|
848
895
|
}
|
|
849
896
|
return activeSky.toParams?.() ?? null;
|
|
@@ -2,7 +2,7 @@ import { applyToonShader } from '../toon/toonMaterialAdapter.js';
|
|
|
2
2
|
import { applyEnvironmentShader } from '../environment/environmentMaterialAdapter.js';
|
|
3
3
|
import {
|
|
4
4
|
applyUrbanPropShaderProfile,
|
|
5
|
-
|
|
5
|
+
classifyUrbanMaterial,
|
|
6
6
|
createUrbanAnimePropMaterial,
|
|
7
7
|
createUrbanAnimePropNodeMaterial,
|
|
8
8
|
createUrbanPropShaderControls,
|
|
@@ -49,8 +49,19 @@ function applyManufacturedSurface(root, settings, { target } = {}) {
|
|
|
49
49
|
root?.traverse?.((object) => {
|
|
50
50
|
if (!object?.isMesh || !object.material) return;
|
|
51
51
|
const materials = Array.isArray(object.material) ? object.material : [object.material];
|
|
52
|
+
// `classifyUrbanPropSurface` returns a PROFILE ID string, and the adapter's
|
|
53
|
+
// `classification` parameter takes a classification OBJECT. Passing the
|
|
54
|
+
// string meant `classificationForAdapter` saw a non-object, found no
|
|
55
|
+
// `surface` either, and fell all the way through to its
|
|
56
|
+
// `paintedMetal` default — so every material converted through the
|
|
57
|
+
// documented style-bundle path resolved to one profile regardless of its
|
|
58
|
+
// semantic role. §8 requires the opposite ("every material receives
|
|
59
|
+
// semantic ToonLab roles before the Manufactured Surface shader is
|
|
60
|
+
// applied"), and the manifest written by
|
|
61
|
+
// `applyManufacturedMaterialManifest` was reaching this call and then
|
|
62
|
+
// being discarded one frame later. See D19-080.
|
|
52
63
|
const next = materials.map((material) => createMaterial(material, {
|
|
53
|
-
classification:
|
|
64
|
+
classification: classifyUrbanMaterial(object, material),
|
|
54
65
|
controls,
|
|
55
66
|
}));
|
|
56
67
|
object.material = Array.isArray(object.material) ? next : next[0];
|
|
@@ -665,7 +665,7 @@ export async function fetchStyleBundle(ref, {
|
|
|
665
665
|
if (firstParty) {
|
|
666
666
|
return { document: firstParty, settings: resolveStyleBundleSettings(firstParty) };
|
|
667
667
|
}
|
|
668
|
-
const url = /^https
|
|
668
|
+
const url = /^(?:https?:\/\/|\/)/.test(ref)
|
|
669
669
|
? ref
|
|
670
670
|
: `${baseUrl.replace(/\/+$/, '')}/api/v1/bundles/${encodeURIComponent(ref)}`;
|
|
671
671
|
const response = await fetchImpl(url, { headers: { accept: 'application/json' } });
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// color grading, glow, and all PBR derivation keep working on top. Decoding
|
|
11
11
|
// stays the caller's job so this module remains DOM-free.
|
|
12
12
|
|
|
13
|
-
import { hashCombine } from '../rockgen/noise/prng.js';
|
|
13
|
+
import { hash3u, hashCombine } from '../rockgen/noise/prng.js';
|
|
14
14
|
import { periodicFbm2 } from './noise2.js';
|
|
15
15
|
import { compileTextureLayer } from './textureGenerators.js';
|
|
16
16
|
import { createTextureSettings } from './textureSettings.js';
|
|
@@ -90,6 +90,15 @@ function overlayOp(a, b) {
|
|
|
90
90
|
return a < 0.5 ? 2 * a * b : 1 - 2 * (1 - a) * (1 - b);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Independent [0, 1) draw from a pattern cell id. The legacy per-cell tint read
|
|
95
|
+
* two overlapping 16-bit slices of the same hash for hue and value, which tied a
|
|
96
|
+
* cell's hue to its brightness; a fresh salted hash per channel breaks that.
|
|
97
|
+
*/
|
|
98
|
+
function cellUnit(id, salt) {
|
|
99
|
+
return hash3u(salt, id | 0, (id >>> 16) | 0, 0) / 4294967296;
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
function blendHeight(mode, h, d) {
|
|
94
103
|
switch (mode) {
|
|
95
104
|
case 'add': return clamp01(h + (d - 0.5));
|
|
@@ -356,6 +365,12 @@ export async function evaluateTextureMaps(rawSettings, {
|
|
|
356
365
|
const emissive = settings.emissive;
|
|
357
366
|
const jitterSeed = hashCombine(seed, 0x11a77);
|
|
358
367
|
const hueSeed = hashCombine(seed, 0x22b33);
|
|
368
|
+
// Per-cell tint variety (0 = legacy). Above 0 the cell's hue and value come
|
|
369
|
+
// from independent salted hashes and the painterly drift keeps running inside
|
|
370
|
+
// the cell, so N modules stop reading as N flat swatches (D19-057).
|
|
371
|
+
const cellVariety = Math.min(1, Math.max(0, color.jitterCellVariety ?? 0));
|
|
372
|
+
const cellValueSalt = hashCombine(seed, 0x33c1e);
|
|
373
|
+
const cellHueSalt = hashCombine(seed, 0x44d2f);
|
|
359
374
|
const jitterScale = Math.max(2, Math.round(color.jitterScale));
|
|
360
375
|
const cavityTint = toLinear(color.cavityTint);
|
|
361
376
|
const sheenTint = toLinear(color.sheenTint);
|
|
@@ -443,7 +458,13 @@ export async function evaluateTextureMaps(rawSettings, {
|
|
|
443
458
|
if (color.jitterValue > 0) {
|
|
444
459
|
let jn;
|
|
445
460
|
if (color.jitterCells && baseCell !== null) {
|
|
446
|
-
jn =
|
|
461
|
+
jn = cellVariety > 0
|
|
462
|
+
? cellUnit(baseCell, cellValueSalt)
|
|
463
|
+
: ((baseCell >>> 8) & 0xffff) / 65536;
|
|
464
|
+
if (cellVariety > 0) {
|
|
465
|
+
const drift = periodicFbm2(jitterSeed, u * jitterScale, v * jitterScale, jitterScale, jitterScale, 2, 0.5) * 0.5 + 0.5;
|
|
466
|
+
jn = clamp01(jn + (drift - 0.5) * cellVariety * 0.6);
|
|
467
|
+
}
|
|
447
468
|
} else {
|
|
448
469
|
jn = periodicFbm2(jitterSeed, u * jitterScale, v * jitterScale, jitterScale, jitterScale, 2, 0.5) * 0.5 + 0.5;
|
|
449
470
|
}
|
|
@@ -458,7 +479,13 @@ export async function evaluateTextureMaps(rawSettings, {
|
|
|
458
479
|
if (color.jitterHue > 0) {
|
|
459
480
|
let hn;
|
|
460
481
|
if (color.jitterCells && baseCell !== null) {
|
|
461
|
-
hn =
|
|
482
|
+
hn = cellVariety > 0
|
|
483
|
+
? cellUnit(baseCell, cellHueSalt)
|
|
484
|
+
: (baseCell & 0xffff) / 65536;
|
|
485
|
+
if (cellVariety > 0) {
|
|
486
|
+
const drift = periodicFbm2(hueSeed, u * jitterScale, v * jitterScale, jitterScale, jitterScale, 2, 0.5) * 0.5 + 0.5;
|
|
487
|
+
hn = clamp01(hn + (drift - 0.5) * cellVariety * 0.6);
|
|
488
|
+
}
|
|
462
489
|
} else {
|
|
463
490
|
hn = periodicFbm2(hueSeed, u * jitterScale, v * jitterScale, jitterScale, jitterScale, 2, 0.5) * 0.5 + 0.5;
|
|
464
491
|
}
|
|
@@ -123,19 +123,49 @@ function makeCellular(project) {
|
|
|
123
123
|
// Grid patterns (bricks, tiles, checker, weave, ...)
|
|
124
124
|
// ---------------------------------------------------------------------------
|
|
125
125
|
|
|
126
|
-
/**
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Distance-to-edge bevel profile for rectangular cells.
|
|
128
|
+
*
|
|
129
|
+
* `gap` and `bevel` are cell-local fractions, so a non-square cell (a plank,
|
|
130
|
+
* a 200x100 subway tile, a clapboard) would otherwise get a joint whose world
|
|
131
|
+
* width differs per axis by the cell aspect ratio. `sx`/`sy` scale the joint
|
|
132
|
+
* per axis so an elongated module can carry an even mortar/groove line;
|
|
133
|
+
* authors drive them from the layer's existing `stretchX`/`stretchY` fields.
|
|
134
|
+
*
|
|
135
|
+
* At sx = sy = 1 this is exactly the original single-axis form: smoothstep is
|
|
136
|
+
* monotonic, so min(smoothstep(du), smoothstep(dv)) === smoothstep(min(du, dv)).
|
|
137
|
+
*/
|
|
138
|
+
function faceProfile(lu, lv, gap, bevel, sx = 1, sy = 1) {
|
|
139
|
+
const du = Math.min(lu, 1 - lu);
|
|
140
|
+
const dv = Math.min(lv, 1 - lv);
|
|
141
|
+
const gu = gap * 0.5 * sx;
|
|
142
|
+
const gv = gap * 0.5 * sy;
|
|
143
|
+
const bu = Math.max(0.004, bevel * sx);
|
|
144
|
+
const bv = Math.max(0.004, bevel * sy);
|
|
145
|
+
return Math.min(
|
|
146
|
+
smoothstep(gu, gu + bu, du),
|
|
147
|
+
smoothstep(gv, gv + bv, dv),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Joint anisotropy from a layer's stretch fields (1 = isotropic). The clamp
|
|
153
|
+
* mirrors the schema range for `stretchX`/`stretchY`; at 0.125 a module may run
|
|
154
|
+
* 8:1 (a 1.6 m deck board in a 200 mm run) and still carry an even joint.
|
|
155
|
+
*/
|
|
156
|
+
function jointAspect(params) {
|
|
157
|
+
const sx = Number.isFinite(params.stretchX) ? Math.min(16, Math.max(0.125, params.stretchX)) : 1;
|
|
158
|
+
const sy = Number.isFinite(params.stretchY) ? Math.min(16, Math.max(0.125, params.stretchY)) : 1;
|
|
159
|
+
return { sx, sy };
|
|
131
160
|
}
|
|
132
161
|
|
|
133
162
|
function compileBricks(params, seed, offsetMode) {
|
|
134
|
-
const cols = intIn(params.columns ?? 4, 1,
|
|
135
|
-
const rows = intIn(params.rows ?? 8, 1,
|
|
163
|
+
const cols = intIn(params.columns ?? 4, 1, 256, 4);
|
|
164
|
+
const rows = intIn(params.rows ?? 8, 1, 256, 8);
|
|
136
165
|
const gap = Math.min(0.4, Math.max(0, params.gap ?? 0.06));
|
|
137
166
|
const bevel = Math.min(0.5, Math.max(0, params.bevel ?? 0.12));
|
|
138
167
|
const variation = clamp01(params.cellVariation ?? 0.35);
|
|
168
|
+
const { sx, sy } = jointAspect(params);
|
|
139
169
|
return (u, v) => {
|
|
140
170
|
const y = v * rows;
|
|
141
171
|
const row = Math.floor(y);
|
|
@@ -143,14 +173,14 @@ function compileBricks(params, seed, offsetMode) {
|
|
|
143
173
|
const x = u * cols + shift;
|
|
144
174
|
const col = Math.floor(x);
|
|
145
175
|
const id = hash3u(seed, ((col % cols) + cols) % cols, ((row % rows) + rows) % rows, 0);
|
|
146
|
-
const value = faceProfile(fract(x), fract(y), gap, bevel);
|
|
176
|
+
const value = faceProfile(fract(x), fract(y), gap, bevel, sx, sy);
|
|
147
177
|
return { v: withCellVariation(value, id, variation, seed), cell: id };
|
|
148
178
|
};
|
|
149
179
|
}
|
|
150
180
|
|
|
151
181
|
function compileChecker(params, seed) {
|
|
152
|
-
const cols = intIn(params.columns ?? 4, 1,
|
|
153
|
-
const rows = intIn(params.rows ?? 4, 1,
|
|
182
|
+
const cols = intIn(params.columns ?? 4, 1, 256, 4);
|
|
183
|
+
const rows = intIn(params.rows ?? 4, 1, 256, 4);
|
|
154
184
|
return (u, v) => {
|
|
155
185
|
const x = Math.floor(u * cols);
|
|
156
186
|
const y = Math.floor(v * rows);
|
|
@@ -160,21 +190,21 @@ function compileChecker(params, seed) {
|
|
|
160
190
|
}
|
|
161
191
|
|
|
162
192
|
function compileGrid(params) {
|
|
163
|
-
const cols = intIn(params.columns ?? 8, 1,
|
|
164
|
-
const rows = intIn(params.rows ?? 8, 1,
|
|
193
|
+
const cols = intIn(params.columns ?? 8, 1, 256, 8);
|
|
194
|
+
const rows = intIn(params.rows ?? 8, 1, 256, 8);
|
|
165
195
|
const gap = Math.min(0.4, Math.max(0.005, params.gap ?? 0.06));
|
|
166
196
|
const bevel = Math.min(0.5, Math.max(0.004, params.bevel ?? 0.06));
|
|
197
|
+
const { sx, sy } = jointAspect(params);
|
|
167
198
|
return (u, v) => {
|
|
168
199
|
const lu = fract(u * cols);
|
|
169
200
|
const lv = fract(v * rows);
|
|
170
|
-
|
|
171
|
-
return { v: smoothstep(gap * 0.5, gap * 0.5 + bevel, d), cell: null };
|
|
201
|
+
return { v: faceProfile(lu, lv, gap, bevel, sx, sy), cell: null };
|
|
172
202
|
};
|
|
173
203
|
}
|
|
174
204
|
|
|
175
205
|
function compileStripes(params, seed) {
|
|
176
|
-
const cols = intIn(params.columns ?? 8, 1,
|
|
177
|
-
const rows = intIn(params.rows ?? 8, 1,
|
|
206
|
+
const cols = intIn(params.columns ?? 8, 1, 256, 8);
|
|
207
|
+
const rows = intIn(params.rows ?? 8, 1, 256, 8);
|
|
178
208
|
const variation = clamp01(params.cellVariation ?? 0);
|
|
179
209
|
return (u, v) => {
|
|
180
210
|
const y = v * rows;
|
|
@@ -197,8 +227,8 @@ function compileStripes(params, seed) {
|
|
|
197
227
|
}
|
|
198
228
|
|
|
199
229
|
function compileChevron(params) {
|
|
200
|
-
const cols = intIn(params.columns ?? 8, 1,
|
|
201
|
-
const rows = intIn(params.rows ?? 8, 1,
|
|
230
|
+
const cols = intIn(params.columns ?? 8, 1, 256, 8);
|
|
231
|
+
const rows = intIn(params.rows ?? 8, 1, 256, 8);
|
|
202
232
|
return (u, v) => {
|
|
203
233
|
const zig = Math.abs(fract(v * rows) * 2 - 1);
|
|
204
234
|
const t = fract(u * cols + zig);
|
|
@@ -207,8 +237,8 @@ function compileChevron(params) {
|
|
|
207
237
|
}
|
|
208
238
|
|
|
209
239
|
function compileWeave(params, seed) {
|
|
210
|
-
const cols = intIn(params.columns ?? 10, 1,
|
|
211
|
-
const rows = intIn(params.rows ?? 10, 1,
|
|
240
|
+
const cols = intIn(params.columns ?? 10, 1, 256, 10);
|
|
241
|
+
const rows = intIn(params.rows ?? 10, 1, 256, 10);
|
|
212
242
|
const gap = Math.min(0.45, Math.max(0.02, params.gap ?? 0.16));
|
|
213
243
|
return (u, v) => {
|
|
214
244
|
const x = u * cols;
|
|
@@ -231,8 +261,8 @@ function compileWeave(params, seed) {
|
|
|
231
261
|
}
|
|
232
262
|
|
|
233
263
|
function compileBasketWeave(params, seed) {
|
|
234
|
-
const cols = intIn(params.columns ?? 6, 1,
|
|
235
|
-
const rows = intIn(params.rows ?? 6, 1,
|
|
264
|
+
const cols = intIn(params.columns ?? 6, 1, 256, 6);
|
|
265
|
+
const rows = intIn(params.rows ?? 6, 1, 256, 6);
|
|
236
266
|
const gap = Math.min(0.4, Math.max(0, params.gap ?? 0.08));
|
|
237
267
|
const bevel = Math.min(0.5, Math.max(0.01, params.bevel ?? 0.1));
|
|
238
268
|
const slats = 3;
|
|
@@ -257,7 +287,7 @@ function compileBasketWeave(params, seed) {
|
|
|
257
287
|
}
|
|
258
288
|
|
|
259
289
|
function compileHex(params, seed) {
|
|
260
|
-
const cols = intIn(params.columns ?? 6, 1,
|
|
290
|
+
const cols = intIn(params.columns ?? 6, 1, 256, 6);
|
|
261
291
|
// A staggered lattice with 2:sqrt(3) aspect produces regular hexagons
|
|
262
292
|
// out of plain nearest-center Voronoi. Rows derive from columns so the
|
|
263
293
|
// lattice stays periodic in the unit tile.
|
|
@@ -299,8 +329,8 @@ function compileHex(params, seed) {
|
|
|
299
329
|
}
|
|
300
330
|
|
|
301
331
|
function compileScales(params, seed) {
|
|
302
|
-
const cols = intIn(params.columns ?? 8, 1,
|
|
303
|
-
const rows = intIn(params.rows ?? 12, 1,
|
|
332
|
+
const cols = intIn(params.columns ?? 8, 1, 256, 8);
|
|
333
|
+
const rows = intIn(params.rows ?? 12, 1, 256, 12);
|
|
304
334
|
const variation = clamp01(params.cellVariation ?? 0.25);
|
|
305
335
|
return (u, v) => {
|
|
306
336
|
// Staggered rows of overlapping shingles: the row above clips the one
|
|
@@ -328,8 +358,8 @@ function compileScales(params, seed) {
|
|
|
328
358
|
}
|
|
329
359
|
|
|
330
360
|
function compileDots(params, seed) {
|
|
331
|
-
const cols = intIn(params.columns ?? 8, 1,
|
|
332
|
-
const rows = intIn(params.rows ?? 8, 1,
|
|
361
|
+
const cols = intIn(params.columns ?? 8, 1, 256, 8);
|
|
362
|
+
const rows = intIn(params.rows ?? 8, 1, 256, 8);
|
|
333
363
|
const radius = Math.min(0.48, Math.max(0.02, 0.5 - (params.gap ?? 0.18)));
|
|
334
364
|
const soft = Math.min(0.5, Math.max(0.01, params.bevel ?? 0.08));
|
|
335
365
|
const jitter = clamp01(params.cellJitter ?? 0);
|
|
@@ -383,7 +413,7 @@ function compileMarble(params, seed) {
|
|
|
383
413
|
const { px, py } = periods(params);
|
|
384
414
|
const octaves = intIn(params.detail ?? 4, 1, 8, 4);
|
|
385
415
|
const sharp = 0.5 + (params.grain ?? 0.5) * 6;
|
|
386
|
-
const veins = intIn(params.rings ?? 3, 1,
|
|
416
|
+
const veins = intIn(params.rings ?? 3, 1, 128, 3);
|
|
387
417
|
return (u, v) => {
|
|
388
418
|
const turb = periodicTurbulence2(seed, u * px, v * py, px, py, octaves, 0.55);
|
|
389
419
|
const phase = fract(u * veins + turb * 1.35);
|
|
@@ -394,7 +424,7 @@ function compileMarble(params, seed) {
|
|
|
394
424
|
|
|
395
425
|
function compileWoodGrain(params, seed) {
|
|
396
426
|
const { px, py } = periods(params);
|
|
397
|
-
const rings = intIn(params.rings ?? 6, 1,
|
|
427
|
+
const rings = intIn(params.rings ?? 6, 1, 128, 6);
|
|
398
428
|
const grain = clamp01(params.grain ?? 0.5);
|
|
399
429
|
const octaves = intIn(params.detail ?? 3, 1, 8, 3);
|
|
400
430
|
const streakSeed = hashCombine(seed, 0xbeef);
|
|
@@ -441,7 +471,9 @@ function compileFlat() {
|
|
|
441
471
|
|
|
442
472
|
const NOISE_USES = ['scale', 'detail', 'detailGain', 'stretchX', 'stretchY', 'warp', 'warpScale'];
|
|
443
473
|
const CELL_USES = ['scale', 'stretchX', 'stretchY', 'cellJitter', 'cellVariation', 'edgeWidth', 'warp', 'warpScale'];
|
|
444
|
-
|
|
474
|
+
// stretchX/stretchY scale the joint width per axis so non-square modules
|
|
475
|
+
// (planks, 200x100 subway tile) can carry an even mortar line.
|
|
476
|
+
const GRID_USES = ['columns', 'rows', 'gap', 'bevel', 'cellVariation', 'stretchX', 'stretchY', 'warp', 'warpScale'];
|
|
445
477
|
|
|
446
478
|
export const TEXTURE_GENERATORS = Object.freeze({
|
|
447
479
|
fbm: { label: 'Soft noise (fbm)', category: 'noise', uses: NOISE_USES, compile: makeNoise(periodicFbm2, true) },
|
|
@@ -484,7 +516,7 @@ export const TEXTURE_GENERATORS = Object.freeze({
|
|
|
484
516
|
tiles: { label: 'Tiles (stacked)', category: 'pattern', uses: GRID_USES, compile: (p, s) => compileBricks(p, s, 'stacked') },
|
|
485
517
|
hex: { label: 'Hex tiles', category: 'pattern', uses: ['columns', 'gap', 'bevel', 'cellVariation', 'warp', 'warpScale'], compile: compileHex },
|
|
486
518
|
checker: { label: 'Checker', category: 'pattern', uses: ['columns', 'rows', 'warp', 'warpScale'], compile: compileChecker },
|
|
487
|
-
grid: { label: 'Grid lines', category: 'pattern', uses: ['columns', 'rows', 'gap', 'bevel', 'warp', 'warpScale'], compile: compileGrid },
|
|
519
|
+
grid: { label: 'Grid lines', category: 'pattern', uses: ['columns', 'rows', 'gap', 'bevel', 'stretchX', 'stretchY', 'warp', 'warpScale'], compile: compileGrid },
|
|
488
520
|
stripes: { label: 'Stripes', category: 'pattern', uses: ['columns', 'rows', 'cellVariation', 'warp', 'warpScale'], compile: compileStripes },
|
|
489
521
|
chevron: { label: 'Chevron zigzag', category: 'pattern', uses: ['columns', 'rows', 'warp', 'warpScale'], compile: compileChevron },
|
|
490
522
|
weave: { label: 'Cloth weave', category: 'pattern', uses: ['columns', 'rows', 'gap', 'warp', 'warpScale'], compile: compileWeave },
|
|
@@ -34,22 +34,31 @@ const generatorField = (defaultValue, description) => sel(
|
|
|
34
34
|
// generator reads is declared in TEXTURE_GENERATORS[id].uses — UIs filter
|
|
35
35
|
// on that so only meaningful sliders show.
|
|
36
36
|
const layerParamFields = ({ advancedShape = false } = {}) => [
|
|
37
|
-
|
|
37
|
+
// Feature size is a property of the (scale, world tile) pairing, not of the
|
|
38
|
+
// map: a tile bound at T metres resolves features of T / scale. At the old
|
|
39
|
+
// cap of 64 a 2.4 m deck tile could not describe anything finer than 3.8 cm,
|
|
40
|
+
// which is coarser than plank grain, asphalt aggregate or paver texture — the
|
|
41
|
+
// cap, not the resolution, was setting the floor on feature size.
|
|
42
|
+
num('scale', 'Scale', 1, 256, 1, 6, 'Feature cells across the tile. Higher = finer features. At a 2 m world tile, 200 resolves 1 cm detail.'),
|
|
38
43
|
bool('rotate90', 'Rotate 90°', false, 'Turns the pattern a quarter turn (planks run vertical, strata run horizontal). Tiling stays exact.', { advanced: true }),
|
|
39
44
|
num('detail', 'Detail octaves', 1, 8, 1, 4, 'Fractal octaves layered into the noise.'),
|
|
40
45
|
num('detailGain', 'Detail strength', 0.15, 0.85, 0.01, 0.5, 'How much each finer octave contributes.', { advanced: true }),
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
// Naming note: these multiply the FEATURE PERIOD COUNT on each axis, so
|
|
47
|
+
// raising one makes features finer on that axis and therefore elongated
|
|
48
|
+
// along the other. stretchX > 1 reads as vertical streaks, not horizontal
|
|
49
|
+
// ones. The old labels claimed the opposite.
|
|
50
|
+
num('stretchX', 'X frequency', 0.125, 16, 0.05, 1, 'Multiplies feature periods across U. Higher = finer in U, so features elongate along V and read as VERTICAL streaks (drips, fibers, strata). On grid patterns it instead widens the joint on the U axis.', { advanced: true }),
|
|
51
|
+
num('stretchY', 'Y frequency', 0.125, 16, 0.05, 1, 'Multiplies feature periods down V. Higher = finer in V, so features elongate along U and read as HORIZONTAL streaks (brushed metal, boards lying across the frame). On grid patterns it instead widens the joint on the V axis.', { advanced: true }),
|
|
43
52
|
num('warp', 'Warp', 0, 1, 0.01, 0, 'Domain warp: melts straight features into organic meanders.'),
|
|
44
53
|
num('warpScale', 'Warp scale', 1, 32, 1, 3, 'Frequency of the warp field.', { advanced: true }),
|
|
45
|
-
num('columns', 'Columns', 1,
|
|
46
|
-
num('rows', 'Rows', 1,
|
|
54
|
+
num('columns', 'Columns', 1, 256, 1, 4, 'Pattern cells across the tile.', { advanced: advancedShape }),
|
|
55
|
+
num('rows', 'Rows', 1, 256, 1, 8, 'Pattern cells down the tile.', { advanced: advancedShape }),
|
|
47
56
|
num('gap', 'Gap width', 0, 0.4, 0.005, 0.06, 'Mortar/groove width between pattern cells.', { advanced: advancedShape }),
|
|
48
57
|
num('bevel', 'Bevel', 0, 0.5, 0.005, 0.12, 'Edge ramp from groove up to the cell face.', { advanced: advancedShape }),
|
|
49
58
|
num('cellJitter', 'Cell jitter', 0, 1, 0.01, 1, 'Randomizes cell centers: 0 = perfect grid, 1 = organic.', { advanced: true }),
|
|
50
59
|
num('cellVariation', 'Cell variation', 0, 1, 0.01, 0.35, 'Per-cell brightness variance (brick tint shifts).'),
|
|
51
60
|
num('edgeWidth', 'Edge width', 0.01, 0.6, 0.005, 0.12, 'Width of cracks / caustic filaments / speckle chips.', { advanced: advancedShape }),
|
|
52
|
-
num('rings', 'Rings / veins', 1,
|
|
61
|
+
num('rings', 'Rings / veins', 1, 128, 1, 6, 'Ring or vein count across the tile (wood, marble).', { advanced: advancedShape }),
|
|
53
62
|
num('grain', 'Grain', 0, 1, 0.01, 0.5, 'Streak amount (wood) or vein sharpness (marble).', { advanced: advancedShape }),
|
|
54
63
|
];
|
|
55
64
|
|
|
@@ -115,8 +124,16 @@ const RAW_FIELD_SCHEMA = {
|
|
|
115
124
|
num('rampSmooth', 'Band smoothness', 0, 1, 0.01, 1, '1 = smooth gradient, 0 = hard cel bands between the five stops.'),
|
|
116
125
|
num('jitterHue', 'Hue jitter', 0, 0.5, 0.005, 0.04, 'Painterly hue drift across the surface.'),
|
|
117
126
|
num('jitterValue', 'Value jitter', 0, 0.5, 0.005, 0.08, 'Painterly brightness drift across the surface.'),
|
|
118
|
-
num('jitterScale', 'Jitter scale', 2,
|
|
119
|
-
bool('jitterCells', 'Jitter per cell', false, 'Applies drift per pattern cell (per brick / plank / scale) instead of smoothly.'),
|
|
127
|
+
num('jitterScale', 'Jitter scale', 2, 256, 1, 24, 'Frequency of the painterly drift.', { advanced: true }),
|
|
128
|
+
bool('jitterCells', 'Jitter per cell', false, 'Applies drift per pattern cell (per brick / plank / scale) instead of smoothly. Reads the BASE layer\'s cell id, so it is a no-op when the base pattern has no cells.'),
|
|
129
|
+
// A periodic tile can only ever hold `columns * rows` cell ids, and the
|
|
130
|
+
// legacy path derived hue and value from two overlapping 16-bit slices of
|
|
131
|
+
// the same hash — so a 4x4 paver tile showed 16 flat tints whose hue and
|
|
132
|
+
// value moved together. Raising this decorrelates hue from value with
|
|
133
|
+
// independent hashes and keeps the painterly drift alive inside each cell,
|
|
134
|
+
// so modules stop reading as N flat swatches. 0 keeps the legacy result
|
|
135
|
+
// byte-for-byte.
|
|
136
|
+
num('jitterCellVariety', 'Per-cell variety', 0, 1, 0.01, 0, 'Enriches per-cell drift: decorrelates each cell\'s hue from its value and keeps painterly drift running inside the cell, so a tile with N modules stops reading as N flat tints. 0 = legacy flat per-cell tint.'),
|
|
120
137
|
num('cavity', 'Cavity shading', 0, 1, 0.01, 0.35, 'Darkens crevices toward the cavity tint — the hand-painted occlusion read.'),
|
|
121
138
|
col('cavityTint', 'Cavity tint', [0.13, 0.09, 0.08], 'Color the crevices sink toward.', { advanced: true }),
|
|
122
139
|
num('sheen', 'Ridge sheen', 0, 1, 0.01, 0.18, 'Screens the sheen tint over ridges and edges — worn highlight.'),
|
package/src/toon/toonSettings.js
CHANGED
|
@@ -77,6 +77,100 @@ export const TOON_PRESET_IDS = Object.freeze({
|
|
|
77
77
|
|
|
78
78
|
const DEFAULT_TOON_PRESET_SETTINGS = Object.freeze({});
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The Call Me Sensei house look, authored against premium anime-game character
|
|
82
|
+
* rendering rather than left at the library baseline.
|
|
83
|
+
*
|
|
84
|
+
* The baseline is deliberately conservative: it has to look sane on any imported
|
|
85
|
+
* GLB/VRM, so its outlines are thin, its speculars are near-off and its shadows
|
|
86
|
+
* are close to neutral. That produces a character who is *cel shaded* but whose
|
|
87
|
+
* material families all read the same — skin, cloth, leather and metal separate
|
|
88
|
+
* only by albedo. This preset is the opposite trade: it assumes a hero character
|
|
89
|
+
* under art direction and spends contrast on telling those families apart.
|
|
90
|
+
*
|
|
91
|
+
* Every value below is a deliberate delta from `DEFAULT_*_SETTINGS`; anything not
|
|
92
|
+
* listed is intentionally inherited. The three axes that carry the separation:
|
|
93
|
+
*
|
|
94
|
+
* 1. **Outline weight by role.** Contour is the single strongest anime material
|
|
95
|
+
* cue. Costume and metal carry a heavy near-neutral line, hair carries a
|
|
96
|
+
* weighty warm-dark line (the baseline's 0.00055 is invisible past two metres),
|
|
97
|
+
* skin carries a thin warm line, and the face carries none at all — a line
|
|
98
|
+
* across a cel-shaded face reads as dirt, not drawing.
|
|
99
|
+
* 2. **Specular by role.** Cloth stays matte, hair takes a broad anisotropic
|
|
100
|
+
* band, and metal takes a tight bright hit. Leather is reached per-material
|
|
101
|
+
* through `specular.sourceMaskMode: 'source'` plus a `toonSpecularMaskMap`,
|
|
102
|
+
* because a fifth family cannot be expressed through roles alone.
|
|
103
|
+
* 3. **Coloured shadow families.** Shadows shift blue-violet and gain saturation
|
|
104
|
+
* instead of just going dark, and the terminator gains a warmer, more
|
|
105
|
+
* saturated transition band — the value grouping §4 asks for.
|
|
106
|
+
*/
|
|
107
|
+
const CALL_ME_SENSEI_TOON_PRESET_SETTINGS = Object.freeze({
|
|
108
|
+
// Slightly earlier, slightly tighter terminator so the light/shadow split is a
|
|
109
|
+
// drawn shape with a clean edge rather than a soft gradient.
|
|
110
|
+
celShade: Object.freeze({
|
|
111
|
+
bodyCelMidPoint: 0.04,
|
|
112
|
+
bodyCelSoftness: 0.036,
|
|
113
|
+
}),
|
|
114
|
+
// Occlusion under collars, chins, cuffs and hems is what stops a toon character
|
|
115
|
+
// reading as a decal on the plate.
|
|
116
|
+
contactShadow: Object.freeze({
|
|
117
|
+
faceStrength: 0.46,
|
|
118
|
+
strength: 0.62,
|
|
119
|
+
}),
|
|
120
|
+
hairHighlight: Object.freeze({
|
|
121
|
+
intensity: 0.22,
|
|
122
|
+
shadowFloor: 0.32,
|
|
123
|
+
sideBandPower: 2.2,
|
|
124
|
+
strandPower: 8,
|
|
125
|
+
}),
|
|
126
|
+
outline: Object.freeze({
|
|
127
|
+
defaultLightingMix: 0.22,
|
|
128
|
+
defaultMaxBrightness: 0.3,
|
|
129
|
+
defaultMinBrightness: 0.035,
|
|
130
|
+
defaultTintColor: [0.22, 0.21, 0.28],
|
|
131
|
+
defaultWidth: 0.0024,
|
|
132
|
+
hairLightingMix: 0.1,
|
|
133
|
+
hairMaxBrightness: 0.42,
|
|
134
|
+
hairMinBrightness: 0.07,
|
|
135
|
+
hairTintColor: [0.3, 0.24, 0.27],
|
|
136
|
+
hairWidth: 0.0016,
|
|
137
|
+
metalLightingMix: 0.2,
|
|
138
|
+
metalMaxBrightness: 0.32,
|
|
139
|
+
metalTintColor: [0.2, 0.2, 0.26],
|
|
140
|
+
metalWidth: 0.0022,
|
|
141
|
+
skinTintColor: [0.55, 0.3, 0.3],
|
|
142
|
+
skinWidth: 0.0011,
|
|
143
|
+
}),
|
|
144
|
+
rimLight: Object.freeze({
|
|
145
|
+
defaultIntensity: 0.16,
|
|
146
|
+
defaultTintColor: [0.79, 0.88, 1],
|
|
147
|
+
hairIntensity: 0.3,
|
|
148
|
+
skinIntensity: 0.15,
|
|
149
|
+
}),
|
|
150
|
+
// Cloth stays matte (a low, broad, late-onset lobe), hair broadens, metal takes
|
|
151
|
+
// a bright tight hit. `sourceMaskMode: 'source'` is what lets a single costume
|
|
152
|
+
// role still contain a leather family: give that material a
|
|
153
|
+
// `userData.toonSpecularMaskMap` and it gets the highlight, its neighbours do not.
|
|
154
|
+
specular: Object.freeze({
|
|
155
|
+
defaultIntensity: 0.09,
|
|
156
|
+
defaultMidPoint: 0.74,
|
|
157
|
+
defaultPower: 48,
|
|
158
|
+
hairIntensity: 0.24,
|
|
159
|
+
hairPower: 44,
|
|
160
|
+
metalIntensity: 0.5,
|
|
161
|
+
sourceMaskMode: 'source',
|
|
162
|
+
}),
|
|
163
|
+
// Shadows move blue-violet and gain chroma rather than only losing value; the
|
|
164
|
+
// terminator keeps a warmer, more saturated band against them.
|
|
165
|
+
shadowColor: Object.freeze({
|
|
166
|
+
selfShadowAreaHueOffset: -0.022,
|
|
167
|
+
selfShadowAreaSaturationBoost: 0.34,
|
|
168
|
+
selfShadowAreaValueMul: 0.63,
|
|
169
|
+
transitionAreaHueOffset: 0.018,
|
|
170
|
+
transitionAreaSaturationBoost: 0.5,
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
|
|
80
174
|
export const TOON_SETTING_DEFAULTS = Object.freeze({
|
|
81
175
|
alpha: DEFAULT_ALPHA_SETTINGS,
|
|
82
176
|
averageShadow: DEFAULT_AVERAGE_SHADOW_SETTINGS,
|
|
@@ -470,9 +564,9 @@ const BUILT_IN_TOON_PRESETS = Object.freeze({
|
|
|
470
564
|
settings: DEFAULT_TOON_PRESET_SETTINGS,
|
|
471
565
|
}),
|
|
472
566
|
[TOON_PRESET_IDS.callMeSensei]: Object.freeze({
|
|
473
|
-
description: 'Team-maintained house style
|
|
567
|
+
description: 'Team-maintained house style. Role-weighted outlines, per-role specular and coloured shadow families so skin, hair, cloth, leather and metal read as distinct materials.',
|
|
474
568
|
label: 'Call Me Sensei',
|
|
475
|
-
settings:
|
|
569
|
+
settings: CALL_ME_SENSEI_TOON_PRESET_SETTINGS,
|
|
476
570
|
}),
|
|
477
571
|
[TOON_PRESET_IDS.showcase]: Object.freeze({
|
|
478
572
|
description: 'Every optional feature turned on with demo values — a one-click way to see the full feature set. Not a production look.',
|