@holoscript/core 8.0.0 → 8.0.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/README.md +2 -2
- package/dist/{GLTFPipeline-M7WSSJED.cjs → GLTFPipeline-N6B6PJ6G.cjs} +10 -9
- package/dist/{GLTFPipeline-276GE64O.js → GLTFPipeline-P3WMNYCN.js} +6 -5
- package/dist/{HoloScriptRuntime-3URVTKPJ.js → HoloScriptRuntime-CBYJX3LZ.js} +3 -3
- package/dist/{HoloScriptRuntime-SXEAEU2N.cjs → HoloScriptRuntime-VJ374RER.cjs} +4 -4
- package/dist/{chunk-JJBE3TIH.cjs → chunk-2IWC4FP7.cjs} +6 -6
- package/dist/{chunk-S4W6Z2MN.js → chunk-2X7NGX3M.js} +3 -3
- package/dist/chunk-4GU5GCIY.js +1384 -0
- package/dist/{chunk-T2QCTZMO.js → chunk-4SDKXQ4C.js} +3 -3
- package/dist/{chunk-253GBQZY.cjs → chunk-5H2G27PK.cjs} +35 -35
- package/dist/{chunk-C2A5DUMF.cjs → chunk-5NWINHR5.cjs} +35 -35
- package/dist/chunk-6Q7RC2MK.cjs +1418 -0
- package/dist/{chunk-D54CNIAJ.cjs → chunk-7H33U5SM.cjs} +2 -2
- package/dist/{chunk-CXFDRBQU.cjs → chunk-7M3PNU2M.cjs} +7 -7
- package/dist/{chunk-B4TFTDG4.js → chunk-7T25IEZ2.js} +3 -3
- package/dist/{chunk-7CUEMLDZ.cjs → chunk-7V4JI5TB.cjs} +540 -38
- package/dist/{chunk-WJIHFLMJ.js → chunk-ADADVMXY.js} +2 -2
- package/dist/{chunk-GQT4TYKY.cjs → chunk-ALC6K7GL.cjs} +40 -19
- package/dist/{chunk-3HYYMEXI.cjs → chunk-AOVGZ25K.cjs} +17 -12
- package/dist/{chunk-NHWSN2SD.cjs → chunk-AVJHHUC5.cjs} +17 -17
- package/dist/{chunk-UM6C3CGK.js → chunk-BGY3SDG3.js} +24 -3
- package/dist/{chunk-JCLBKPMX.js → chunk-BOVPWH4H.js} +48 -4
- package/dist/{chunk-ZZEI7MV4.js → chunk-CCATWPXM.js} +3 -3
- package/dist/{chunk-NBMHU6SW.js → chunk-CMT5NMMS.js} +3 -3
- package/dist/{chunk-HINXWAOD.cjs → chunk-CR4FMIWL.cjs} +11 -11
- package/dist/{chunk-W7VDOASB.cjs → chunk-CR7XRXRN.cjs} +5 -5
- package/dist/{chunk-AYBXB26F.js → chunk-CSD27UNQ.js} +5 -5
- package/dist/{chunk-IZM5VQZD.js → chunk-D3STJ56J.js} +3 -3
- package/dist/{chunk-ZBQXLH2L.js → chunk-DHO2YRIM.js} +3 -3
- package/dist/{chunk-JX7YL6ZE.js → chunk-E6XXE66V.js} +4 -4
- package/dist/{chunk-62FBK3B7.js → chunk-FFAOS5HQ.js} +4 -4
- package/dist/{chunk-NV4VMVXU.js → chunk-FL33V6U7.js} +3 -3
- package/dist/{chunk-XUUVDHIF.cjs → chunk-HKEXJUWS.cjs} +48 -3
- package/dist/{chunk-NXZFOPVD.cjs → chunk-HNDK4IQU.cjs} +16 -16
- package/dist/{chunk-RVBHAXFG.js → chunk-HR2OSYVQ.js} +16 -11
- package/dist/{chunk-LICPB5HZ.cjs → chunk-IUDGEUDO.cjs} +10 -10
- package/dist/{chunk-73FELZVJ.cjs → chunk-IWC5DM2M.cjs} +11 -11
- package/dist/{chunk-NGKREZ36.js → chunk-J52VSAQ7.js} +505 -5
- package/dist/{chunk-PHVRG4I2.js → chunk-J7XZV333.js} +3 -3
- package/dist/{chunk-K4VVR7CN.cjs → chunk-JBNDTXXV.cjs} +15 -15
- package/dist/{chunk-3NFFOWGG.js → chunk-JIBL5LRK.js} +2 -2
- package/dist/{chunk-HZSPDCXV.cjs → chunk-K574J76X.cjs} +3 -3
- package/dist/{chunk-SJBGLS25.cjs → chunk-KHDZJE5D.cjs} +5 -5
- package/dist/{chunk-KH4CNH66.cjs → chunk-L5LKKENW.cjs} +4 -4
- package/dist/{chunk-IVSZDECV.js → chunk-LHAXPADA.js} +3 -3
- package/dist/{chunk-EUDQTK7N.cjs → chunk-MQWAT42R.cjs} +2 -2
- package/dist/{chunk-KVRSTFNH.cjs → chunk-NCWCUZNS.cjs} +8 -8
- package/dist/{chunk-MGE5Z4GS.js → chunk-OH2FAIVU.js} +3 -3
- package/dist/{chunk-UMABK6SI.js → chunk-PY37LNDP.js} +3 -3
- package/dist/{chunk-23J755AR.js → chunk-QD32ADK6.js} +3 -3
- package/dist/{chunk-HZTDET3N.js → chunk-R24NNAEG.js} +3 -3
- package/dist/{chunk-6DPSFNJB.cjs → chunk-ROHLEP2W.cjs} +16 -16
- package/dist/{chunk-25IQCHTO.cjs → chunk-TWUXUCEW.cjs} +35 -35
- package/dist/{chunk-3AO425MZ.cjs → chunk-UFZZ7TSN.cjs} +17 -17
- package/dist/{chunk-BE4UJCQT.js → chunk-UWJHCOH2.js} +3 -3
- package/dist/{chunk-KEFNV2WM.cjs → chunk-X3UVCWNL.cjs} +13 -13
- package/dist/{chunk-7HIASF7S.cjs → chunk-XB274MW5.cjs} +10 -10
- package/dist/{chunk-KJLB4OPV.js → chunk-YN2YWTXW.js} +3 -3
- package/dist/{chunk-IPWSXCCN.js → chunk-YVD3CSO3.js} +3 -3
- package/dist/{chunk-F43YAA34.js → chunk-YVOAF4ML.js} +3 -3
- package/dist/{chunk-OR4Q2BGL.cjs → chunk-YWOBW2PV.cjs} +15 -15
- package/dist/compiler/android-xr.cjs +5 -5
- package/dist/compiler/android-xr.js +3 -3
- package/dist/compiler/babylon.cjs +4 -4
- package/dist/compiler/babylon.js +3 -3
- package/dist/compiler/business-quest.cjs +9 -9
- package/dist/compiler/business-quest.js +1 -1
- package/dist/compiler/domain-block-utils.cjs +154 -150
- package/dist/compiler/domain-block-utils.js +2 -2
- package/dist/compiler/gltf-pipeline.cjs +8 -8
- package/dist/compiler/gltf-pipeline.js +1 -1
- package/dist/compiler/godot.cjs +4 -4
- package/dist/compiler/godot.js +3 -3
- package/dist/compiler/index.cjs +129 -581
- package/dist/compiler/index.d.ts +12 -0
- package/dist/compiler/index.js +42 -498
- package/dist/compiler/multi-layer.cjs +5 -5
- package/dist/compiler/multi-layer.js +4 -4
- package/dist/compiler/nodetoy.d.ts +69 -0
- package/dist/compiler/openxr.cjs +4 -4
- package/dist/compiler/openxr.js +3 -3
- package/dist/compiler/playcanvas.cjs +4 -4
- package/dist/compiler/playcanvas.js +3 -3
- package/dist/compiler/r3f.cjs +9 -8
- package/dist/compiler/r3f.js +4 -3
- package/dist/compiler/sdf.cjs +5 -5
- package/dist/compiler/sdf.js +3 -3
- package/dist/compiler/unity.cjs +4 -4
- package/dist/compiler/unity.js +3 -3
- package/dist/compiler/unreal.cjs +5 -5
- package/dist/compiler/unreal.js +3 -3
- package/dist/compiler/urdf.cjs +12 -12
- package/dist/compiler/urdf.js +3 -3
- package/dist/compiler/usd-physics.cjs +5 -5
- package/dist/compiler/usd-physics.js +2 -2
- package/dist/compiler/visionos.cjs +4 -4
- package/dist/compiler/visionos.js +3 -3
- package/dist/compiler/vrchat.cjs +5 -5
- package/dist/compiler/vrchat.js +3 -3
- package/dist/compiler/vrr.cjs +5 -5
- package/dist/compiler/vrr.js +3 -3
- package/dist/compiler/wasm.cjs +8 -8
- package/dist/compiler/wasm.js +3 -3
- package/dist/compiler/webgpu.cjs +4 -4
- package/dist/compiler/webgpu.js +3 -3
- package/dist/debugger.cjs +4 -4
- package/dist/debugger.js +2 -2
- package/dist/dist-ACXG6IH5.cjs +3113 -0
- package/dist/dist-VD4L6V7Q.js +3076 -0
- package/dist/index.cjs +1915 -488
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1480 -53
- package/dist/reconstruction/index.cjs +31 -31
- package/dist/reconstruction/index.js +2 -2
- package/dist/runtime.cjs +6 -6
- package/dist/runtime.js +2 -2
- package/dist/src-4ROC227P.cjs +2094 -0
- package/dist/src-HCCCSYP7.js +2073 -0
- package/dist/traits/botanical-lotus.cjs +146 -10
- package/dist/traits/botanical-lotus.d.ts +330 -0
- package/dist/traits/botanical-lotus.js +4 -1
- package/dist/traits/index.cjs +594 -455
- package/dist/traits/index.js +9 -5
- package/package.json +20 -20
- package/dist/chunk-VECQYRXL.js +0 -474
- package/dist/chunk-WDT3JTGM.cjs +0 -483
|
@@ -0,0 +1,1384 @@
|
|
|
1
|
+
import { __esm } from './chunk-OL46QLBJ.js';
|
|
2
|
+
|
|
3
|
+
// src/traits/BotanicalLotusTrait.ts
|
|
4
|
+
function cloneDefaultConfig() {
|
|
5
|
+
const d = DEFAULT_BOTANICAL_LOTUS_CONFIG;
|
|
6
|
+
return {
|
|
7
|
+
...d,
|
|
8
|
+
source: { ...d.source },
|
|
9
|
+
reference_anchors: d.reference_anchors.map((a) => ({ ...a })),
|
|
10
|
+
material: {
|
|
11
|
+
...d.material,
|
|
12
|
+
subsurface_radius_rgb: [...d.material.subsurface_radius_rgb]
|
|
13
|
+
},
|
|
14
|
+
colors: { ...d.colors },
|
|
15
|
+
geometry: {
|
|
16
|
+
...d.geometry,
|
|
17
|
+
petal_rings: d.geometry.petal_rings.map((r) => ({ ...r }))
|
|
18
|
+
},
|
|
19
|
+
renderer: {
|
|
20
|
+
...d.renderer,
|
|
21
|
+
requires: [...d.renderer.requires],
|
|
22
|
+
lod: { ...d.renderer.lod }
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function isFiniteNumber(value) {
|
|
27
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
28
|
+
}
|
|
29
|
+
function addRangeError(errors, label, value, min, max) {
|
|
30
|
+
if (!isFiniteNumber(value) || value < min || value > max) {
|
|
31
|
+
errors.push(`${label} must be a finite number in [${min}, ${max}]`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function validateHexColors(errors, colors) {
|
|
35
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
36
|
+
if (!HEX_COLOR_PATTERN.test(value)) {
|
|
37
|
+
errors.push(`colors.${key} must be a 6-digit hex color`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function validateReferenceAnchor(errors, anchor, index) {
|
|
42
|
+
const prefix = `reference_anchors[${index}]`;
|
|
43
|
+
if (!anchor.id || typeof anchor.id !== "string") {
|
|
44
|
+
errors.push(`${prefix}.id is required`);
|
|
45
|
+
}
|
|
46
|
+
if (!anchor.uri || typeof anchor.uri !== "string") {
|
|
47
|
+
errors.push(`${prefix}.uri is required`);
|
|
48
|
+
}
|
|
49
|
+
if (anchor.status !== "pending_media_ingest" && anchor.status !== "hashed" && anchor.status !== "wallet_signed") {
|
|
50
|
+
errors.push(`${prefix}.status must be pending_media_ingest, hashed, or wallet_signed`);
|
|
51
|
+
}
|
|
52
|
+
if (anchor.status !== "pending_media_ingest") {
|
|
53
|
+
if (!anchor.content_hash || !SHA256_HASH_PATTERN.test(anchor.content_hash)) {
|
|
54
|
+
errors.push(`${prefix}.content_hash must be sha256:<64 lowercase hex chars>`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (anchor.status === "wallet_signed" && !anchor.wallet_signature) {
|
|
58
|
+
errors.push(`${prefix}.wallet_signature is required when status is wallet_signed`);
|
|
59
|
+
}
|
|
60
|
+
if (anchor.width !== void 0 && (!Number.isInteger(anchor.width) || anchor.width <= 0)) {
|
|
61
|
+
errors.push(`${prefix}.width must be a positive integer when present`);
|
|
62
|
+
}
|
|
63
|
+
if (anchor.height !== void 0 && (!Number.isInteger(anchor.height) || anchor.height <= 0)) {
|
|
64
|
+
errors.push(`${prefix}.height must be a positive integer when present`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function normalizeBotanicalLotusConfig(input = {}) {
|
|
68
|
+
const d = cloneDefaultConfig();
|
|
69
|
+
const inputGeometry = input.geometry ?? {};
|
|
70
|
+
const inputRenderer = input.renderer ?? {};
|
|
71
|
+
const petalRings = Array.isArray(inputGeometry.petal_rings) ? inputGeometry.petal_rings.map((r) => ({ ...r })) : d.geometry.petal_rings.map((r) => ({ ...r }));
|
|
72
|
+
return {
|
|
73
|
+
schema: input.schema ?? d.schema,
|
|
74
|
+
status: input.status ?? d.status,
|
|
75
|
+
source: {
|
|
76
|
+
...d.source,
|
|
77
|
+
...input.source ?? {}
|
|
78
|
+
},
|
|
79
|
+
reference_anchors: Array.isArray(input.reference_anchors) ? input.reference_anchors.map((a) => ({ ...a })) : d.reference_anchors,
|
|
80
|
+
material: {
|
|
81
|
+
...d.material,
|
|
82
|
+
...input.material ?? {},
|
|
83
|
+
subsurface_radius_rgb: [
|
|
84
|
+
...input.material?.subsurface_radius_rgb ?? d.material.subsurface_radius_rgb
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
colors: {
|
|
88
|
+
...d.colors,
|
|
89
|
+
...input.colors ?? {}
|
|
90
|
+
},
|
|
91
|
+
geometry: {
|
|
92
|
+
...d.geometry,
|
|
93
|
+
...inputGeometry,
|
|
94
|
+
petal_rings: petalRings
|
|
95
|
+
},
|
|
96
|
+
renderer: {
|
|
97
|
+
...d.renderer,
|
|
98
|
+
...inputRenderer,
|
|
99
|
+
requires: [...inputRenderer.requires ?? d.renderer.requires],
|
|
100
|
+
lod: {
|
|
101
|
+
...d.renderer.lod,
|
|
102
|
+
...inputRenderer.lod ?? {}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function validateBotanicalLotusConfig(input = {}) {
|
|
108
|
+
const config = normalizeBotanicalLotusConfig(input);
|
|
109
|
+
const errors = [];
|
|
110
|
+
const { material, geometry } = config;
|
|
111
|
+
if (config.schema !== "holoscript.trait.botanical_lotus.v0") {
|
|
112
|
+
errors.push("schema must be holoscript.trait.botanical_lotus.v0");
|
|
113
|
+
}
|
|
114
|
+
if (config.status !== "visual_seed" && config.status !== "content_hashed" && config.status !== "wallet_signed") {
|
|
115
|
+
errors.push("status must be visual_seed, content_hashed, or wallet_signed");
|
|
116
|
+
}
|
|
117
|
+
if (config.reference_anchors.length === 0) {
|
|
118
|
+
errors.push("reference_anchors must include at least one anchor");
|
|
119
|
+
}
|
|
120
|
+
config.reference_anchors.forEach(
|
|
121
|
+
(anchor, index) => validateReferenceAnchor(errors, anchor, index)
|
|
122
|
+
);
|
|
123
|
+
addRangeError(errors, "material.subsurface_scattering", material.subsurface_scattering, 0, 1);
|
|
124
|
+
if (material.subsurface_radius_rgb.length !== 3) {
|
|
125
|
+
errors.push("material.subsurface_radius_rgb must contain exactly three values");
|
|
126
|
+
}
|
|
127
|
+
material.subsurface_radius_rgb.forEach(
|
|
128
|
+
(value, index) => addRangeError(errors, `material.subsurface_radius_rgb[${index}]`, value, 0, 2)
|
|
129
|
+
);
|
|
130
|
+
addRangeError(
|
|
131
|
+
errors,
|
|
132
|
+
"material.petal_translucency_base",
|
|
133
|
+
material.petal_translucency_base,
|
|
134
|
+
0,
|
|
135
|
+
1
|
|
136
|
+
);
|
|
137
|
+
addRangeError(
|
|
138
|
+
errors,
|
|
139
|
+
"material.petal_translucency_edge",
|
|
140
|
+
material.petal_translucency_edge,
|
|
141
|
+
0,
|
|
142
|
+
1
|
|
143
|
+
);
|
|
144
|
+
addRangeError(errors, "material.roughness", material.roughness, 0, 1);
|
|
145
|
+
addRangeError(errors, "material.ior", material.ior, 1, 2.5);
|
|
146
|
+
addRangeError(
|
|
147
|
+
errors,
|
|
148
|
+
"material.vein_normal_intensity",
|
|
149
|
+
material.vein_normal_intensity,
|
|
150
|
+
0,
|
|
151
|
+
0.25
|
|
152
|
+
);
|
|
153
|
+
addRangeError(errors, "material.edge_curl_intensity", material.edge_curl_intensity, 0, 1);
|
|
154
|
+
addRangeError(errors, "material.gravity_sag_outer", material.gravity_sag_outer, 0, 1);
|
|
155
|
+
addRangeError(errors, "material.sheen", material.sheen, 0, 1);
|
|
156
|
+
addRangeError(errors, "material.sheen_roughness", material.sheen_roughness, 0, 1);
|
|
157
|
+
if (!HEX_COLOR_PATTERN.test(material.sheen_color)) {
|
|
158
|
+
errors.push("material.sheen_color must be a 6-digit hex color");
|
|
159
|
+
}
|
|
160
|
+
validateHexColors(errors, config.colors);
|
|
161
|
+
if (geometry.petal_rings.length === 0) {
|
|
162
|
+
errors.push("geometry.petal_rings must include at least one ring");
|
|
163
|
+
}
|
|
164
|
+
geometry.petal_rings.forEach((ring, index) => {
|
|
165
|
+
if (!ring.name) {
|
|
166
|
+
errors.push(`geometry.petal_rings[${index}].name is required`);
|
|
167
|
+
}
|
|
168
|
+
if (!Number.isInteger(ring.count) || ring.count <= 0) {
|
|
169
|
+
errors.push(`geometry.petal_rings[${index}].count must be a positive integer`);
|
|
170
|
+
}
|
|
171
|
+
addRangeError(errors, `geometry.petal_rings[${index}].cup`, ring.cup, 0, 1);
|
|
172
|
+
addRangeError(
|
|
173
|
+
errors,
|
|
174
|
+
`geometry.petal_rings[${index}].gravity_sag`,
|
|
175
|
+
ring.gravity_sag,
|
|
176
|
+
0,
|
|
177
|
+
1
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
if (!Number.isInteger(geometry.stamen_filament_count) || geometry.stamen_filament_count <= 0) {
|
|
181
|
+
errors.push("geometry.stamen_filament_count must be a positive integer");
|
|
182
|
+
}
|
|
183
|
+
if (config.renderer.requires.length === 0) {
|
|
184
|
+
errors.push("renderer.requires must include at least one render capability");
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
ok: errors.length === 0,
|
|
188
|
+
errors,
|
|
189
|
+
config
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function assertBotanicalLotusConfig(input = {}) {
|
|
193
|
+
const result = validateBotanicalLotusConfig(input);
|
|
194
|
+
if (!result.ok) {
|
|
195
|
+
throw new Error(`Invalid botanical_lotus config: ${result.errors.join("; ")}`);
|
|
196
|
+
}
|
|
197
|
+
return result.config;
|
|
198
|
+
}
|
|
199
|
+
function deriveBotanicalLotusAnchorStatus(anchors) {
|
|
200
|
+
if (anchors.length > 0 && anchors.every((anchor) => anchor.status === "wallet_signed")) {
|
|
201
|
+
return "wallet_signed";
|
|
202
|
+
}
|
|
203
|
+
if (anchors.length > 0 && anchors.every((anchor) => anchor.status === "hashed" || anchor.status === "wallet_signed")) {
|
|
204
|
+
return "hashed";
|
|
205
|
+
}
|
|
206
|
+
return "pending_media_ingest";
|
|
207
|
+
}
|
|
208
|
+
function getBotanicalLotusPetalCount(input = {}) {
|
|
209
|
+
const config = normalizeBotanicalLotusConfig(input);
|
|
210
|
+
return config.geometry.petal_rings.reduce((sum, ring) => sum + ring.count, 0);
|
|
211
|
+
}
|
|
212
|
+
function createBotanicalLotusRenderProfile(input = {}, placement, lighting) {
|
|
213
|
+
const config = assertBotanicalLotusConfig(input);
|
|
214
|
+
const anchorStatus = deriveBotanicalLotusAnchorStatus(config.reference_anchors);
|
|
215
|
+
const petalRings = config.geometry.petal_rings.map((ring, index) => {
|
|
216
|
+
const base = RING_RENDER_DEFAULTS[ring.name] ?? {
|
|
217
|
+
radius: 0.42 + index * 0.24,
|
|
218
|
+
length: 0.82 + index * 0.2,
|
|
219
|
+
width: 0.34 + index * 0.08,
|
|
220
|
+
pitch_degrees: Math.max(12, 70 - index * 18)
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
name: ring.name,
|
|
224
|
+
count: ring.count,
|
|
225
|
+
radius: base.radius,
|
|
226
|
+
length: base.length,
|
|
227
|
+
width: base.width,
|
|
228
|
+
cup: ring.cup,
|
|
229
|
+
gravity_sag: ring.gravity_sag,
|
|
230
|
+
pitch_degrees: base.pitch_degrees
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
trait: "botanical_lotus",
|
|
235
|
+
anchor_status: anchorStatus,
|
|
236
|
+
wallet_signed: anchorStatus === "wallet_signed",
|
|
237
|
+
petal_count: petalRings.reduce((sum, ring) => sum + ring.count, 0),
|
|
238
|
+
petal_rings: petalRings,
|
|
239
|
+
pbr_uniforms: {
|
|
240
|
+
subsurface_scattering: config.material.subsurface_scattering,
|
|
241
|
+
subsurface_radius_rgb: config.material.subsurface_radius_rgb,
|
|
242
|
+
transmission: config.material.petal_translucency_base,
|
|
243
|
+
thickness: config.material.petal_translucency_edge,
|
|
244
|
+
roughness: config.material.roughness,
|
|
245
|
+
ior: config.material.ior,
|
|
246
|
+
vein_normal_intensity: config.material.vein_normal_intensity,
|
|
247
|
+
sheen: config.material.sheen,
|
|
248
|
+
sheen_roughness: config.material.sheen_roughness,
|
|
249
|
+
sheen_color: config.material.sheen_color
|
|
250
|
+
},
|
|
251
|
+
colors: config.colors,
|
|
252
|
+
stamen_filament_count: config.geometry.stamen_filament_count,
|
|
253
|
+
seed_pod_dot_pattern: config.geometry.seed_pod_dot_pattern,
|
|
254
|
+
reference_anchor_ids: config.reference_anchors.map((anchor) => anchor.id),
|
|
255
|
+
renderer_requires: config.renderer.requires,
|
|
256
|
+
surface_anchor_id: placement?.surface_anchor_id,
|
|
257
|
+
lighting_reference: lighting?.reference_id
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function deriveLotusBloomFromGlow(intensity, pulse) {
|
|
261
|
+
if (intensity >= 1 && pulse) return "full";
|
|
262
|
+
if (intensity >= 0.7) return "blooming";
|
|
263
|
+
if (intensity >= 0.3) return "budding";
|
|
264
|
+
return "sealed";
|
|
265
|
+
}
|
|
266
|
+
function lotusPetalRenderColor(ring, isRoot, colors) {
|
|
267
|
+
if (isRoot) return colors.petal_base;
|
|
268
|
+
if (ring === 1) return colors.petal_inner;
|
|
269
|
+
if (ring === 2) return colors.petal_mid;
|
|
270
|
+
return LOTUS_OUTER_PETAL_COLOR;
|
|
271
|
+
}
|
|
272
|
+
function lotusObjectTraitConfig(obj, name) {
|
|
273
|
+
return obj.traits?.find((t) => t.name === name)?.config;
|
|
274
|
+
}
|
|
275
|
+
function buildLotusSceneFromComposition(objects, profile = createBotanicalLotusRenderProfile(), options = {}) {
|
|
276
|
+
const ringProfile = /* @__PURE__ */ new Map();
|
|
277
|
+
profile.petal_rings.forEach((r, i) => ringProfile.set(i + 1, r));
|
|
278
|
+
const lastRing = profile.petal_rings[profile.petal_rings.length - 1];
|
|
279
|
+
const goldenAngle = LOTUS_GOLDEN_ANGLE_DEG * Math.PI / 180;
|
|
280
|
+
const petalObjs = objects.filter((o) => LOTUS_PETAL_NAME_RE.test(String(o.name ?? "")));
|
|
281
|
+
const ringCounters = { 1: 0, 2: 0, 3: 0 };
|
|
282
|
+
const seedNum = Number(options.seed ?? LOTUS_GENESIS_SEED_PLACEHOLDER) >>> 0;
|
|
283
|
+
const PHYLLOTAXIS_WARMUP = 60;
|
|
284
|
+
const phyllotaxis = simulateLotusPhyllotaxis({
|
|
285
|
+
count: petalObjs.length + PHYLLOTAXIS_WARMUP,
|
|
286
|
+
seed: seedNum
|
|
287
|
+
});
|
|
288
|
+
const grownAngles = phyllotaxis.primordia.slice(PHYLLOTAXIS_WARMUP).map((p) => p.theta);
|
|
289
|
+
const petals = petalObjs.map((obj, index) => {
|
|
290
|
+
const match = LOTUS_PETAL_NAME_RE.exec(String(obj.name));
|
|
291
|
+
const ring = Number(match?.[1] ?? 3);
|
|
292
|
+
const ringIndex = ringCounters[ring]++;
|
|
293
|
+
const glow = lotusObjectTraitConfig(obj, "glowing") ?? {};
|
|
294
|
+
const intensity = typeof glow.intensity === "number" ? glow.intensity : 0.1;
|
|
295
|
+
const pulse = glow.pulse === true;
|
|
296
|
+
const isRoot = index === 0;
|
|
297
|
+
const bloom = isRoot ? "full" : deriveLotusBloomFromGlow(intensity, pulse);
|
|
298
|
+
const base = ringProfile.get(ring) ?? lastRing;
|
|
299
|
+
const scale = LOTUS_RING_SCALING[ring];
|
|
300
|
+
return {
|
|
301
|
+
index,
|
|
302
|
+
ring,
|
|
303
|
+
ringIndex,
|
|
304
|
+
angle: grownAngles[index] ?? index * goldenAngle,
|
|
305
|
+
radius: base.radius * scale.radius,
|
|
306
|
+
length: base.length * scale.length,
|
|
307
|
+
width: base.width * scale.width,
|
|
308
|
+
cup: base.cup,
|
|
309
|
+
gravitySag: base.gravity_sag,
|
|
310
|
+
height: scale.height,
|
|
311
|
+
color: lotusPetalRenderColor(ring, isRoot, profile.colors),
|
|
312
|
+
bloom,
|
|
313
|
+
label: `P${ring}.${ringIndex}`,
|
|
314
|
+
title: String(obj.name ?? `Petal P${ring}.${ringIndex}`)
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
const rings = [1, 2, 3].map((ring) => {
|
|
318
|
+
const base = ringProfile.get(ring) ?? lastRing;
|
|
319
|
+
const scale = LOTUS_RING_SCALING[ring];
|
|
320
|
+
return {
|
|
321
|
+
ring,
|
|
322
|
+
count: base.count,
|
|
323
|
+
radius: base.radius * scale.radius,
|
|
324
|
+
length: base.length * scale.length,
|
|
325
|
+
width: base.width * scale.width,
|
|
326
|
+
cup: base.cup,
|
|
327
|
+
gravity_sag: base.gravity_sag,
|
|
328
|
+
height: scale.height
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
return {
|
|
332
|
+
seed: options.seed ?? LOTUS_GENESIS_SEED_PLACEHOLDER,
|
|
333
|
+
golden_angle_deg: LOTUS_GOLDEN_ANGLE_DEG,
|
|
334
|
+
growth_seconds: LOTUS_GROWTH_SECONDS,
|
|
335
|
+
rings,
|
|
336
|
+
petals,
|
|
337
|
+
material: profile.pbr_uniforms,
|
|
338
|
+
colors: profile.colors,
|
|
339
|
+
stamen_filament_count: profile.stamen_filament_count,
|
|
340
|
+
seed_pod_dot_pattern: profile.seed_pod_dot_pattern
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function hexToRgb01(hex) {
|
|
344
|
+
const h = hex.replace("#", "");
|
|
345
|
+
const r = parseInt(h.slice(0, 2), 16) / 255;
|
|
346
|
+
const g = parseInt(h.slice(2, 4), 16) / 255;
|
|
347
|
+
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
348
|
+
return [r, g, b];
|
|
349
|
+
}
|
|
350
|
+
function buildLotusPetalMaterialSpec(profile, options = {}) {
|
|
351
|
+
const pbr = profile.pbr_uniforms;
|
|
352
|
+
const colors = profile.colors;
|
|
353
|
+
const dyn = { ...LOTUS_PETAL_DEFAULT_DYNAMIC, ...options.dynamic };
|
|
354
|
+
const size = options.detailMapSize ?? 256;
|
|
355
|
+
const veinSeed = options.veinSeed ?? 57005;
|
|
356
|
+
const roughnessSeed = options.roughnessSeed ?? 45063;
|
|
357
|
+
const physical = {
|
|
358
|
+
color: colors.petal_base,
|
|
359
|
+
roughness: pbr.roughness,
|
|
360
|
+
metalness: 0,
|
|
361
|
+
transparent: true,
|
|
362
|
+
transmission: pbr.transmission,
|
|
363
|
+
thickness: pbr.thickness,
|
|
364
|
+
ior: pbr.ior,
|
|
365
|
+
sheen: pbr.sheen,
|
|
366
|
+
sheenColor: pbr.sheen_color,
|
|
367
|
+
sheenRoughness: pbr.sheen_roughness
|
|
368
|
+
};
|
|
369
|
+
const uniforms = {
|
|
370
|
+
// Static color uniforms (from the declared palette) — vec3 in 0..1.
|
|
371
|
+
uLotusBaseColor: { value: hexToRgb01(colors.petal_base) },
|
|
372
|
+
uLotusMidColor: { value: hexToRgb01(colors.petal_mid) },
|
|
373
|
+
uLotusRimColor: { value: hexToRgb01(colors.petal_rim) },
|
|
374
|
+
uLotusShadowColor: { value: hexToRgb01(colors.petal_shadow) },
|
|
375
|
+
// Subsurface scatter tint comes from the declared radius RGB (already 0..1).
|
|
376
|
+
uLotusSubsurfaceColor: { value: [...pbr.subsurface_radius_rgb] },
|
|
377
|
+
// Static scalar uniforms from the PBR profile.
|
|
378
|
+
uLotusSSS: { value: pbr.subsurface_scattering },
|
|
379
|
+
uLotusTransmissionBase: { value: pbr.transmission },
|
|
380
|
+
uLotusTransmissionEdge: { value: pbr.thickness },
|
|
381
|
+
uLotusVeinIntensity: { value: pbr.vein_normal_intensity },
|
|
382
|
+
// Dynamic uniforms (driven per-frame via LOTUS_PETAL_UNIFORM_BINDINGS).
|
|
383
|
+
uPetalDevTime: { value: dyn.devTime },
|
|
384
|
+
uPetalCurlScale: { value: dyn.curlScale },
|
|
385
|
+
uLotusGrowth: { value: dyn.growth },
|
|
386
|
+
uLotusBloom: { value: dyn.bloom },
|
|
387
|
+
uLotusTime: { value: dyn.time }
|
|
388
|
+
};
|
|
389
|
+
return {
|
|
390
|
+
physical,
|
|
391
|
+
shaderChunks: {
|
|
392
|
+
chunks: [...LOTUS_PETAL_CHUNK_ENTRIES],
|
|
393
|
+
uniforms
|
|
394
|
+
},
|
|
395
|
+
proceduralMaps: {
|
|
396
|
+
normalMap: {
|
|
397
|
+
generator: "botanical_normal",
|
|
398
|
+
params: {
|
|
399
|
+
pattern: "petal_veins",
|
|
400
|
+
size,
|
|
401
|
+
seed: veinSeed,
|
|
402
|
+
strength: Math.max(0.5, pbr.vein_normal_intensity * 32)
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
roughnessMap: {
|
|
406
|
+
generator: "botanical_roughness",
|
|
407
|
+
params: { size, seed: roughnessSeed, base: pbr.roughness, variance: 0.3 }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function botMix(a, b, t) {
|
|
413
|
+
return a + (b - a) * t;
|
|
414
|
+
}
|
|
415
|
+
function botHash(ix, iy, seed) {
|
|
416
|
+
let h = Math.imul(ix, 374761393) + Math.imul(iy, 668265263) + Math.imul(seed, 1013904223) >>> 0;
|
|
417
|
+
h = Math.imul(h ^ h >>> 13, 1274126177) >>> 0;
|
|
418
|
+
return ((h ^ h >>> 16) >>> 0) / 4294967296;
|
|
419
|
+
}
|
|
420
|
+
function botValueNoise(x, y, seed) {
|
|
421
|
+
const ix = Math.floor(x);
|
|
422
|
+
const iy = Math.floor(y);
|
|
423
|
+
const fx = x - ix;
|
|
424
|
+
const fy = y - iy;
|
|
425
|
+
const ux = fx * fx * (3 - 2 * fx);
|
|
426
|
+
const uy = fy * fy * (3 - 2 * fy);
|
|
427
|
+
const a = botHash(ix, iy, seed);
|
|
428
|
+
const b = botHash(ix + 1, iy, seed);
|
|
429
|
+
const c = botHash(ix, iy + 1, seed);
|
|
430
|
+
const d = botHash(ix + 1, iy + 1, seed);
|
|
431
|
+
return botMix(botMix(a, b, ux), botMix(c, d, ux), uy);
|
|
432
|
+
}
|
|
433
|
+
function botFbm(x, y, seed, octaves = 4) {
|
|
434
|
+
let amp = 0.5;
|
|
435
|
+
let freq = 1;
|
|
436
|
+
let sum = 0;
|
|
437
|
+
let norm = 0;
|
|
438
|
+
for (let i = 0; i < octaves; i += 1) {
|
|
439
|
+
sum += amp * botValueNoise(x * freq, y * freq, seed + i * 101);
|
|
440
|
+
norm += amp;
|
|
441
|
+
amp *= 0.5;
|
|
442
|
+
freq *= 2;
|
|
443
|
+
}
|
|
444
|
+
return sum / norm;
|
|
445
|
+
}
|
|
446
|
+
function botanicalSurfaceHeight(pattern, u, v, seed) {
|
|
447
|
+
const micro = botFbm(u * 26, v * 26, seed, 4);
|
|
448
|
+
if (pattern === "petal_veins") {
|
|
449
|
+
const sx = u * 2 - 1;
|
|
450
|
+
const major = Math.pow(1 - Math.abs(Math.sin((sx * 9 + v * 3.2) * Math.PI)), 8);
|
|
451
|
+
const secondary = Math.pow(1 - Math.abs(Math.sin((sx * 19 - v * 2) * Math.PI)), 16) * 0.5;
|
|
452
|
+
const ridge = Math.max(0, 1 - Math.abs(sx) * 3) * (0.4 + v * 0.3);
|
|
453
|
+
return micro * 0.28 + major * 0.46 + secondary * 0.2 + ridge * 0.4;
|
|
454
|
+
}
|
|
455
|
+
if (pattern === "leaf_radial") {
|
|
456
|
+
const cx = u * 2 - 1;
|
|
457
|
+
const cy = v * 2 - 1;
|
|
458
|
+
const ang = Math.atan2(cy, cx);
|
|
459
|
+
const rad = Math.min(1, Math.sqrt(cx * cx + cy * cy));
|
|
460
|
+
const veins = Math.pow(Math.abs(Math.sin(ang * 13)), 6) * (1 - rad);
|
|
461
|
+
const rings = Math.pow(Math.abs(Math.sin(rad * 22)), 4) * 0.3;
|
|
462
|
+
return micro * 0.34 + veins * 0.56 + rings;
|
|
463
|
+
}
|
|
464
|
+
if (pattern === "stalk_fiber") {
|
|
465
|
+
const fibers = Math.pow(Math.abs(Math.sin(u * Math.PI * 40)), 3) * 0.5 + Math.abs(Math.sin(u * Math.PI * 83)) * 0.18;
|
|
466
|
+
const grain = botFbm(u * 12, v * 64, seed, 3);
|
|
467
|
+
return micro * 0.2 + fibers * 0.5 + grain * 0.3;
|
|
468
|
+
}
|
|
469
|
+
return micro;
|
|
470
|
+
}
|
|
471
|
+
function generateBotanicalNormalMap(opts) {
|
|
472
|
+
const size = opts.size ?? 256;
|
|
473
|
+
const seed = (opts.seed ?? 57005) >>> 0;
|
|
474
|
+
const strength = opts.strength ?? 1.6;
|
|
475
|
+
const data = new Uint8Array(size * size * 4);
|
|
476
|
+
const inv = 1 / size;
|
|
477
|
+
for (let y = 0; y < size; y += 1) {
|
|
478
|
+
for (let x = 0; x < size; x += 1) {
|
|
479
|
+
const u = x * inv;
|
|
480
|
+
const v = y * inv;
|
|
481
|
+
const hL = botanicalSurfaceHeight(opts.pattern, (x - 1 + size) % size * inv, v, seed);
|
|
482
|
+
const hR = botanicalSurfaceHeight(opts.pattern, (x + 1) % size * inv, v, seed);
|
|
483
|
+
const hD = botanicalSurfaceHeight(opts.pattern, u, (y - 1 + size) % size * inv, seed);
|
|
484
|
+
const hU = botanicalSurfaceHeight(opts.pattern, u, (y + 1) % size * inv, seed);
|
|
485
|
+
let nx = (hL - hR) * strength;
|
|
486
|
+
let ny = (hD - hU) * strength;
|
|
487
|
+
const nz = 1;
|
|
488
|
+
const len = Math.hypot(nx, ny, nz) || 1;
|
|
489
|
+
nx /= len;
|
|
490
|
+
ny /= len;
|
|
491
|
+
const i = (y * size + x) * 4;
|
|
492
|
+
data[i] = Math.round((nx * 0.5 + 0.5) * 255);
|
|
493
|
+
data[i + 1] = Math.round((ny * 0.5 + 0.5) * 255);
|
|
494
|
+
data[i + 2] = Math.round((nz / len * 0.5 + 0.5) * 255);
|
|
495
|
+
data[i + 3] = 255;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return { width: size, height: size, data };
|
|
499
|
+
}
|
|
500
|
+
function generateBotanicalRoughnessMap(opts) {
|
|
501
|
+
const size = opts.size ?? 256;
|
|
502
|
+
const seed = (opts.seed ?? 45063) >>> 0;
|
|
503
|
+
const base = opts.base ?? 0.6;
|
|
504
|
+
const variance = opts.variance ?? 0.3;
|
|
505
|
+
const scale = opts.scale ?? 10;
|
|
506
|
+
const data = new Uint8Array(size * size * 4);
|
|
507
|
+
const inv = 1 / size;
|
|
508
|
+
for (let y = 0; y < size; y += 1) {
|
|
509
|
+
for (let x = 0; x < size; x += 1) {
|
|
510
|
+
const r = Math.min(
|
|
511
|
+
1,
|
|
512
|
+
Math.max(0, base + (botFbm(x * inv * scale, y * inv * scale, seed, 4) - 0.5) * 2 * variance)
|
|
513
|
+
);
|
|
514
|
+
const g = Math.round(r * 255);
|
|
515
|
+
const i = (y * size + x) * 4;
|
|
516
|
+
data[i] = g;
|
|
517
|
+
data[i + 1] = g;
|
|
518
|
+
data[i + 2] = g;
|
|
519
|
+
data[i + 3] = 255;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return { width: size, height: size, data };
|
|
523
|
+
}
|
|
524
|
+
function lotusMulberry32(seed) {
|
|
525
|
+
let a = seed >>> 0;
|
|
526
|
+
return () => {
|
|
527
|
+
a = a + 1831565813 >>> 0;
|
|
528
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
529
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
530
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function simulateLotusMorphogenesis(params) {
|
|
534
|
+
const count = Math.max(1, Math.floor(params.count));
|
|
535
|
+
const seed = (params.seed ?? 57005) >>> 0;
|
|
536
|
+
const apexRadius = params.apexRadius ?? 1;
|
|
537
|
+
const radialVelocity = params.radialVelocity ?? 0.08;
|
|
538
|
+
const s = params.inhibitionExponent ?? 3;
|
|
539
|
+
const samples = params.angularSamples ?? 1440;
|
|
540
|
+
const rand = lotusMulberry32(seed);
|
|
541
|
+
const primordia = [];
|
|
542
|
+
const TWO_PI = Math.PI * 2;
|
|
543
|
+
for (let n = 0; n < count; n += 1) {
|
|
544
|
+
for (const p of primordia) p.r += radialVelocity;
|
|
545
|
+
let chosenTheta;
|
|
546
|
+
if (primordia.length === 0) {
|
|
547
|
+
chosenTheta = rand() * TWO_PI;
|
|
548
|
+
} else {
|
|
549
|
+
let bestInhibition = Infinity;
|
|
550
|
+
let bestTheta = 0;
|
|
551
|
+
for (let i = 0; i < samples; i += 1) {
|
|
552
|
+
const theta = i / samples * TWO_PI;
|
|
553
|
+
const x = apexRadius * Math.cos(theta);
|
|
554
|
+
const y = apexRadius * Math.sin(theta);
|
|
555
|
+
let inhibition = 0;
|
|
556
|
+
for (const p of primordia) {
|
|
557
|
+
const px = p.r * Math.cos(p.theta);
|
|
558
|
+
const py = p.r * Math.sin(p.theta);
|
|
559
|
+
const d = Math.hypot(x - px, y - py);
|
|
560
|
+
inhibition += 1 / Math.pow(Math.max(d, 1e-4), s);
|
|
561
|
+
}
|
|
562
|
+
if (inhibition < bestInhibition) {
|
|
563
|
+
bestInhibition = inhibition;
|
|
564
|
+
bestTheta = theta;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
chosenTheta = bestTheta;
|
|
568
|
+
}
|
|
569
|
+
primordia.push({ index: n, r: apexRadius, theta: chosenTheta });
|
|
570
|
+
}
|
|
571
|
+
const transient = Math.min(5, Math.max(0, primordia.length - 2));
|
|
572
|
+
let sumCos = 0;
|
|
573
|
+
let sumSin = 0;
|
|
574
|
+
let nDelta = 0;
|
|
575
|
+
for (let i = transient + 1; i < primordia.length; i += 1) {
|
|
576
|
+
let delta = primordia[i].theta - primordia[i - 1].theta;
|
|
577
|
+
delta = (delta % TWO_PI + TWO_PI) % TWO_PI;
|
|
578
|
+
if (delta > Math.PI) delta = TWO_PI - delta;
|
|
579
|
+
sumCos += Math.cos(delta);
|
|
580
|
+
sumSin += Math.sin(delta);
|
|
581
|
+
nDelta += 1;
|
|
582
|
+
}
|
|
583
|
+
const meanDelta = nDelta > 0 ? Math.atan2(sumSin / nDelta, sumCos / nDelta) : 0;
|
|
584
|
+
const meanDeg = (meanDelta * 180 / Math.PI + 360) % 360;
|
|
585
|
+
const resultant = nDelta > 0 ? Math.hypot(sumCos / nDelta, sumSin / nDelta) : 1;
|
|
586
|
+
const spreadDeg = Math.sqrt(Math.max(0, -2 * Math.log(Math.min(1, resultant)))) * 180 / Math.PI;
|
|
587
|
+
return {
|
|
588
|
+
primordia,
|
|
589
|
+
emergentDivergenceDeg: meanDeg,
|
|
590
|
+
divergenceSpreadDeg: spreadDeg,
|
|
591
|
+
resolvedParams: { count, seed, apexRadius, radialVelocity, inhibitionExponent: s, angularSamples: samples }
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function lotusInhibitionAt(theta, primordia, range, apexRadius) {
|
|
595
|
+
const x = apexRadius * Math.cos(theta);
|
|
596
|
+
const y = apexRadius * Math.sin(theta);
|
|
597
|
+
let sum = 0;
|
|
598
|
+
for (const p of primordia) {
|
|
599
|
+
const d = Math.hypot(x - p.r * Math.cos(p.theta), y - p.r * Math.sin(p.theta));
|
|
600
|
+
sum += Math.exp(-d / range);
|
|
601
|
+
}
|
|
602
|
+
return sum;
|
|
603
|
+
}
|
|
604
|
+
function createLotusMeristem(params) {
|
|
605
|
+
const seed = (params.seed ?? 57005) >>> 0;
|
|
606
|
+
const apexRadius = params.apexRadius ?? 1;
|
|
607
|
+
const v = params.radialVelocity ?? 0.025;
|
|
608
|
+
const range = params.inhibitorRange ?? 0.5;
|
|
609
|
+
const threshold = params.threshold ?? 1;
|
|
610
|
+
const [wMinDeg, wMaxDeg] = params.chiralWindowDeg ?? [80, 230];
|
|
611
|
+
const samples = params.angularSamples ?? 720;
|
|
612
|
+
const DEG = Math.PI / 180;
|
|
613
|
+
const wMin = wMinDeg * DEG;
|
|
614
|
+
const wMax = wMaxDeg * DEG;
|
|
615
|
+
const rand = lotusMulberry32(seed);
|
|
616
|
+
const last = rand() * Math.PI * 2;
|
|
617
|
+
return {
|
|
618
|
+
primordia: [{ index: 0, r: apexRadius, theta: last }],
|
|
619
|
+
last,
|
|
620
|
+
apexRadius,
|
|
621
|
+
v,
|
|
622
|
+
range,
|
|
623
|
+
threshold,
|
|
624
|
+
wMin,
|
|
625
|
+
wMax,
|
|
626
|
+
samples,
|
|
627
|
+
step: (wMax - wMin) / samples
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function stepLotusMeristem(m) {
|
|
631
|
+
const TWO_PI = Math.PI * 2;
|
|
632
|
+
for (const p of m.primordia) p.r += m.v;
|
|
633
|
+
let bestK = 0;
|
|
634
|
+
let bestI = Infinity;
|
|
635
|
+
for (let k = 0; k <= m.samples; k += 1) {
|
|
636
|
+
const I = lotusInhibitionAt(m.last + m.wMin + m.step * k, m.primordia, m.range, m.apexRadius);
|
|
637
|
+
if (I < bestI) {
|
|
638
|
+
bestI = I;
|
|
639
|
+
bestK = k;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
let off = bestK;
|
|
643
|
+
if (bestK > 0 && bestK < m.samples) {
|
|
644
|
+
const y0 = lotusInhibitionAt(m.last + m.wMin + m.step * (bestK - 1), m.primordia, m.range, m.apexRadius);
|
|
645
|
+
const y2 = lotusInhibitionAt(m.last + m.wMin + m.step * (bestK + 1), m.primordia, m.range, m.apexRadius);
|
|
646
|
+
const den = y0 - 2 * bestI + y2;
|
|
647
|
+
if (Math.abs(den) > 1e-12) off = bestK + 0.5 * (y0 - y2) / den;
|
|
648
|
+
}
|
|
649
|
+
if (bestI < m.threshold) {
|
|
650
|
+
const theta = m.last + m.wMin + m.step * off;
|
|
651
|
+
m.last = (theta % TWO_PI + TWO_PI) % TWO_PI;
|
|
652
|
+
m.primordia.push({ index: m.primordia.length, r: m.apexRadius, theta: m.last });
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
function simulateLotusPhyllotaxis(params) {
|
|
658
|
+
const count = Math.max(1, Math.floor(params.count));
|
|
659
|
+
const TWO_PI = Math.PI * 2;
|
|
660
|
+
const meristem = createLotusMeristem(params);
|
|
661
|
+
let guard = 0;
|
|
662
|
+
const guardMax = count * 2e5;
|
|
663
|
+
while (meristem.primordia.length < count && guard++ < guardMax) stepLotusMeristem(meristem);
|
|
664
|
+
const primordia = meristem.primordia;
|
|
665
|
+
const tailStart = Math.max(1, primordia.length - 30);
|
|
666
|
+
let sumCos = 0;
|
|
667
|
+
let sumSin = 0;
|
|
668
|
+
let nDelta = 0;
|
|
669
|
+
for (let i = tailStart; i < primordia.length; i += 1) {
|
|
670
|
+
let delta = primordia[i].theta - primordia[i - 1].theta;
|
|
671
|
+
delta = (delta % TWO_PI + TWO_PI) % TWO_PI;
|
|
672
|
+
if (delta > Math.PI) delta = TWO_PI - delta;
|
|
673
|
+
sumCos += Math.cos(delta);
|
|
674
|
+
sumSin += Math.sin(delta);
|
|
675
|
+
nDelta += 1;
|
|
676
|
+
}
|
|
677
|
+
const meanDelta = nDelta > 0 ? Math.atan2(sumSin / nDelta, sumCos / nDelta) : 0;
|
|
678
|
+
const emergentDivergenceDeg = (meanDelta * 180 / Math.PI + 360) % 360;
|
|
679
|
+
const resultant = nDelta > 0 ? Math.hypot(sumCos / nDelta, sumSin / nDelta) : 1;
|
|
680
|
+
const divergenceSpreadDeg = Math.sqrt(Math.max(0, -2 * Math.log(Math.min(1, resultant)))) * 180 / Math.PI;
|
|
681
|
+
return { primordia, emergentDivergenceDeg, divergenceSpreadDeg };
|
|
682
|
+
}
|
|
683
|
+
function lotusSmoothstep01(edge0, edge1, x) {
|
|
684
|
+
const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
|
|
685
|
+
return t * t * (3 - 2 * t);
|
|
686
|
+
}
|
|
687
|
+
function simulateLotusPetalGrowth(params) {
|
|
688
|
+
const t = Math.max(0, Math.min(1, params.developmentalTime));
|
|
689
|
+
const budCurl = params.budCurl ?? 2.8;
|
|
690
|
+
const openCurl = params.openCurl ?? -0.25;
|
|
691
|
+
const matureStart = params.matureStart ?? 0.15;
|
|
692
|
+
const matureSpan = params.matureSpan ?? 0.5;
|
|
693
|
+
const acropetalDelay = params.acropetalDelay ?? 0.35;
|
|
694
|
+
const baseAngle = (params.baseAngleDeg ?? 90) * Math.PI / 180;
|
|
695
|
+
const N = Math.max(4, Math.floor(params.segments ?? 24));
|
|
696
|
+
let phi = baseAngle;
|
|
697
|
+
let x = 0;
|
|
698
|
+
let y = 0;
|
|
699
|
+
const centerline = [[0, 0]];
|
|
700
|
+
const curvature = [];
|
|
701
|
+
const ds = 1 / N;
|
|
702
|
+
for (let i = 0; i < N; i += 1) {
|
|
703
|
+
const s = (i + 0.5) / N;
|
|
704
|
+
const maturity = lotusSmoothstep01(0, 1, (t - matureStart - s * acropetalDelay) / matureSpan);
|
|
705
|
+
const kappa = budCurl * (1 - maturity) + openCurl * maturity;
|
|
706
|
+
curvature.push(kappa);
|
|
707
|
+
phi += kappa * ds;
|
|
708
|
+
x += Math.cos(phi) * ds;
|
|
709
|
+
y += Math.sin(phi) * ds;
|
|
710
|
+
centerline.push([x, y]);
|
|
711
|
+
}
|
|
712
|
+
const baseMaturity = lotusSmoothstep01(0, 1, (t - matureStart) / matureSpan);
|
|
713
|
+
const tipMaturity = lotusSmoothstep01(0, 1, (t - matureStart - acropetalDelay) / matureSpan);
|
|
714
|
+
return {
|
|
715
|
+
tipAngleDeg: phi * 180 / Math.PI,
|
|
716
|
+
centerline,
|
|
717
|
+
curvature,
|
|
718
|
+
baseMaturity,
|
|
719
|
+
tipMaturity
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
function createLotusPond(params = {}) {
|
|
723
|
+
const size = Math.max(8, Math.floor(params.size ?? 96));
|
|
724
|
+
const extent = params.extent ?? 4;
|
|
725
|
+
const c = params.waveSpeed ?? 2.2;
|
|
726
|
+
const damping = params.damping ?? 0.6;
|
|
727
|
+
const stalkRadius = params.stalkRadius ?? 0.09;
|
|
728
|
+
const capillaryLength = params.capillaryLength ?? 0.22;
|
|
729
|
+
const contactRise = params.contactRise ?? 0.05;
|
|
730
|
+
const dx = 2 * extent / size;
|
|
731
|
+
const n = size * size;
|
|
732
|
+
const h = new Float32Array(n);
|
|
733
|
+
const v = new Float32Array(n);
|
|
734
|
+
const meniscus = new Float32Array(n);
|
|
735
|
+
const mask = new Float32Array(n);
|
|
736
|
+
for (let j = 0; j < size; j += 1) {
|
|
737
|
+
for (let i = 0; i < size; i += 1) {
|
|
738
|
+
const wx = -extent + (i + 0.5) * dx;
|
|
739
|
+
const wz = -extent + (j + 0.5) * dx;
|
|
740
|
+
const r = Math.hypot(wx, wz);
|
|
741
|
+
const idx = j * size + i;
|
|
742
|
+
if (r <= stalkRadius) {
|
|
743
|
+
mask[idx] = 0;
|
|
744
|
+
meniscus[idx] = contactRise;
|
|
745
|
+
} else {
|
|
746
|
+
mask[idx] = 1;
|
|
747
|
+
meniscus[idx] = contactRise * Math.exp(-(r - stalkRadius) / capillaryLength);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return { size, extent, c, damping, dx, h, v, meniscus, mask };
|
|
752
|
+
}
|
|
753
|
+
function disturbLotusPond(state, worldX, worldZ, amplitude, radius = 0.15) {
|
|
754
|
+
const { size, extent, dx } = state;
|
|
755
|
+
const ci = (worldX + extent) / dx - 0.5;
|
|
756
|
+
const cj = (worldZ + extent) / dx - 0.5;
|
|
757
|
+
const rad = Math.max(1, radius / dx);
|
|
758
|
+
const i0 = Math.max(0, Math.floor(ci - rad * 3));
|
|
759
|
+
const i1 = Math.min(size - 1, Math.ceil(ci + rad * 3));
|
|
760
|
+
const j0 = Math.max(0, Math.floor(cj - rad * 3));
|
|
761
|
+
const j1 = Math.min(size - 1, Math.ceil(cj + rad * 3));
|
|
762
|
+
for (let j = j0; j <= j1; j += 1) {
|
|
763
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
764
|
+
const d2 = (i - ci) * (i - ci) + (j - cj) * (j - cj);
|
|
765
|
+
const idx = j * size + i;
|
|
766
|
+
state.v[idx] += amplitude * Math.exp(-d2 / (2 * rad * rad)) * state.mask[idx];
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function stepLotusPond(state, dt) {
|
|
771
|
+
const { size, c, damping, dx, h, v, mask } = state;
|
|
772
|
+
const cflDt = 0.6 * dx / Math.max(c, 1e-6);
|
|
773
|
+
const sub = Math.max(1, Math.min(8, Math.ceil(dt / cflDt)));
|
|
774
|
+
const subDt = dt / sub;
|
|
775
|
+
const c2 = c * c;
|
|
776
|
+
for (let step = 0; step < sub; step += 1) {
|
|
777
|
+
for (let j = 1; j < size - 1; j += 1) {
|
|
778
|
+
for (let i = 1; i < size - 1; i += 1) {
|
|
779
|
+
const idx = j * size + i;
|
|
780
|
+
if (mask[idx] === 0) {
|
|
781
|
+
v[idx] = 0;
|
|
782
|
+
h[idx] = 0;
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const lap = (h[idx - 1] + h[idx + 1] + h[idx - size] + h[idx + size] - 4 * h[idx]) / (dx * dx);
|
|
786
|
+
v[idx] += (c2 * lap - damping * v[idx]) * subDt;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
for (let k = 0; k < h.length; k += 1) h[k] += v[k] * subDt;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function lotusPondSurface(state, out) {
|
|
793
|
+
const n = state.h.length;
|
|
794
|
+
const surface = out && out.length === n ? out : new Float32Array(n);
|
|
795
|
+
for (let k = 0; k < n; k += 1) surface[k] = state.h[k] * state.mask[k] + state.meniscus[k];
|
|
796
|
+
return surface;
|
|
797
|
+
}
|
|
798
|
+
function createLotusMorphogen(params = {}) {
|
|
799
|
+
const size = Math.max(8, Math.floor(params.size ?? 96));
|
|
800
|
+
const d = params.diffusionRatio ?? 40;
|
|
801
|
+
const gamma = params.gamma ?? 800;
|
|
802
|
+
const a = params.a ?? 0.1;
|
|
803
|
+
const b = params.b ?? 0.9;
|
|
804
|
+
const seed = (params.seed ?? 57005) >>> 0;
|
|
805
|
+
const dx = 1 / size;
|
|
806
|
+
const dt = 0.4 * (dx * dx) / Math.max(1, d);
|
|
807
|
+
const us = a + b;
|
|
808
|
+
const vs = b / (us * us);
|
|
809
|
+
const rand = lotusMulberry32(seed);
|
|
810
|
+
const u = new Float32Array(size);
|
|
811
|
+
const v = new Float32Array(size);
|
|
812
|
+
for (let i = 0; i < size; i += 1) {
|
|
813
|
+
u[i] = us + 0.05 * (rand() - 0.5);
|
|
814
|
+
v[i] = vs + 0.05 * (rand() - 0.5);
|
|
815
|
+
}
|
|
816
|
+
return { size, d, gamma, a, b, dt, u, v };
|
|
817
|
+
}
|
|
818
|
+
function stepLotusMorphogen(field, iterations = 1) {
|
|
819
|
+
const { size, d, gamma, a, b, dt, u, v } = field;
|
|
820
|
+
const dx2 = 1 / size * (1 / size);
|
|
821
|
+
const lu = new Float32Array(size);
|
|
822
|
+
const lv = new Float32Array(size);
|
|
823
|
+
for (let it = 0; it < iterations; it += 1) {
|
|
824
|
+
for (let i = 0; i < size; i += 1) {
|
|
825
|
+
const ip = (i + 1) % size;
|
|
826
|
+
const im = (i - 1 + size) % size;
|
|
827
|
+
lu[i] = (u[im] - 2 * u[i] + u[ip]) / dx2;
|
|
828
|
+
lv[i] = (v[im] - 2 * v[i] + v[ip]) / dx2;
|
|
829
|
+
}
|
|
830
|
+
for (let i = 0; i < size; i += 1) {
|
|
831
|
+
const uv = u[i] * u[i] * v[i];
|
|
832
|
+
u[i] += dt * (lu[i] + gamma * (a - u[i] + uv));
|
|
833
|
+
v[i] += dt * (d * lv[i] + gamma * (b - uv));
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function lotusMorphogenPeaks(field) {
|
|
838
|
+
const { size, u } = field;
|
|
839
|
+
let mn = Infinity;
|
|
840
|
+
let mx = -Infinity;
|
|
841
|
+
for (let i = 0; i < size; i += 1) {
|
|
842
|
+
if (u[i] < mn) mn = u[i];
|
|
843
|
+
if (u[i] > mx) mx = u[i];
|
|
844
|
+
}
|
|
845
|
+
const thr = (mn + mx) / 2;
|
|
846
|
+
let peaks = 0;
|
|
847
|
+
for (let i = 0; i < size; i += 1) {
|
|
848
|
+
const ip = (i + 1) % size;
|
|
849
|
+
const im = (i - 1 + size) % size;
|
|
850
|
+
if (u[i] > thr && u[i] >= u[ip] && u[i] > u[im]) peaks += 1;
|
|
851
|
+
}
|
|
852
|
+
return peaks;
|
|
853
|
+
}
|
|
854
|
+
function createLotusMorphogen2D(params = {}) {
|
|
855
|
+
const size = Math.max(8, Math.floor(params.size ?? 64));
|
|
856
|
+
const d = params.diffusionRatio ?? 40;
|
|
857
|
+
const gamma = params.gamma ?? 1600;
|
|
858
|
+
const a = params.a ?? 0.1;
|
|
859
|
+
const b = params.b ?? 0.9;
|
|
860
|
+
const seed = (params.seed ?? 57005) >>> 0;
|
|
861
|
+
const dx = 1 / size;
|
|
862
|
+
const dt = 0.2 * (dx * dx) / Math.max(1, d);
|
|
863
|
+
const us = a + b;
|
|
864
|
+
const vs = b / (us * us);
|
|
865
|
+
const rand = lotusMulberry32(seed);
|
|
866
|
+
const n = size * size;
|
|
867
|
+
const u = new Float32Array(n);
|
|
868
|
+
const v = new Float32Array(n);
|
|
869
|
+
const mask = new Uint8Array(n);
|
|
870
|
+
const c = (size - 1) / 2;
|
|
871
|
+
for (let j = 0; j < size; j += 1) {
|
|
872
|
+
for (let i = 0; i < size; i += 1) {
|
|
873
|
+
const k = j * size + i;
|
|
874
|
+
const nx = (i - c) / c;
|
|
875
|
+
const ny = (j - c) / c;
|
|
876
|
+
const inside = nx * nx + ny * ny <= 1 ? 1 : 0;
|
|
877
|
+
mask[k] = inside;
|
|
878
|
+
u[k] = us + (inside ? 0.05 * (rand() - 0.5) : 0);
|
|
879
|
+
v[k] = vs;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return { size, d, gamma, a, b, dt, u, v, mask };
|
|
883
|
+
}
|
|
884
|
+
function stepLotusMorphogen2D(field, iterations = 1) {
|
|
885
|
+
const { size, d, gamma, a, b, dt, u, v, mask } = field;
|
|
886
|
+
const dx2 = 1 / size * (1 / size);
|
|
887
|
+
const n = size * size;
|
|
888
|
+
const lu = new Float32Array(n);
|
|
889
|
+
const lv = new Float32Array(n);
|
|
890
|
+
for (let it = 0; it < iterations; it += 1) {
|
|
891
|
+
for (let j = 0; j < size; j += 1) {
|
|
892
|
+
for (let i = 0; i < size; i += 1) {
|
|
893
|
+
const k = j * size + i;
|
|
894
|
+
if (!mask[k]) continue;
|
|
895
|
+
const left = i > 0 && mask[k - 1] ? u[k - 1] : u[k];
|
|
896
|
+
const right = i < size - 1 && mask[k + 1] ? u[k + 1] : u[k];
|
|
897
|
+
const up = j > 0 && mask[k - size] ? u[k - size] : u[k];
|
|
898
|
+
const down = j < size - 1 && mask[k + size] ? u[k + size] : u[k];
|
|
899
|
+
const leftV = i > 0 && mask[k - 1] ? v[k - 1] : v[k];
|
|
900
|
+
const rightV = i < size - 1 && mask[k + 1] ? v[k + 1] : v[k];
|
|
901
|
+
const upV = j > 0 && mask[k - size] ? v[k - size] : v[k];
|
|
902
|
+
const downV = j < size - 1 && mask[k + size] ? v[k + size] : v[k];
|
|
903
|
+
lu[k] = (left + right + up + down - 4 * u[k]) / dx2;
|
|
904
|
+
lv[k] = (leftV + rightV + upV + downV - 4 * v[k]) / dx2;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
for (let k = 0; k < n; k += 1) {
|
|
908
|
+
if (!mask[k]) continue;
|
|
909
|
+
const uv = u[k] * u[k] * v[k];
|
|
910
|
+
u[k] += dt * (lu[k] + gamma * (a - u[k] + uv));
|
|
911
|
+
v[k] += dt * (d * lv[k] + gamma * (b - uv));
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function lotusMorphogen2DPeaks(field) {
|
|
916
|
+
const { size, u, mask } = field;
|
|
917
|
+
let mn = Infinity;
|
|
918
|
+
let mx = -Infinity;
|
|
919
|
+
for (let k = 0; k < u.length; k += 1) {
|
|
920
|
+
if (!mask[k]) continue;
|
|
921
|
+
if (u[k] < mn) mn = u[k];
|
|
922
|
+
if (u[k] > mx) mx = u[k];
|
|
923
|
+
}
|
|
924
|
+
const range = mx - mn || 1;
|
|
925
|
+
const thr = (mn + mx) / 2;
|
|
926
|
+
const c = (size - 1) / 2;
|
|
927
|
+
const peaks = [];
|
|
928
|
+
for (let j = 0; j < size; j += 1) {
|
|
929
|
+
for (let i = 0; i < size; i += 1) {
|
|
930
|
+
const k = j * size + i;
|
|
931
|
+
if (!mask[k] || u[k] <= thr) continue;
|
|
932
|
+
let isMax = true;
|
|
933
|
+
for (let dj = -1; dj <= 1 && isMax; dj += 1) {
|
|
934
|
+
for (let di = -1; di <= 1; di += 1) {
|
|
935
|
+
if (di === 0 && dj === 0) continue;
|
|
936
|
+
const ni = i + di;
|
|
937
|
+
const nj = j + dj;
|
|
938
|
+
if (ni < 0 || ni >= size || nj < 0 || nj >= size) continue;
|
|
939
|
+
const nk = nj * size + ni;
|
|
940
|
+
if (mask[nk] && u[nk] > u[k]) {
|
|
941
|
+
isMax = false;
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (!isMax) continue;
|
|
947
|
+
const x = (i - c) / c;
|
|
948
|
+
const y = (j - c) / c;
|
|
949
|
+
peaks.push({
|
|
950
|
+
x,
|
|
951
|
+
y,
|
|
952
|
+
radius: Math.min(1, Math.hypot(x, y)),
|
|
953
|
+
angle: Math.atan2(y, x),
|
|
954
|
+
value: (u[k] - mn) / range
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return peaks;
|
|
959
|
+
}
|
|
960
|
+
function lotusMorphogen2DSampleAt(field, x, y) {
|
|
961
|
+
const { size, u, mask } = field;
|
|
962
|
+
const c = (size - 1) / 2;
|
|
963
|
+
const i = Math.round(x * c + c);
|
|
964
|
+
const j = Math.round(y * c + c);
|
|
965
|
+
if (i < 0 || i >= size || j < 0 || j >= size) return 0;
|
|
966
|
+
const k = j * size + i;
|
|
967
|
+
if (!mask[k]) return 0;
|
|
968
|
+
let mn = Infinity;
|
|
969
|
+
let mx = -Infinity;
|
|
970
|
+
for (let m = 0; m < u.length; m += 1) {
|
|
971
|
+
if (!mask[m]) continue;
|
|
972
|
+
if (u[m] < mn) mn = u[m];
|
|
973
|
+
if (u[m] > mx) mx = u[m];
|
|
974
|
+
}
|
|
975
|
+
return (u[k] - mn) / (mx - mn || 1);
|
|
976
|
+
}
|
|
977
|
+
function createLotusPetalTurgor(params = {}) {
|
|
978
|
+
const segments = Math.max(4, Math.floor(params.segments ?? 24));
|
|
979
|
+
const budCurl = params.budCurl ?? 2.8;
|
|
980
|
+
const kappaActual = new Float32Array(segments).fill(budCurl);
|
|
981
|
+
return {
|
|
982
|
+
segments,
|
|
983
|
+
baseAngle: (params.baseAngleDeg ?? 90) * Math.PI / 180,
|
|
984
|
+
budCurl,
|
|
985
|
+
openCurl: params.openCurl ?? -0.25,
|
|
986
|
+
matureStart: params.matureStart ?? 0.15,
|
|
987
|
+
matureSpan: params.matureSpan ?? 0.5,
|
|
988
|
+
acropetalDelay: params.acropetalDelay ?? 0.35,
|
|
989
|
+
turgorStiffness: params.turgorStiffness ?? 6,
|
|
990
|
+
turgorRise: params.turgorRise ?? 3,
|
|
991
|
+
turgor: 0,
|
|
992
|
+
kappaActual
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function stepLotusPetalTurgor(state, dt, developmentalTime) {
|
|
996
|
+
const t = Math.max(0, Math.min(1, developmentalTime));
|
|
997
|
+
const baseMaturity = lotusSmoothstep01(0, 1, (t - state.matureStart) / state.matureSpan);
|
|
998
|
+
state.turgor += state.turgorRise * (baseMaturity - state.turgor) * dt;
|
|
999
|
+
state.turgor = Math.max(0, Math.min(1, state.turgor));
|
|
1000
|
+
const rate = state.turgorStiffness * state.turgor * dt;
|
|
1001
|
+
for (let i = 0; i < state.segments; i += 1) {
|
|
1002
|
+
const s = (i + 0.5) / state.segments;
|
|
1003
|
+
const maturity = lotusSmoothstep01(0, 1, (t - state.matureStart - s * state.acropetalDelay) / state.matureSpan);
|
|
1004
|
+
const kappaRest = state.budCurl * (1 - maturity) + state.openCurl * maturity;
|
|
1005
|
+
state.kappaActual[i] += rate * (kappaRest - state.kappaActual[i]);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function lotusPetalTurgorTipDeg(state) {
|
|
1009
|
+
let phi = state.baseAngle;
|
|
1010
|
+
const ds = 1 / state.segments;
|
|
1011
|
+
for (let i = 0; i < state.segments; i += 1) phi += state.kappaActual[i] * ds;
|
|
1012
|
+
return phi * 180 / Math.PI;
|
|
1013
|
+
}
|
|
1014
|
+
function lotusPetalTurgorOpenProgress(state) {
|
|
1015
|
+
const tip = lotusPetalTurgorTipDeg(state);
|
|
1016
|
+
return Math.max(0, Math.min(1, (250 - tip) / (250 - 76)));
|
|
1017
|
+
}
|
|
1018
|
+
var DEFAULT_BOTANICAL_LOTUS_CONFIG, SHA256_HASH_PATTERN, HEX_COLOR_PATTERN, RING_RENDER_DEFAULTS, botanicalLotusHandler, LOTUS_GOLDEN_ANGLE_DEG, LOTUS_GROWTH_SECONDS, LOTUS_GENESIS_SEED_PLACEHOLDER, LOTUS_RING_SCALING, LOTUS_OUTER_PETAL_COLOR, LOTUS_PETAL_NAME_RE, LOTUS_PETAL_SHADER_CHUNKS, LOTUS_PETAL_CHUNK_ENTRIES, LOTUS_PETAL_DEFAULT_DYNAMIC, LOTUS_PETAL_UNIFORM_BINDINGS;
|
|
1019
|
+
var init_BotanicalLotusTrait = __esm({
|
|
1020
|
+
"src/traits/BotanicalLotusTrait.ts"() {
|
|
1021
|
+
DEFAULT_BOTANICAL_LOTUS_CONFIG = {
|
|
1022
|
+
schema: "holoscript.trait.botanical_lotus.v0",
|
|
1023
|
+
status: "visual_seed",
|
|
1024
|
+
source: {
|
|
1025
|
+
kind: "conversation_reference_images",
|
|
1026
|
+
count: 3,
|
|
1027
|
+
content_hash_status: "pending_media_ingest",
|
|
1028
|
+
wallet_signature_status: "pending_cael_anchor",
|
|
1029
|
+
note: "Derived from three pink lotus reference images provided in-thread; raw media ingest is pending."
|
|
1030
|
+
},
|
|
1031
|
+
reference_anchors: [
|
|
1032
|
+
{
|
|
1033
|
+
id: "lotus-reference-2026-05-06-01",
|
|
1034
|
+
label: "open pink lotus close-up",
|
|
1035
|
+
uri: "conversation://2026-05-06/lotus-reference-01",
|
|
1036
|
+
role: "material",
|
|
1037
|
+
status: "pending_media_ingest"
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
id: "lotus-reference-2026-05-06-02",
|
|
1041
|
+
label: "upright pink lotus silhouette",
|
|
1042
|
+
uri: "conversation://2026-05-06/lotus-reference-02",
|
|
1043
|
+
role: "silhouette",
|
|
1044
|
+
status: "pending_media_ingest"
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
id: "lotus-reference-2026-05-06-03",
|
|
1048
|
+
label: "pink lotus with leaves and water context",
|
|
1049
|
+
uri: "conversation://2026-05-06/lotus-reference-03",
|
|
1050
|
+
role: "leaf_context",
|
|
1051
|
+
status: "pending_media_ingest"
|
|
1052
|
+
}
|
|
1053
|
+
],
|
|
1054
|
+
material: {
|
|
1055
|
+
subsurface_scattering: 0.74,
|
|
1056
|
+
subsurface_radius_rgb: [0.9, 0.32, 0.72],
|
|
1057
|
+
petal_translucency_base: 0.68,
|
|
1058
|
+
petal_translucency_edge: 0.36,
|
|
1059
|
+
roughness: 0.72,
|
|
1060
|
+
ior: 1.36,
|
|
1061
|
+
vein_normal_intensity: 0.045,
|
|
1062
|
+
edge_curl_intensity: 0.58,
|
|
1063
|
+
gravity_sag_outer: 0.3,
|
|
1064
|
+
sheen: 0.5,
|
|
1065
|
+
sheen_roughness: 0.42,
|
|
1066
|
+
sheen_color: "#ffe8f2"
|
|
1067
|
+
},
|
|
1068
|
+
colors: {
|
|
1069
|
+
petal_base: "#fff1f6",
|
|
1070
|
+
petal_mid: "#f47ab7",
|
|
1071
|
+
petal_inner: "#ff9ecf",
|
|
1072
|
+
petal_rim: "#c42a86",
|
|
1073
|
+
petal_shadow: "#84205f",
|
|
1074
|
+
seed_pod: "#f4d74a",
|
|
1075
|
+
seed_pod_rim: "#b7c66b",
|
|
1076
|
+
stamen: "#f59e0b",
|
|
1077
|
+
stamen_tip: "#fff4bd",
|
|
1078
|
+
leaf: "#235f4f",
|
|
1079
|
+
leaf_dark: "#102f28",
|
|
1080
|
+
water: "#07140f"
|
|
1081
|
+
},
|
|
1082
|
+
geometry: {
|
|
1083
|
+
petal_rings: [
|
|
1084
|
+
{ name: "inner", count: 8, cup: 0.86, gravity_sag: 0.02 },
|
|
1085
|
+
{ name: "mid", count: 13, cup: 0.5, gravity_sag: 0.12 },
|
|
1086
|
+
{ name: "outer", count: 21, cup: 0.24, gravity_sag: 0.3 }
|
|
1087
|
+
],
|
|
1088
|
+
petal_shape: "broad_elliptic_pointed_tip_with_center_ridge",
|
|
1089
|
+
stamen_filament_count: 58,
|
|
1090
|
+
seed_pod_dot_pattern: "1_center_7_12_18_radial"
|
|
1091
|
+
},
|
|
1092
|
+
renderer: {
|
|
1093
|
+
requires: ["sss_render", "instanced_filaments", "water_surface"],
|
|
1094
|
+
lod: {
|
|
1095
|
+
close: "full_filaments",
|
|
1096
|
+
mid: "simplified_filaments",
|
|
1097
|
+
far: "petal_billboard"
|
|
1098
|
+
},
|
|
1099
|
+
material_model: "thin_tissue_mesh_physical_material"
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
SHA256_HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
1103
|
+
HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
1104
|
+
RING_RENDER_DEFAULTS = {
|
|
1105
|
+
inner: { radius: 0.32, length: 0.82, width: 0.34, pitch_degrees: 74 },
|
|
1106
|
+
mid: { radius: 0.58, length: 1.15, width: 0.46, pitch_degrees: 42 },
|
|
1107
|
+
outer: { radius: 0.92, length: 1.52, width: 0.58, pitch_degrees: 18 }
|
|
1108
|
+
};
|
|
1109
|
+
botanicalLotusHandler = {
|
|
1110
|
+
name: "botanical_lotus",
|
|
1111
|
+
defaultConfig: DEFAULT_BOTANICAL_LOTUS_CONFIG,
|
|
1112
|
+
validate: validateBotanicalLotusConfig,
|
|
1113
|
+
toRenderProfile: createBotanicalLotusRenderProfile,
|
|
1114
|
+
onAttach(node, config, context) {
|
|
1115
|
+
const normalized = assertBotanicalLotusConfig(config);
|
|
1116
|
+
const profile = createBotanicalLotusRenderProfile(normalized);
|
|
1117
|
+
const state = { config: normalized, profile };
|
|
1118
|
+
node.__botanicalLotusState = state;
|
|
1119
|
+
const nodeId = String(node.id ?? "unknown");
|
|
1120
|
+
context.setState?.({
|
|
1121
|
+
[`botanical_lotus.${nodeId}.anchor_status`]: profile.anchor_status,
|
|
1122
|
+
[`botanical_lotus.${nodeId}.petal_count`]: profile.petal_count
|
|
1123
|
+
});
|
|
1124
|
+
context.emit?.("botanical_lotus_attached", {
|
|
1125
|
+
node,
|
|
1126
|
+
anchorStatus: profile.anchor_status,
|
|
1127
|
+
walletSigned: profile.wallet_signed,
|
|
1128
|
+
petalCount: profile.petal_count,
|
|
1129
|
+
referenceAnchorIds: profile.reference_anchor_ids,
|
|
1130
|
+
rendererRequires: profile.renderer_requires
|
|
1131
|
+
});
|
|
1132
|
+
},
|
|
1133
|
+
onDetach(node, _config, context) {
|
|
1134
|
+
context.emit?.("botanical_lotus_detached", { node });
|
|
1135
|
+
delete node.__botanicalLotusState;
|
|
1136
|
+
},
|
|
1137
|
+
onUpdate(_node, _config, _context, _delta) {
|
|
1138
|
+
},
|
|
1139
|
+
onEvent(node, _config, context, event) {
|
|
1140
|
+
const state = node.__botanicalLotusState;
|
|
1141
|
+
if (!state) return;
|
|
1142
|
+
if (event.type === "botanical_lotus_query") {
|
|
1143
|
+
context.emit?.("botanical_lotus_response", {
|
|
1144
|
+
queryId: event.queryId,
|
|
1145
|
+
node,
|
|
1146
|
+
config: state.config,
|
|
1147
|
+
profile: state.profile
|
|
1148
|
+
});
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (event.type === "botanical_lotus_reference_anchored") {
|
|
1152
|
+
const anchorId = String(event.anchorId ?? "");
|
|
1153
|
+
const contentHash = typeof event.contentHash === "string" ? event.contentHash : void 0;
|
|
1154
|
+
const walletSignature = typeof event.walletSignature === "string" ? event.walletSignature : void 0;
|
|
1155
|
+
if (!anchorId || !contentHash) return;
|
|
1156
|
+
const updatedAnchors = state.config.reference_anchors.map((anchor) => {
|
|
1157
|
+
if (anchor.id !== anchorId) return anchor;
|
|
1158
|
+
return {
|
|
1159
|
+
...anchor,
|
|
1160
|
+
content_hash: contentHash,
|
|
1161
|
+
wallet_signature: walletSignature,
|
|
1162
|
+
status: walletSignature ? "wallet_signed" : "hashed"
|
|
1163
|
+
};
|
|
1164
|
+
});
|
|
1165
|
+
const updatedConfig = assertBotanicalLotusConfig({
|
|
1166
|
+
...state.config,
|
|
1167
|
+
status: updatedAnchors.every((anchor) => anchor.status === "wallet_signed") ? "wallet_signed" : "content_hashed",
|
|
1168
|
+
source: {
|
|
1169
|
+
...state.config.source,
|
|
1170
|
+
content_hash_status: deriveBotanicalLotusAnchorStatus(updatedAnchors),
|
|
1171
|
+
wallet_signature_status: updatedAnchors.every(
|
|
1172
|
+
(anchor) => anchor.status === "wallet_signed"
|
|
1173
|
+
) ? "wallet_signed" : "pending_cael_anchor"
|
|
1174
|
+
},
|
|
1175
|
+
reference_anchors: updatedAnchors
|
|
1176
|
+
});
|
|
1177
|
+
state.config = updatedConfig;
|
|
1178
|
+
state.profile = createBotanicalLotusRenderProfile(updatedConfig, state.placement, state.lighting);
|
|
1179
|
+
context.emit?.("botanical_lotus_reference_updated", {
|
|
1180
|
+
node,
|
|
1181
|
+
anchorId,
|
|
1182
|
+
anchorStatus: state.profile.anchor_status,
|
|
1183
|
+
walletSigned: state.profile.wallet_signed,
|
|
1184
|
+
referenceAnchorIds: state.profile.reference_anchor_ids
|
|
1185
|
+
});
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (event.type === "holomap:surface_anchor_placed") {
|
|
1189
|
+
const payload = event.payload ?? {};
|
|
1190
|
+
const surfaceAnchorId = typeof payload.surfaceAnchorId === "string" ? payload.surfaceAnchorId : void 0;
|
|
1191
|
+
const surfaceNormal = Array.isArray(payload.surfaceNormal) ? payload.surfaceNormal : void 0;
|
|
1192
|
+
const worldPosition = Array.isArray(payload.worldPosition) ? payload.worldPosition : void 0;
|
|
1193
|
+
if (!surfaceAnchorId) return;
|
|
1194
|
+
state.placement = {
|
|
1195
|
+
surface_anchor_id: surfaceAnchorId,
|
|
1196
|
+
surface_normal: surfaceNormal,
|
|
1197
|
+
world_position: worldPosition
|
|
1198
|
+
};
|
|
1199
|
+
state.profile = createBotanicalLotusRenderProfile(state.config, state.placement, state.lighting);
|
|
1200
|
+
context.emit?.("botanical_lotus_surface_bound", {
|
|
1201
|
+
node,
|
|
1202
|
+
surfaceAnchorId,
|
|
1203
|
+
surfaceNormal,
|
|
1204
|
+
worldPosition
|
|
1205
|
+
});
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
if (event.type === "holomap:lighting_update") {
|
|
1209
|
+
const payload = event.payload ?? {};
|
|
1210
|
+
const referenceId = typeof payload.referenceId === "string" ? payload.referenceId : void 0;
|
|
1211
|
+
const estimatedLux = typeof payload.estimatedLux === "number" && Number.isFinite(payload.estimatedLux) ? payload.estimatedLux : void 0;
|
|
1212
|
+
const colorTemperatureK = typeof payload.colorTemperatureK === "number" && Number.isFinite(payload.colorTemperatureK) ? payload.colorTemperatureK : void 0;
|
|
1213
|
+
const dominantDirection = Array.isArray(payload.dominantDirection) ? payload.dominantDirection : void 0;
|
|
1214
|
+
if (!referenceId) return;
|
|
1215
|
+
state.lighting = {
|
|
1216
|
+
reference_id: referenceId,
|
|
1217
|
+
estimated_lux: estimatedLux,
|
|
1218
|
+
color_temperature_k: colorTemperatureK,
|
|
1219
|
+
dominant_direction: dominantDirection
|
|
1220
|
+
};
|
|
1221
|
+
state.profile = createBotanicalLotusRenderProfile(state.config, state.placement, state.lighting);
|
|
1222
|
+
context.emit?.("botanical_lotus_lighting_updated", {
|
|
1223
|
+
node,
|
|
1224
|
+
referenceId,
|
|
1225
|
+
estimatedLux,
|
|
1226
|
+
colorTemperatureK,
|
|
1227
|
+
dominantDirection
|
|
1228
|
+
});
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
if (event.type === "holomap:anchor_state_changed") {
|
|
1232
|
+
const payload = event.payload ?? {};
|
|
1233
|
+
const anchorFrameIndex = typeof payload.anchorFrameIndex === "number" ? payload.anchorFrameIndex : void 0;
|
|
1234
|
+
if (state.placement?.surface_anchor_id && anchorFrameIndex !== void 0) {
|
|
1235
|
+
context.emit?.("botanical_lotus_anchor_drift", {
|
|
1236
|
+
node,
|
|
1237
|
+
surfaceAnchorId: state.placement.surface_anchor_id,
|
|
1238
|
+
anchorFrameIndex
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
LOTUS_GOLDEN_ANGLE_DEG = 137.50776;
|
|
1246
|
+
LOTUS_GROWTH_SECONDS = 12.5;
|
|
1247
|
+
LOTUS_GENESIS_SEED_PLACEHOLDER = "0x0000DEAD";
|
|
1248
|
+
LOTUS_RING_SCALING = {
|
|
1249
|
+
1: { radius: 1.25, length: 0.98, width: 1.65, height: 1.12 },
|
|
1250
|
+
2: { radius: 1.25, length: 0.94, width: 1.48, height: 0.98 },
|
|
1251
|
+
3: { radius: 1.25, length: 0.82, width: 1.35, height: 0.78 }
|
|
1252
|
+
};
|
|
1253
|
+
LOTUS_OUTER_PETAL_COLOR = "#d94b9a";
|
|
1254
|
+
LOTUS_PETAL_NAME_RE = /^Petal\s+P([123])\.(\d+)/;
|
|
1255
|
+
LOTUS_PETAL_SHADER_CHUNKS = {
|
|
1256
|
+
/** Spliced after `#include <common>` in the vertex shader. */
|
|
1257
|
+
vertexHeader: `
|
|
1258
|
+
attribute vec2 petalUv;
|
|
1259
|
+
attribute float veinPhase;
|
|
1260
|
+
varying vec2 vLotusPetalUv;
|
|
1261
|
+
varying float vLotusVeinPhase;
|
|
1262
|
+
varying vec3 vLotusWorldNormal;
|
|
1263
|
+
varying vec3 vLotusViewDir;
|
|
1264
|
+
uniform float uPetalDevTime;
|
|
1265
|
+
uniform float uPetalCurlScale;
|
|
1266
|
+
`,
|
|
1267
|
+
/**
|
|
1268
|
+
* Spliced after \`#include <begin_vertex>\` — EMERGENT petal curl (L2). Bends the
|
|
1269
|
+
* length axis by the integral of the maturation-front growth curvature (mirrors
|
|
1270
|
+
* simulateLotusPetalGrowth), RELATIVE to the fully-open state so the bend is zero at
|
|
1271
|
+
* full bloom (the approved open pose is preserved exactly) and the bud-wrap appears
|
|
1272
|
+
* only while opening. Curvature reverses inward(bud) -> outward(open) behind the
|
|
1273
|
+
* acropetal maturation front.
|
|
1274
|
+
*/
|
|
1275
|
+
vertexBend: `
|
|
1276
|
+
{
|
|
1277
|
+
float lotusVlen = clamp(petalUv.y, 0.0, 1.0);
|
|
1278
|
+
float lotusDs = lotusVlen / 12.0;
|
|
1279
|
+
float lotusPhiN = 0.0, lotusPhiO = 0.0;
|
|
1280
|
+
vec2 lotusCN = vec2(0.0), lotusCO = vec2(0.0);
|
|
1281
|
+
for (int k = 0; k < 12; k++) {
|
|
1282
|
+
float s = (float(k) + 0.5) * lotusDs;
|
|
1283
|
+
float matN = smoothstep(0.0, 1.0, (uPetalDevTime - 0.15 - s * 0.35) / 0.5);
|
|
1284
|
+
float matO = smoothstep(0.0, 1.0, (1.0 - 0.15 - s * 0.35) / 0.5);
|
|
1285
|
+
lotusPhiN += mix(2.8, -0.25, matN) * lotusDs;
|
|
1286
|
+
lotusPhiO += mix(2.8, -0.25, matO) * lotusDs;
|
|
1287
|
+
lotusCN += vec2(cos(lotusPhiN), sin(lotusPhiN)) * lotusDs;
|
|
1288
|
+
lotusCO += vec2(cos(lotusPhiO), sin(lotusPhiO)) * lotusDs;
|
|
1289
|
+
}
|
|
1290
|
+
vec2 lotusBend = (lotusCN - lotusCO) * uPetalCurlScale;
|
|
1291
|
+
transformed.x += lotusBend.x;
|
|
1292
|
+
transformed.y += lotusBend.y;
|
|
1293
|
+
}
|
|
1294
|
+
`,
|
|
1295
|
+
/** Spliced after \`#include <worldpos_vertex>\`. */
|
|
1296
|
+
vertexWorld: `
|
|
1297
|
+
vLotusPetalUv = petalUv;
|
|
1298
|
+
vLotusVeinPhase = veinPhase;
|
|
1299
|
+
vLotusWorldNormal = normalize(mat3(modelMatrix) * objectNormal);
|
|
1300
|
+
vLotusViewDir = normalize(cameraPosition - worldPosition.xyz);
|
|
1301
|
+
`,
|
|
1302
|
+
/** Spliced after `#include <common>` in the fragment shader. */
|
|
1303
|
+
fragmentHeader: `
|
|
1304
|
+
uniform vec3 uLotusBaseColor;
|
|
1305
|
+
uniform vec3 uLotusMidColor;
|
|
1306
|
+
uniform vec3 uLotusRimColor;
|
|
1307
|
+
uniform vec3 uLotusShadowColor;
|
|
1308
|
+
uniform vec3 uLotusSubsurfaceColor;
|
|
1309
|
+
uniform float uLotusSSS;
|
|
1310
|
+
uniform float uLotusTransmissionBase;
|
|
1311
|
+
uniform float uLotusTransmissionEdge;
|
|
1312
|
+
uniform float uLotusVeinIntensity;
|
|
1313
|
+
uniform float uLotusGrowth;
|
|
1314
|
+
uniform float uLotusBloom;
|
|
1315
|
+
uniform float uLotusTime;
|
|
1316
|
+
varying vec2 vLotusPetalUv;
|
|
1317
|
+
varying float vLotusVeinPhase;
|
|
1318
|
+
varying vec3 vLotusWorldNormal;
|
|
1319
|
+
varying vec3 vLotusViewDir;
|
|
1320
|
+
|
|
1321
|
+
float lotusVeinField(vec2 uv, float phase) {
|
|
1322
|
+
float signedX = uv.x * 2.0 - 1.0;
|
|
1323
|
+
float major = pow(1.0 - abs(sin((signedX * 18.0 + uv.y * 6.0 + phase) * 3.14159265)), 20.0);
|
|
1324
|
+
float secondary = pow(1.0 - abs(sin((signedX * 34.0 - uv.y * 4.0 - phase * 0.7) * 3.14159265)), 32.0);
|
|
1325
|
+
float taper = (1.0 - smoothstep(0.82, 1.0, uv.y)) * (1.0 - abs(signedX) * 0.34);
|
|
1326
|
+
return clamp((major * 0.62 + secondary * 0.38) * taper, 0.0, 1.0);
|
|
1327
|
+
}
|
|
1328
|
+
`,
|
|
1329
|
+
/** Spliced after `#include <normal_fragment_maps>` — vein normal perturbation. */
|
|
1330
|
+
fragmentNormalInjection: `
|
|
1331
|
+
float lotusNormalVein = lotusVeinField(vLotusPetalUv, vLotusVeinPhase);
|
|
1332
|
+
float lotusVeinSide = sign(vLotusPetalUv.x - 0.5);
|
|
1333
|
+
normal = normalize(normal + vec3(
|
|
1334
|
+
lotusNormalVein * lotusVeinSide * uLotusVeinIntensity * 1.7,
|
|
1335
|
+
lotusNormalVein * uLotusVeinIntensity * 0.6,
|
|
1336
|
+
0.0
|
|
1337
|
+
));`,
|
|
1338
|
+
/** Spliced after `#include <color_fragment>` — petal profile gradient + veins. */
|
|
1339
|
+
fragmentColorInjection: `
|
|
1340
|
+
float lotusEdge = abs(vLotusPetalUv.x * 2.0 - 1.0);
|
|
1341
|
+
float lotusTip = smoothstep(0.78, 1.0, vLotusPetalUv.y);
|
|
1342
|
+
vec3 lotusProfileColor = mix(uLotusBaseColor, uLotusMidColor, smoothstep(0.08, 0.72, vLotusPetalUv.y));
|
|
1343
|
+
lotusProfileColor = mix(lotusProfileColor, uLotusRimColor, clamp(lotusEdge * lotusEdge * 0.42 + lotusTip * 0.35, 0.0, 0.82));
|
|
1344
|
+
float lotusVeinColorField = lotusVeinField(vLotusPetalUv, vLotusVeinPhase);
|
|
1345
|
+
vec3 lotusVeinColor = mix(uLotusShadowColor, uLotusRimColor, 0.58);
|
|
1346
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, diffuseColor.rgb * lotusProfileColor, 0.62);
|
|
1347
|
+
diffuseColor.rgb += lotusVeinColor * lotusVeinColorField * uLotusVeinIntensity * uLotusGrowth * 7.5;
|
|
1348
|
+
diffuseColor.a *= mix(0.7, 1.0, uLotusGrowth);`,
|
|
1349
|
+
/** Spliced after `#include <emissivemap_fragment>` — backlit subsurface scatter. */
|
|
1350
|
+
fragmentEmissiveInjection: `
|
|
1351
|
+
float lotusBacklight = pow(1.0 - abs(dot(normalize(vLotusWorldNormal), normalize(vLotusViewDir))), 2.15);
|
|
1352
|
+
float lotusTranslucency = mix(uLotusTransmissionBase, uLotusTransmissionEdge, lotusEdge);
|
|
1353
|
+
float lotusPulse = 0.92 + sin(uLotusTime * 0.65 + vLotusVeinPhase * 6.28318) * 0.08;
|
|
1354
|
+
vec3 lotusScatter = uLotusSubsurfaceColor * lotusBacklight * lotusTranslucency * uLotusSSS * uLotusGrowth * lotusPulse;
|
|
1355
|
+
totalEmissiveRadiance += lotusScatter * (0.28 + uLotusBloom * 0.72);`
|
|
1356
|
+
};
|
|
1357
|
+
LOTUS_PETAL_CHUNK_ENTRIES = [
|
|
1358
|
+
{ stage: "vertex", include: "common", code: LOTUS_PETAL_SHADER_CHUNKS.vertexHeader },
|
|
1359
|
+
{ stage: "vertex", include: "begin_vertex", code: LOTUS_PETAL_SHADER_CHUNKS.vertexBend },
|
|
1360
|
+
{ stage: "vertex", include: "worldpos_vertex", code: LOTUS_PETAL_SHADER_CHUNKS.vertexWorld },
|
|
1361
|
+
{ stage: "fragment", include: "common", code: LOTUS_PETAL_SHADER_CHUNKS.fragmentHeader },
|
|
1362
|
+
{ stage: "fragment", include: "normal_fragment_maps", code: LOTUS_PETAL_SHADER_CHUNKS.fragmentNormalInjection },
|
|
1363
|
+
{ stage: "fragment", include: "color_fragment", code: LOTUS_PETAL_SHADER_CHUNKS.fragmentColorInjection },
|
|
1364
|
+
{ stage: "fragment", include: "emissivemap_fragment", code: LOTUS_PETAL_SHADER_CHUNKS.fragmentEmissiveInjection }
|
|
1365
|
+
];
|
|
1366
|
+
LOTUS_PETAL_DEFAULT_DYNAMIC = {
|
|
1367
|
+
devTime: 1,
|
|
1368
|
+
curlScale: 1,
|
|
1369
|
+
growth: 1,
|
|
1370
|
+
bloom: 1,
|
|
1371
|
+
time: 0
|
|
1372
|
+
};
|
|
1373
|
+
LOTUS_PETAL_UNIFORM_BINDINGS = {
|
|
1374
|
+
uPetalDevTime: "devTime",
|
|
1375
|
+
uLotusGrowth: "growth",
|
|
1376
|
+
uLotusBloom: "bloom",
|
|
1377
|
+
uLotusTime: "time"
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
export { DEFAULT_BOTANICAL_LOTUS_CONFIG, LOTUS_GENESIS_SEED_PLACEHOLDER, LOTUS_GOLDEN_ANGLE_DEG, LOTUS_GROWTH_SECONDS, LOTUS_OUTER_PETAL_COLOR, LOTUS_PETAL_CHUNK_ENTRIES, LOTUS_PETAL_SHADER_CHUNKS, LOTUS_PETAL_UNIFORM_BINDINGS, LOTUS_RING_SCALING, assertBotanicalLotusConfig, botanicalLotusHandler, buildLotusPetalMaterialSpec, buildLotusSceneFromComposition, createBotanicalLotusRenderProfile, createLotusMeristem, createLotusMorphogen, createLotusMorphogen2D, createLotusPetalTurgor, createLotusPond, deriveBotanicalLotusAnchorStatus, deriveLotusBloomFromGlow, disturbLotusPond, generateBotanicalNormalMap, generateBotanicalRoughnessMap, getBotanicalLotusPetalCount, init_BotanicalLotusTrait, lotusMorphogen2DPeaks, lotusMorphogen2DSampleAt, lotusMorphogenPeaks, lotusPetalRenderColor, lotusPetalTurgorOpenProgress, lotusPetalTurgorTipDeg, lotusPondSurface, normalizeBotanicalLotusConfig, simulateLotusMorphogenesis, simulateLotusPetalGrowth, simulateLotusPhyllotaxis, stepLotusMeristem, stepLotusMorphogen, stepLotusMorphogen2D, stepLotusPetalTurgor, stepLotusPond, validateBotanicalLotusConfig };
|
|
1383
|
+
//# sourceMappingURL=chunk-4GU5GCIY.js.map
|
|
1384
|
+
//# sourceMappingURL=chunk-4GU5GCIY.js.map
|