@call-me-sensei/toonlab 0.4.21 → 0.4.22
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 +51 -2
- package/README.md +4 -3
- package/agents/PROMPTS.md +1 -1
- package/database/apply-one-catalog-seed.mjs +63 -0
- package/database/seeds/catalog/0005_2026-08-c8-first12.sql +1101 -0
- package/database/seeds/catalog/0006_2026-09-c8-first100.sql +14245 -0
- package/database/seeds/catalog/0007_2026-09-c8-first100-primary-model.sql +14845 -0
- package/package.json +4 -2
- package/src/catalog/officialCatalog.js +1 -0
- package/src/catalog/officialCatalogAssetRuntime.js +98 -1
- package/src/catalog/officialCatalogLod.js +160 -16
- package/src/catalog/officialCatalogPlacement.js +52 -12
- package/src/catalog/officialCatalogProvider.js +79 -8
- package/src/catalog/officialCatalogRockPackage.js +172 -0
- package/src/rock-shader/rockMaterial.js +205 -28
- package/src/rock-shader/rockShaderRuntime.js +17 -1
- package/src/rock-shader/rockShaderSettings.js +16 -0
- package/src/rockgen/lod/index.js +1 -0
- package/src/rockgen/lod/rockDenseFieldPolicy.js +143 -0
- package/src/rockgen/rockDocument.js +604 -26
- package/src/version.js +1 -1
- package/types/catalog/officialCatalog.d.ts +1 -0
- package/types/catalog/officialCatalogPlacement.d.ts +4 -0
- package/types/catalog/officialCatalogRockPackage.d.ts +68 -0
- package/types/index.d.ts +24 -0
- package/types/rock-shader/rockMaterial.d.ts +3 -0
- package/types/rock-shader/rockShaderSettings.d.ts +2 -0
- package/types/rockgen/lod/index.d.ts +1 -0
- package/types/rockgen/lod/rockDenseFieldPolicy.d.ts +181 -0
- package/types/rockgen/rockDocument.d.ts +579 -68
- package/types/version.d.ts +1 -1
|
@@ -34,6 +34,20 @@ const GROUP_DEFINITIONS = Object.freeze({
|
|
|
34
34
|
description: 'World-space base projection and graphic texture treatment.',
|
|
35
35
|
label: 'Base Projection',
|
|
36
36
|
fields: Object.freeze({
|
|
37
|
+
mode: field({
|
|
38
|
+
defaultValue: 'triplanar',
|
|
39
|
+
description: 'Compile-time texture graph: ordinary three-axis projection or coherent bedding that excludes the top-axis structural sample.',
|
|
40
|
+
label: 'Projection Mode',
|
|
41
|
+
options: ['triplanar', 'directional-bedding'],
|
|
42
|
+
type: 'select',
|
|
43
|
+
}),
|
|
44
|
+
upAxis: field({
|
|
45
|
+
defaultValue: 'y',
|
|
46
|
+
description: 'World-space geological up axis used by directional bedding.',
|
|
47
|
+
label: 'Bedding Up Axis',
|
|
48
|
+
options: ['x', 'y', 'z'],
|
|
49
|
+
type: 'select',
|
|
50
|
+
}),
|
|
37
51
|
scale: field({
|
|
38
52
|
defaultValue: 1.6,
|
|
39
53
|
description: 'World-space size of the projected rock texture in meters.',
|
|
@@ -792,6 +806,8 @@ export const DEFAULT_ROCK_SHADER_SETTINGS = Object.freeze(Object.fromEntries(
|
|
|
792
806
|
// visible, serializable, and independent from any preview fixture.
|
|
793
807
|
export const CALL_ME_SENSEI_ROCK_SHADER_SETTINGS = Object.freeze({
|
|
794
808
|
projection: Object.freeze({
|
|
809
|
+
mode: 'triplanar',
|
|
810
|
+
upAxis: 'y',
|
|
795
811
|
scale: 48,
|
|
796
812
|
// Geological maps already own lithology color and value structure. The
|
|
797
813
|
// previous 0.72 saturation/contrast pair washed every family toward the
|
package/src/rockgen/lod/index.js
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectCatalogLodBindings,
|
|
3
|
+
createCatalogLodRuntime,
|
|
4
|
+
} from '../../catalog/officialCatalogLod.js';
|
|
5
|
+
|
|
6
|
+
export const DENSE_FIELD_ROCK_LOD_LEVELS = Object.freeze([
|
|
7
|
+
Object.freeze({ id: 'LOD0', level: 0, purpose: 'hero-close' }),
|
|
8
|
+
Object.freeze({ id: 'LOD1', level: 1, purpose: 'near-gameplay' }),
|
|
9
|
+
Object.freeze({ id: 'LOD2', level: 2, purpose: 'mid-gameplay' }),
|
|
10
|
+
Object.freeze({ id: 'LOD3', level: 3, purpose: 'far-simplified' }),
|
|
11
|
+
Object.freeze({ id: 'LOD4', level: 4, purpose: 'very-far-silhouette' }),
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const DENSE_FIELD_ROCK_PIXEL_THRESHOLDS = Object.freeze([240, 110, 48, 16, 6]);
|
|
15
|
+
export const DENSE_FIELD_ROCK_CULL_BELOW_PIXELS = 5;
|
|
16
|
+
|
|
17
|
+
export const DENSE_FIELD_ROCK_TRIANGLE_CEILINGS = Object.freeze({
|
|
18
|
+
ordinary: Object.freeze({ LOD3: 1200, LOD4: 350 }),
|
|
19
|
+
formation: Object.freeze({ LOD3: 1800, LOD4: 600 }),
|
|
20
|
+
landmark: Object.freeze({ LOD3: 2500, LOD4: 800 }),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const DENSE_FIELD_ROCK_PRODUCTION_RULE = Object.freeze({
|
|
24
|
+
cullBelowPixels: DENSE_FIELD_ROCK_CULL_BELOW_PIXELS,
|
|
25
|
+
farShadowLastLevel: 2,
|
|
26
|
+
levelCount: 5,
|
|
27
|
+
levels: DENSE_FIELD_ROCK_LOD_LEVELS,
|
|
28
|
+
pixelThresholds: DENSE_FIELD_ROCK_PIXEL_THRESHOLDS,
|
|
29
|
+
selectionBasis: 'projected-object-diameter-pixels',
|
|
30
|
+
triangleCeilings: DENSE_FIELD_ROCK_TRIANGLE_CEILINGS,
|
|
31
|
+
veryFar: Object.freeze({
|
|
32
|
+
allowedSurfaceChannels: Object.freeze(['baseColor', 'broadLighting']),
|
|
33
|
+
castShadow: false,
|
|
34
|
+
preserve: Object.freeze(['outerSilhouette', 'primaryMass', 'groundContact', 'broadBaseColor']),
|
|
35
|
+
remove: Object.freeze([
|
|
36
|
+
'cracks',
|
|
37
|
+
'strataRelief',
|
|
38
|
+
'cavities',
|
|
39
|
+
'secondaryLedges',
|
|
40
|
+
'normalMap',
|
|
41
|
+
'heightMap',
|
|
42
|
+
'aoMap',
|
|
43
|
+
'uniqueRoughness',
|
|
44
|
+
]),
|
|
45
|
+
silhouetteOnly: true,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export function inferDenseFieldRockClass({ scaleClass = null, targetHeightMetres = null } = {}) {
|
|
50
|
+
const named = String(scaleClass ?? '').trim().toLowerCase();
|
|
51
|
+
if (named === 'landmark' || named === 'hero' || named === 'backdrop') return 'landmark';
|
|
52
|
+
if (named === 'formation' || named === 'outcrop' || named === 'cliff') return 'formation';
|
|
53
|
+
if (named === 'ordinary' || named === 'boulder' || named === 'debris') return 'ordinary';
|
|
54
|
+
const height = Number(targetHeightMetres);
|
|
55
|
+
if (Number.isFinite(height) && height > 6) return 'landmark';
|
|
56
|
+
if (Number.isFinite(height) && height > 2) return 'formation';
|
|
57
|
+
return 'ordinary';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function validateDenseFieldRockLodTargets(targets, {
|
|
61
|
+
assetClass = 'ordinary',
|
|
62
|
+
} = {}) {
|
|
63
|
+
const classification = Object.hasOwn(DENSE_FIELD_ROCK_TRIANGLE_CEILINGS, assetClass)
|
|
64
|
+
? assetClass
|
|
65
|
+
: 'ordinary';
|
|
66
|
+
const ceilings = DENSE_FIELD_ROCK_TRIANGLE_CEILINGS[classification];
|
|
67
|
+
const values = DENSE_FIELD_ROCK_LOD_LEVELS.map(({ id }) => Number(targets?.[id]));
|
|
68
|
+
const errors = [];
|
|
69
|
+
values.forEach((value, level) => {
|
|
70
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
71
|
+
errors.push(`${DENSE_FIELD_ROCK_LOD_LEVELS[level].id} must be a positive integer`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
for (let level = 1; level < values.length; level += 1) {
|
|
75
|
+
if (Number.isFinite(values[level]) && Number.isFinite(values[level - 1])
|
|
76
|
+
&& values[level] >= values[level - 1]) {
|
|
77
|
+
errors.push(`LOD${level} must contain fewer triangles than LOD${level - 1}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (Number.isFinite(values[3]) && values[3] > ceilings.LOD3) {
|
|
81
|
+
errors.push(`LOD3 exceeds the ${classification} ${ceilings.LOD3}-triangle ceiling`);
|
|
82
|
+
}
|
|
83
|
+
if (Number.isFinite(values[4]) && values[4] > ceilings.LOD4) {
|
|
84
|
+
errors.push(`LOD4 exceeds the ${classification} ${ceilings.LOD4}-triangle ceiling`);
|
|
85
|
+
}
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
assetClass: classification,
|
|
88
|
+
ceilings,
|
|
89
|
+
errors: Object.freeze(errors),
|
|
90
|
+
valid: errors.length === 0,
|
|
91
|
+
values: Object.freeze(values),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Install the mandatory dense-field rock behavior on an authored LOD0-LOD4
|
|
97
|
+
* root. The final tier is geometry-only silhouette support; it never casts a
|
|
98
|
+
* shadow and the root is culled once its projected diameter drops below the
|
|
99
|
+
* production pixel threshold.
|
|
100
|
+
*/
|
|
101
|
+
export function createDenseFieldRockLodRuntime(root, {
|
|
102
|
+
cullBelowPixels = DENSE_FIELD_ROCK_CULL_BELOW_PIXELS,
|
|
103
|
+
hysteresis = 0.12,
|
|
104
|
+
maxLevel = Number.POSITIVE_INFINITY,
|
|
105
|
+
pixelThresholds = DENSE_FIELD_ROCK_PIXEL_THRESHOLDS,
|
|
106
|
+
referenceDiameter = null,
|
|
107
|
+
requireComplete = true,
|
|
108
|
+
} = {}) {
|
|
109
|
+
const bindings = collectCatalogLodBindings(root);
|
|
110
|
+
const levels = [...new Set(bindings.map(({ level }) => level))].sort((a, b) => a - b);
|
|
111
|
+
const missing = DENSE_FIELD_ROCK_LOD_LEVELS
|
|
112
|
+
.map(({ level }) => level)
|
|
113
|
+
.filter((level) => !levels.includes(level));
|
|
114
|
+
if (requireComplete && missing.length > 0) {
|
|
115
|
+
throw new Error(`Dense-field rock is missing required visual tiers: ${missing.map((level) => `LOD${level}`).join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const originalShadows = new Map(bindings.map(({ mesh }) => [mesh, mesh.castShadow]));
|
|
119
|
+
for (const { level, mesh } of bindings) {
|
|
120
|
+
if (level > DENSE_FIELD_ROCK_PRODUCTION_RULE.farShadowLastLevel) mesh.castShadow = false;
|
|
121
|
+
mesh.userData.toonlabDenseFieldRock = Object.freeze({
|
|
122
|
+
castShadow: mesh.castShadow,
|
|
123
|
+
silhouetteOnly: level === 4,
|
|
124
|
+
visualLevel: level,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const runtime = createCatalogLodRuntime(root, {
|
|
129
|
+
cullBelowPixels,
|
|
130
|
+
hysteresis,
|
|
131
|
+
maxLevel,
|
|
132
|
+
pixelThresholds,
|
|
133
|
+
referenceDiameter,
|
|
134
|
+
});
|
|
135
|
+
return Object.freeze({
|
|
136
|
+
...runtime,
|
|
137
|
+
policy: DENSE_FIELD_ROCK_PRODUCTION_RULE,
|
|
138
|
+
dispose() {
|
|
139
|
+
runtime.dispose();
|
|
140
|
+
for (const [mesh, castShadow] of originalShadows) mesh.castShadow = castShadow;
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|