@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
|
@@ -1,16 +1,53 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
|
|
3
3
|
import { StylizedTree } from './stylizedTree.js';
|
|
4
|
+
import { resolveTreeSurfaceProfileId } from './treeSurfaceTextures.js';
|
|
4
5
|
|
|
5
6
|
export const BRANCH_TREE_DOCUMENT_TYPE = 'toonlab/branch-tree';
|
|
6
7
|
export const BRANCH_TREE_DOCUMENT_VERSION = 1;
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* BranchTree's architecture identity, used by the LOD compiler to pick its
|
|
11
|
+
* triangle envelope. BranchTree is not a legacy aggregate-card tree: it hard-
|
|
12
|
+
* sets the recursive `branching` generator and carries a four-level scaffold,
|
|
13
|
+
* so it budgets like the other recursive woody engines rather than like the
|
|
14
|
+
* legacy default.
|
|
15
|
+
*/
|
|
16
|
+
export const BRANCH_TREE_ARCHITECTURE = Object.freeze({
|
|
17
|
+
id: 'branch-tree',
|
|
18
|
+
engine: 'branch-tree',
|
|
19
|
+
version: 1,
|
|
20
|
+
});
|
|
21
|
+
|
|
8
22
|
export const BRANCH_TREE_LEAF_SHAPES = Object.freeze([
|
|
9
23
|
'teardrop',
|
|
10
24
|
'round',
|
|
11
25
|
'oak',
|
|
12
26
|
'maple',
|
|
27
|
+
// Acer palmatum's deeply dissected seven-lobe leaf. 'maple' above is the
|
|
28
|
+
// blunt five-point sugar-maple star; the two are different species reads
|
|
29
|
+
// and a garden's hero maple needs the dissected one.
|
|
30
|
+
'palmate',
|
|
13
31
|
'gingko',
|
|
32
|
+
// Conifer organs. A needle-bearing BranchTree is not a contradiction: the
|
|
33
|
+
// architecture is a recursive woody skeleton with foliage at the tips,
|
|
34
|
+
// which is exactly how a cloud-pruned pine is built. What it must NOT do is
|
|
35
|
+
// set `skeleton.conifer`, which shortens children toward the leader and
|
|
36
|
+
// produces the excurrent cone a pruned garden pine is defined against.
|
|
37
|
+
'needle',
|
|
38
|
+
'needle-fascicle',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Canopy tuft architectures a BranchTree may select.
|
|
43
|
+
*
|
|
44
|
+
* `null` keeps StylizedTree's own choice, which is what every document
|
|
45
|
+
* authored before this field existed relies on.
|
|
46
|
+
*/
|
|
47
|
+
export const BRANCH_TREE_CANOPY_ARCHITECTURES = Object.freeze([
|
|
48
|
+
'cloud-cards',
|
|
49
|
+
'layered-sprays',
|
|
50
|
+
'needle-whorls',
|
|
14
51
|
]);
|
|
15
52
|
|
|
16
53
|
export const DEFAULT_BRANCH_TREE_SETTINGS = Object.freeze({
|
|
@@ -23,25 +60,110 @@ export const DEFAULT_BRANCH_TREE_SETTINGS = Object.freeze({
|
|
|
23
60
|
gnarliness: 0.16,
|
|
24
61
|
lengthRatio: 0.48,
|
|
25
62
|
levels: 3,
|
|
63
|
+
// Total branches the breadth-first skeleton may grow, and the ceiling on
|
|
64
|
+
// foliage attachments. null = the generator's historical 420 / 380.
|
|
65
|
+
//
|
|
66
|
+
// These are authorable because the fixed budget is a silent quality trap:
|
|
67
|
+
// the skeleton grows breadth-first, so at high `children` levels 1 and 2
|
|
68
|
+
// consume the whole allowance before the leaf-bearing level-3 twigs are
|
|
69
|
+
// ever queued and the tree ships nearly bare (D19-028 — measured 12
|
|
70
|
+
// children → 19 leaf cards). Raising the budget is also the only way to
|
|
71
|
+
// build a many-tipped structure such as a cloud-pruned pine, whose whole
|
|
72
|
+
// silhouette is the count and separation of its foliage pads.
|
|
73
|
+
maxAttachments: null,
|
|
74
|
+
maxBranches: null,
|
|
26
75
|
radialSegments: 8,
|
|
27
76
|
radiusRatio: 0.68,
|
|
28
77
|
start: 0.34,
|
|
29
78
|
}),
|
|
30
79
|
trunk: Object.freeze({
|
|
31
80
|
bend: 0.18,
|
|
81
|
+
// World heading of the trunk's bow, in radians. null = derived from the
|
|
82
|
+
// seed. Authoring it is what makes a coherent windswept stand possible:
|
|
83
|
+
// without it every seed leans a different way and a ridge of trees reads
|
|
84
|
+
// as damage rather than as wind.
|
|
85
|
+
bendDirection: null,
|
|
32
86
|
color: Object.freeze([0.48, 0.29, 0.16]),
|
|
33
87
|
gnarl: 0.18,
|
|
34
88
|
height: 2.1,
|
|
35
89
|
lean: 0.12,
|
|
90
|
+
// Lean heading relative to the bow, in radians. null = derived from the
|
|
91
|
+
// seed; 0 pins the lean along the bow, Math.PI pulls the top back against
|
|
92
|
+
// it for an S-curve.
|
|
93
|
+
leanOffset: null,
|
|
36
94
|
radialSegments: 10,
|
|
37
95
|
radiusBottom: 0.22,
|
|
38
96
|
radiusTop: 0.075,
|
|
97
|
+
// Whether the crown's own shadow map falls on the tree's wood. false is
|
|
98
|
+
// the anime read StylizedTree already documents for pale-limbed trees,
|
|
99
|
+
// and it matters most on a tips-placement tree, where bare limb crosses
|
|
100
|
+
// the canopy in full view: with it on, those limbs render as near-black
|
|
101
|
+
// scribbles inside the crown at close camera.
|
|
102
|
+
receiveShadow: true,
|
|
39
103
|
textureRef: null,
|
|
40
104
|
twist: 0.08,
|
|
41
105
|
}),
|
|
42
106
|
leaves: Object.freeze({
|
|
43
107
|
color: Object.freeze([0.19, 0.48, 0.22]),
|
|
44
108
|
coverageScale: 1,
|
|
109
|
+
// How one tuft of foliage is built at a branch tip. Every field is
|
|
110
|
+
// nullable and every null forwards nothing, so a document authored before
|
|
111
|
+
// this block existed resolves to exactly the geometry it always did.
|
|
112
|
+
//
|
|
113
|
+
// This is the lever the canopy actually needs. `coverageScale` saturates
|
|
114
|
+
// at `clamp(size × coverageScale, 0.4)²` capped at 9, so on any tree
|
|
115
|
+
// larger than size 3 it is inert; `density` only scales an already-fixed
|
|
116
|
+
// per-tuft count; and `branches.children` buys cards by adding wood.
|
|
117
|
+
// The tuft itself was the one thing an author could not reach.
|
|
118
|
+
cluster: Object.freeze({
|
|
119
|
+
// 'cloud-cards' (default): cards fill a small sphere at the tip.
|
|
120
|
+
// 'layered-sprays': cards fill a flattened disc across the tip, which
|
|
121
|
+
// is how a cloud-pruned pad or a horizontally layered maple tier
|
|
122
|
+
// reads. 'needle-whorls': cards align their long axis to the twig,
|
|
123
|
+
// for needle-bearing branchlets.
|
|
124
|
+
architecture: null,
|
|
125
|
+
cards: null, // cards per tuft
|
|
126
|
+
// Bare every tuft below this fraction of the crown's own height. A
|
|
127
|
+
// skeleton that forks low enough to read multi-stemmed also hangs
|
|
128
|
+
// foliage down that low wood and buries the trunk it just built; in a
|
|
129
|
+
// garden the bare trunk is as much of the subject as the canopy. This
|
|
130
|
+
// lifts the foliage skirt without touching `branches.start`, so the
|
|
131
|
+
// limb structure keeps its low fork and only the leaves move.
|
|
132
|
+
crownBase: null,
|
|
133
|
+
// Multiplies the card count of every tuft that SURVIVES pruning, so a
|
|
134
|
+
// crown can be restructured at an unchanged card total rather than
|
|
135
|
+
// simply losing the foliage that pruning removed.
|
|
136
|
+
densityBoost: null,
|
|
137
|
+
// Keep only this many well-separated tufts and bare every other branch
|
|
138
|
+
// tip. This is what makes a cloud-pruned pine possible at all: a
|
|
139
|
+
// recursive skeleton hangs foliage on every terminal twig, and terminal
|
|
140
|
+
// twigs fill the crown evenly, so without pruning the crown is a dome
|
|
141
|
+
// no matter how each individual tuft is shaped.
|
|
142
|
+
pads: null,
|
|
143
|
+
// Horizontal plates: { count, thickness, jitter }. `pads` spaces
|
|
144
|
+
// masses isotropically, which is a pine's 3D scatter and is not a
|
|
145
|
+
// layered broadleaf: an Acer palmatum's signature is flat tiers with
|
|
146
|
+
// sky and limb showing BETWEEN them, and no isotropic count produces an
|
|
147
|
+
// anisotropic structure. This keeps tufts inside `count` height bands
|
|
148
|
+
// and bares the tufts in the gaps.
|
|
149
|
+
plates: null,
|
|
150
|
+
// 0..1, `layered-sprays` only. 0 builds each spray disc across the
|
|
151
|
+
// twig, so a twig rising at 50 deg carries a disc tilted at 50 deg. 1
|
|
152
|
+
// builds it flat with the GROUND, which is what makes a tier read as a
|
|
153
|
+
// tier rather than as a thicket of tilted saucers.
|
|
154
|
+
plateAlignment: null,
|
|
155
|
+
// Drops each plate's outer rim as a fraction of its own radius. A
|
|
156
|
+
// perfectly flat tier is a machined shelf — the Cornus controversa /
|
|
157
|
+
// acacia read — and this is what makes it hang instead.
|
|
158
|
+
plateDroop: null,
|
|
159
|
+
radius: null, // tuft radius, tree-local units
|
|
160
|
+
sizeRange: null, // [min, max] card size multiplier
|
|
161
|
+
sprayLayers: null, // layered-sprays: tiers stacked along the twig
|
|
162
|
+
spraySpread: null, // layered-sprays: pad radius across the twig
|
|
163
|
+
sprayThickness: null, // layered-sprays: pad depth along the twig
|
|
164
|
+
whorlArms: null, // needle-whorls: branchlets per whorl
|
|
165
|
+
whorlRadius: null, // needle-whorls: whorl radius
|
|
166
|
+
}),
|
|
45
167
|
density: 1,
|
|
46
168
|
palette: Object.freeze({}),
|
|
47
169
|
shape: 'round',
|
|
@@ -87,6 +209,73 @@ function reference(value) {
|
|
|
87
209
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
88
210
|
}
|
|
89
211
|
|
|
212
|
+
function nullableAngle(value, fallback) {
|
|
213
|
+
if (value === null) return null;
|
|
214
|
+
if (value === undefined) return fallback;
|
|
215
|
+
const number = Number(value);
|
|
216
|
+
return Number.isFinite(number)
|
|
217
|
+
? THREE.MathUtils.euclideanModulo(number, Math.PI * 2)
|
|
218
|
+
: fallback;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function nullableFinite(value, minimum, maximum) {
|
|
222
|
+
if (value === null || value === undefined) return null;
|
|
223
|
+
const number = Number(value);
|
|
224
|
+
return Number.isFinite(number)
|
|
225
|
+
? THREE.MathUtils.clamp(number, minimum, maximum)
|
|
226
|
+
: null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function nullableInteger(value, minimum, maximum) {
|
|
230
|
+
const resolved = nullableFinite(value, minimum, maximum);
|
|
231
|
+
return resolved === null ? null : Math.round(resolved);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function nullableRange(value, minimum, maximum) {
|
|
235
|
+
if (!Array.isArray(value) || value.length < 2) return null;
|
|
236
|
+
const low = nullableFinite(value[0], minimum, maximum);
|
|
237
|
+
const high = nullableFinite(value[1], minimum, maximum);
|
|
238
|
+
if (low === null || high === null) return null;
|
|
239
|
+
return [Math.min(low, high), Math.max(low, high)];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// `leaves.cluster.plates`. A count below 2 is not a plate structure — one
|
|
243
|
+
// band is the whole crown — so it resolves to null rather than silently
|
|
244
|
+
// pruning the tree down to a single slab of foliage.
|
|
245
|
+
function leafPlates(value) {
|
|
246
|
+
if (value === null || value === undefined || typeof value !== 'object') return null;
|
|
247
|
+
const count = nullableInteger(value.count, 2, 24);
|
|
248
|
+
if (count === null) return null;
|
|
249
|
+
return {
|
|
250
|
+
count,
|
|
251
|
+
jitter: nullableFinite(value.jitter, 0, 1) ?? 0,
|
|
252
|
+
thickness: nullableFinite(value.thickness, 0.02, 1) ?? 0.5,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function leafCluster(value) {
|
|
257
|
+
const source = plainObject(value);
|
|
258
|
+
return {
|
|
259
|
+
architecture: BRANCH_TREE_CANOPY_ARCHITECTURES.includes(source.architecture)
|
|
260
|
+
? source.architecture
|
|
261
|
+
: null,
|
|
262
|
+
cards: nullableInteger(source.cards, 1, 64),
|
|
263
|
+
crownBase: nullableFinite(source.crownBase, 0, 0.95),
|
|
264
|
+
densityBoost: nullableFinite(source.densityBoost, 0.1, 12),
|
|
265
|
+
pads: nullableInteger(source.pads, 1, 2000),
|
|
266
|
+
plates: leafPlates(source.plates),
|
|
267
|
+
plateAlignment: nullableFinite(source.plateAlignment, 0, 1),
|
|
268
|
+
plateDroop: nullableFinite(source.plateDroop, 0, 2),
|
|
269
|
+
radius: nullableFinite(source.radius, 0.01, 4),
|
|
270
|
+
sizeRange: nullableRange(source.sizeRange, 0.02, 6),
|
|
271
|
+
sprayLayers: nullableInteger(source.sprayLayers, 1, 12),
|
|
272
|
+
spraySpread: nullableFinite(source.spraySpread, 0.05, 4),
|
|
273
|
+
sprayThickness: nullableFinite(source.sprayThickness, 0, 2),
|
|
274
|
+
whorlArms: nullableInteger(source.whorlArms, 3, 24),
|
|
275
|
+
whorlRadius: nullableFinite(source.whorlRadius, 0.05, 3),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
90
279
|
function canopyPalette(value) {
|
|
91
280
|
const source = plainObject(value);
|
|
92
281
|
const result = {};
|
|
@@ -126,6 +315,8 @@ export function createBranchTreeSettings(options = {}) {
|
|
|
126
315
|
gnarliness: finite(branches.gnarliness, defaults.branches.gnarliness, 0, 0.6),
|
|
127
316
|
lengthRatio: finite(branches.lengthRatio, defaults.branches.lengthRatio, 0.15, 0.9),
|
|
128
317
|
levels: integer(branches.levels, defaults.branches.levels, 1, 4),
|
|
318
|
+
maxAttachments: nullableInteger(branches.maxAttachments, 8, 4000),
|
|
319
|
+
maxBranches: nullableInteger(branches.maxBranches, 8, 6000),
|
|
129
320
|
radialSegments: integer(
|
|
130
321
|
branches.radialSegments,
|
|
131
322
|
defaults.branches.radialSegments,
|
|
@@ -137,10 +328,12 @@ export function createBranchTreeSettings(options = {}) {
|
|
|
137
328
|
},
|
|
138
329
|
trunk: {
|
|
139
330
|
bend: finite(trunk.bend, defaults.trunk.bend, -1.2, 1.2),
|
|
331
|
+
bendDirection: nullableAngle(trunk.bendDirection, defaults.trunk.bendDirection),
|
|
140
332
|
color: color(trunk.color, defaults.trunk.color),
|
|
141
333
|
gnarl: finite(trunk.gnarl, defaults.trunk.gnarl, 0, 1),
|
|
142
334
|
height: finite(trunk.height, defaults.trunk.height, 0.2, 30),
|
|
143
335
|
lean: finite(trunk.lean, defaults.trunk.lean, -1.2, 1.2),
|
|
336
|
+
leanOffset: nullableAngle(trunk.leanOffset, defaults.trunk.leanOffset),
|
|
144
337
|
map: trunk.map?.isTexture ? trunk.map : null,
|
|
145
338
|
radialSegments: integer(
|
|
146
339
|
trunk.radialSegments,
|
|
@@ -150,11 +343,15 @@ export function createBranchTreeSettings(options = {}) {
|
|
|
150
343
|
),
|
|
151
344
|
radiusBottom: finite(trunk.radiusBottom, defaults.trunk.radiusBottom, 0.01, 5),
|
|
152
345
|
radiusTop: finite(trunk.radiusTop, defaults.trunk.radiusTop, 0.002, 3),
|
|
346
|
+
receiveShadow: trunk.receiveShadow === undefined
|
|
347
|
+
? defaults.trunk.receiveShadow
|
|
348
|
+
: Boolean(trunk.receiveShadow),
|
|
153
349
|
textureRef: reference(trunk.textureRef),
|
|
154
350
|
twist: finite(trunk.twist, defaults.trunk.twist, -Math.PI * 4, Math.PI * 4),
|
|
155
351
|
},
|
|
156
352
|
leaves: {
|
|
157
353
|
color: color(leaves.color, defaults.leaves.color),
|
|
354
|
+
cluster: leafCluster(leaves.cluster),
|
|
158
355
|
coverageScale: finite(
|
|
159
356
|
leaves.coverageScale,
|
|
160
357
|
defaults.leaves.coverageScale,
|
|
@@ -226,6 +423,113 @@ export function parseBranchTreeDocument(input) {
|
|
|
226
423
|
};
|
|
227
424
|
}
|
|
228
425
|
|
|
426
|
+
/**
|
|
427
|
+
* The StylizedTree option object a BranchTree builds from its settings.
|
|
428
|
+
*
|
|
429
|
+
* Shared by the constructor and by createBranchTreeRecipe so the LOD compiler
|
|
430
|
+
* meshes exactly the tree the runtime renders. Runtime-only inputs (live
|
|
431
|
+
* textures, foliage/wind state, shader profile) are layered on by the caller.
|
|
432
|
+
*/
|
|
433
|
+
export function branchTreeStylizedOptions(settingsInput = {}) {
|
|
434
|
+
const settings = settingsInput?.branches && settingsInput?.leaves && settingsInput?.trunk
|
|
435
|
+
? settingsInput
|
|
436
|
+
: createBranchTreeSettings(settingsInput);
|
|
437
|
+
// Only forward what was actually authored. StylizedTree keeps its
|
|
438
|
+
// tips-placement and branching-generator canopy presets unless a caller
|
|
439
|
+
// hands it a value that differs from the module default, so forwarding a
|
|
440
|
+
// resolved default here would silently overwrite those presets and move
|
|
441
|
+
// every existing document's geometry.
|
|
442
|
+
const cluster = settings.leaves.cluster ?? {};
|
|
443
|
+
const canopyCluster = {};
|
|
444
|
+
const forward = (key, value) => {
|
|
445
|
+
if (value !== null && value !== undefined) canopyCluster[key] = value;
|
|
446
|
+
};
|
|
447
|
+
forward('architecture', cluster.architecture);
|
|
448
|
+
forward('cardsPerCluster', cluster.cards);
|
|
449
|
+
forward('padCount', cluster.pads);
|
|
450
|
+
forward('crownBase', cluster.crownBase);
|
|
451
|
+
forward('padDensityBoost', cluster.densityBoost);
|
|
452
|
+
forward('padLayers', cluster.plates);
|
|
453
|
+
forward('plateAlignment', cluster.plateAlignment);
|
|
454
|
+
forward('plateDroop', cluster.plateDroop);
|
|
455
|
+
forward('clusterRadius', cluster.radius);
|
|
456
|
+
forward('cardSizeRange', cluster.sizeRange);
|
|
457
|
+
forward('sprayLayers', cluster.sprayLayers);
|
|
458
|
+
forward('spraySpread', cluster.spraySpread);
|
|
459
|
+
forward('sprayThickness', cluster.sprayThickness);
|
|
460
|
+
forward('whorlArms', cluster.whorlArms);
|
|
461
|
+
forward('whorlRadius', cluster.whorlRadius);
|
|
462
|
+
|
|
463
|
+
const skeletonBudget = {};
|
|
464
|
+
if (settings.branches.maxBranches !== null) {
|
|
465
|
+
skeletonBudget.maxBranches = settings.branches.maxBranches;
|
|
466
|
+
}
|
|
467
|
+
if (settings.branches.maxAttachments !== null) {
|
|
468
|
+
skeletonBudget.maxAttachments = settings.branches.maxAttachments;
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
tree: {
|
|
472
|
+
canopyColor: settings.leaves.color,
|
|
473
|
+
canopyPalette: settings.leaves.palette,
|
|
474
|
+
canopyDepth: 0.92,
|
|
475
|
+
canopyScale: 1,
|
|
476
|
+
canopyWidth: 1.18,
|
|
477
|
+
leafDensity: settings.leaves.density,
|
|
478
|
+
leafPlacement: 'tips',
|
|
479
|
+
seed: settings.seed,
|
|
480
|
+
size: settings.size,
|
|
481
|
+
trunkColor: settings.trunk.color,
|
|
482
|
+
trunkReceiveShadow: settings.trunk.receiveShadow,
|
|
483
|
+
},
|
|
484
|
+
trunk: {
|
|
485
|
+
bend: settings.trunk.bend,
|
|
486
|
+
bendDirection: settings.trunk.bendDirection,
|
|
487
|
+
gnarl: settings.trunk.gnarl,
|
|
488
|
+
height: settings.trunk.height,
|
|
489
|
+
lean: settings.trunk.lean,
|
|
490
|
+
leanOffset: settings.trunk.leanOffset,
|
|
491
|
+
radialSegments: settings.trunk.radialSegments,
|
|
492
|
+
radiusBottom: settings.trunk.radiusBottom,
|
|
493
|
+
radiusTop: settings.trunk.radiusTop,
|
|
494
|
+
twist: settings.trunk.twist,
|
|
495
|
+
},
|
|
496
|
+
skeleton: {
|
|
497
|
+
branchAngle: settings.branches.angle,
|
|
498
|
+
branchStart: settings.branches.start,
|
|
499
|
+
childrenCount: settings.branches.children,
|
|
500
|
+
forceStrength: settings.branches.forceStrength,
|
|
501
|
+
generator: 'branching',
|
|
502
|
+
gnarliness: settings.branches.gnarliness,
|
|
503
|
+
lengthRatio: settings.branches.lengthRatio,
|
|
504
|
+
levels: settings.branches.levels,
|
|
505
|
+
radialSegments: settings.branches.radialSegments,
|
|
506
|
+
radiusRatio: settings.branches.radiusRatio,
|
|
507
|
+
...skeletonBudget,
|
|
508
|
+
},
|
|
509
|
+
canopy: {
|
|
510
|
+
// Physical tree size still participates so leaf cards retain a useful
|
|
511
|
+
// world-space density. The explicit factor lets an author tune that
|
|
512
|
+
// coverage independently instead of enlarging the whole tree.
|
|
513
|
+
coverageScale: settings.size * settings.leaves.coverageScale,
|
|
514
|
+
...canopyCluster,
|
|
515
|
+
// BranchTree knows exactly which tuft fields the author supplied,
|
|
516
|
+
// because every unauthored one is null. Declaring them keeps a value
|
|
517
|
+
// that happens to equal a module default from being mistaken for
|
|
518
|
+
// "unset" and quietly replaced by the branching-generator preset.
|
|
519
|
+
explicit: Object.keys(canopyCluster),
|
|
520
|
+
},
|
|
521
|
+
leafShape: settings.leaves.map ? null : { preset: settings.leaves.shape },
|
|
522
|
+
roots: { preset: settings.roots },
|
|
523
|
+
// A trunk textureRef that names a registered bark profile (or one of its
|
|
524
|
+
// short aliases, e.g. 'beech') selects that profile. An unrecognized ref
|
|
525
|
+
// stays caller-owned provenance and falls through to the style default,
|
|
526
|
+
// so existing documents keep working unchanged.
|
|
527
|
+
trunkSurfaceProfile: settings.trunk.map
|
|
528
|
+
? null
|
|
529
|
+
: resolveTreeSurfaceProfileId(settings.trunk.textureRef),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
229
533
|
/**
|
|
230
534
|
* Stable first-party procedural broadleaf tree.
|
|
231
535
|
*
|
|
@@ -238,52 +542,11 @@ export class BranchTree extends StylizedTree {
|
|
|
238
542
|
const settings = createBranchTreeSettings(options);
|
|
239
543
|
const source = plainObject(options);
|
|
240
544
|
super({
|
|
241
|
-
|
|
242
|
-
canopyColor: settings.leaves.color,
|
|
243
|
-
canopyPalette: settings.leaves.palette,
|
|
244
|
-
canopyDepth: 0.92,
|
|
245
|
-
canopyScale: 1,
|
|
246
|
-
canopyWidth: 1.18,
|
|
247
|
-
leafDensity: settings.leaves.density,
|
|
248
|
-
leafPlacement: 'tips',
|
|
249
|
-
seed: settings.seed,
|
|
250
|
-
size: settings.size,
|
|
251
|
-
trunkColor: settings.trunk.color,
|
|
252
|
-
},
|
|
253
|
-
trunk: {
|
|
254
|
-
bend: settings.trunk.bend,
|
|
255
|
-
gnarl: settings.trunk.gnarl,
|
|
256
|
-
height: settings.trunk.height,
|
|
257
|
-
lean: settings.trunk.lean,
|
|
258
|
-
radialSegments: settings.trunk.radialSegments,
|
|
259
|
-
radiusBottom: settings.trunk.radiusBottom,
|
|
260
|
-
radiusTop: settings.trunk.radiusTop,
|
|
261
|
-
twist: settings.trunk.twist,
|
|
262
|
-
},
|
|
263
|
-
skeleton: {
|
|
264
|
-
branchAngle: settings.branches.angle,
|
|
265
|
-
branchStart: settings.branches.start,
|
|
266
|
-
childrenCount: settings.branches.children,
|
|
267
|
-
forceStrength: settings.branches.forceStrength,
|
|
268
|
-
generator: 'branching',
|
|
269
|
-
gnarliness: settings.branches.gnarliness,
|
|
270
|
-
lengthRatio: settings.branches.lengthRatio,
|
|
271
|
-
levels: settings.branches.levels,
|
|
272
|
-
radialSegments: settings.branches.radialSegments,
|
|
273
|
-
radiusRatio: settings.branches.radiusRatio,
|
|
274
|
-
},
|
|
545
|
+
...branchTreeStylizedOptions(settings),
|
|
275
546
|
foliage: {
|
|
276
547
|
...plainObject(source.foliage),
|
|
277
548
|
...(settings.leaves.map ? { leafMap: settings.leaves.map } : {}),
|
|
278
549
|
},
|
|
279
|
-
canopy: {
|
|
280
|
-
// Physical tree size still participates so leaf cards retain a useful
|
|
281
|
-
// world-space density. The explicit factor lets an author tune that
|
|
282
|
-
// coverage independently instead of enlarging the whole tree.
|
|
283
|
-
coverageScale: settings.size * settings.leaves.coverageScale,
|
|
284
|
-
},
|
|
285
|
-
leafShape: settings.leaves.map ? null : { preset: settings.leaves.shape },
|
|
286
|
-
roots: { preset: settings.roots },
|
|
287
550
|
styleTarget: source.styleTarget ?? {},
|
|
288
551
|
trunkMap: settings.trunk.map,
|
|
289
552
|
vegetationShader: source.vegetationShader ?? null,
|
|
@@ -297,6 +560,28 @@ export class BranchTree extends StylizedTree {
|
|
|
297
560
|
}
|
|
298
561
|
}
|
|
299
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Wrap BranchTree settings in the tree-recipe envelope the LOD compiler
|
|
565
|
+
* consumes, tagged with BranchTree's own architecture so it is budgeted
|
|
566
|
+
* against the BranchTree triangle envelope rather than the legacy default.
|
|
567
|
+
*
|
|
568
|
+
* The portable BranchTree document stays the asset's identity; this is the
|
|
569
|
+
* adapter that lets `compileTreeLodLevels` build its three LODs from it.
|
|
570
|
+
*/
|
|
571
|
+
export function createBranchTreeRecipe(options = {}, { id = null, label = null } = {}) {
|
|
572
|
+
const settings = createBranchTreeSettings(options);
|
|
573
|
+
const recipe = {
|
|
574
|
+
schema: 'treeRecipe',
|
|
575
|
+
version: 3,
|
|
576
|
+
type: 'tree',
|
|
577
|
+
architecture: { ...BRANCH_TREE_ARCHITECTURE },
|
|
578
|
+
options: branchTreeStylizedOptions(settings),
|
|
579
|
+
};
|
|
580
|
+
if (id) recipe.id = id;
|
|
581
|
+
if (label) recipe.label = label;
|
|
582
|
+
return recipe;
|
|
583
|
+
}
|
|
584
|
+
|
|
300
585
|
export function createBranchTree(options = {}) {
|
|
301
586
|
return new BranchTree(options);
|
|
302
587
|
}
|
package/src/vegetation/index.js
CHANGED
|
@@ -4,9 +4,29 @@ export * from './grassClump.js';
|
|
|
4
4
|
export * from './callMeSenseiGrass.js';
|
|
5
5
|
export * from './stylizedFlowers.js';
|
|
6
6
|
export * from './flowerSpecies.js';
|
|
7
|
-
|
|
7
|
+
// Public tree construction/runtime surface. Authored tree recipes are assets
|
|
8
|
+
// and are discovered through ToonLab MCP; showcase/example recipe arrays are
|
|
9
|
+
// deliberately not re-exported from the npm package.
|
|
10
|
+
export {
|
|
11
|
+
DEFAULT_STYLIZED_TREE_SETTINGS,
|
|
12
|
+
STYLIZED_TREE_SETTING_FIELD_SCHEMA,
|
|
13
|
+
STYLIZED_TREE_SETTING_GROUPS,
|
|
14
|
+
TREE_RECIPE_SCHEMA,
|
|
15
|
+
TREE_RECIPE_VERSION,
|
|
16
|
+
TREE_TRUNK_STYLES,
|
|
17
|
+
StylizedTree,
|
|
18
|
+
createBranchTubeGeometry,
|
|
19
|
+
createBranchingTreeSkeleton,
|
|
20
|
+
createStylizedTreeSettings,
|
|
21
|
+
createTreeSkeleton,
|
|
22
|
+
createTreeTrunkGeometry,
|
|
23
|
+
getStylizedTreePresetOptions,
|
|
24
|
+
polarProfileFromOutline,
|
|
25
|
+
registerStylizedTreePreset,
|
|
26
|
+
resolvePadPruning,
|
|
27
|
+
serializableTreeOptions,
|
|
28
|
+
} from './stylizedTree.js';
|
|
8
29
|
export * from './branchTree.js';
|
|
9
|
-
export * from './legacyTreePresets.js';
|
|
10
30
|
export * from './stylizedTreeFoliage.js';
|
|
11
31
|
export * from './stylizedBush.js';
|
|
12
32
|
export * from './stylizedFlower.js';
|
|
@@ -19,10 +39,12 @@ export * from './contactShadowField.js';
|
|
|
19
39
|
export * from './vegetationShaders.js';
|
|
20
40
|
export {
|
|
21
41
|
TREE_SURFACE_PROFILES,
|
|
42
|
+
TREE_SURFACE_PROFILE_ALIASES,
|
|
22
43
|
TREE_SURFACE_PROFILE_DEFAULTS,
|
|
23
44
|
createTreeSurfaceTexture,
|
|
24
45
|
createTreeSurfaceTextureData,
|
|
25
46
|
getTreeSurfaceProfileOptions,
|
|
47
|
+
resolveTreeSurfaceProfileId,
|
|
26
48
|
} from './treeSurfaceTextures.js';
|
|
27
49
|
export * from './importedVegetationMaterial.js';
|
|
28
50
|
export * from './grassPalettes.js';
|