@3driseai/3drise-viewer 0.1.35 → 0.1.38

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.
@@ -1 +1 @@
1
- {"version":3,"file":"appliers.d.ts","sourceRoot":"","sources":["../../src/animationsV2/appliers.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAiB/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKxC,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAKrE;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,qDAAqD;IACrD,QAAQ,EAAE,GAAG,CAAC;IACd,yFAAyF;IACzF,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC;IACpC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;CACxD;AAWD,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC;AAsOD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMhE;AAED,uFAAuF;AACvF,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAIlH;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAKxF;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE"}
1
+ {"version":3,"file":"appliers.d.ts","sourceRoot":"","sources":["../../src/animationsV2/appliers.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAgB/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKxC,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAKrE;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,qDAAqD;IACrD,QAAQ,EAAE,GAAG,CAAC;IACd,yFAAyF;IACzF,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC;IACpC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;CACxD;AAWD,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC;AAkOD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMhE;AAED,uFAAuF;AACvF,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAIlH;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAKxF;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE"}
@@ -1,5 +1,5 @@
1
- import { findById, SceneStore, applySingleProperty, applyMaterialPropertyToObject, traverseSkippingEdgesAndEffects, EDGES_GROUP_KEY, } from '@3driseai/3drise-core';
2
- import { CONFIG_LIVE_KEY } from '../hooks/useConfigLive.js';
1
+ import { findById, applySingleProperty, applyMaterialPropertyToObject, traverseSkippingEdgesAndEffects, EDGES_GROUP_KEY, } from '@3driseai/3drise-core';
2
+ import { findConfigHandles } from '../hooks/useConfigLive.js';
3
3
  import { CLOUD_LIVE_KEY } from '../hooks/useCloudLive.js';
4
4
  import { OCEAN_LIVE_KEY } from '../hooks/useOceanLive.js';
5
5
  import { TERRAIN_LIVE_KEY } from '../hooks/useTerrainLive.js';
@@ -37,19 +37,13 @@ function objectFor(state) {
37
37
  objectCache.delete(state);
38
38
  return found;
39
39
  }
40
- function configHandleFor(state) {
40
+ /** Every live handle of the object — one per layout slot, so the whole layout follows the clip. */
41
+ function configHandlesFor(state) {
41
42
  const cached = handleCache.get(state);
42
- if (cached && cached.alive)
43
+ if (cached && cached.length && cached.every(h => h.alive))
43
44
  return cached;
44
- let found = null;
45
- SceneStore.scene?.traverse((o) => {
46
- if (found)
47
- return;
48
- const h = o.userData?.[CONFIG_LIVE_KEY];
49
- if (h && h.alive && h.id === state)
50
- found = h;
51
- });
52
- if (found)
45
+ const found = findConfigHandles(state);
46
+ if (found.length)
53
47
  handleCache.set(state, found);
54
48
  else
55
49
  handleCache.delete(state);
@@ -134,11 +128,12 @@ const applyEdgesMaterial = (_ctx, state, path, value) => {
134
128
  applyMaterialPropertyToObject(edges, path.slice('edgesSettings.materialSettings.'.length), value);
135
129
  };
136
130
  const applyConfig = (_ctx, state, path, value) => {
137
- const handle = configHandleFor(state);
138
- if (!handle)
131
+ const handles = configHandlesFor(state);
132
+ if (!handles.length)
139
133
  return false;
140
134
  if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') {
141
- handle.set(path.slice('config.'.length), value);
135
+ for (const h of handles)
136
+ h.set(path.slice('config.'.length), value);
142
137
  }
143
138
  };
144
139
  // --- environment: the same live paths the controllers' sliders use -------------------
@@ -109,8 +109,21 @@ export declare function findCueConflicts(seq: AnimationSequence, clips: ClipLook
109
109
  export declare function newCue(command: SceneCommand, stepId: string, at: number, mode?: SequenceCue['mode']): SequenceCue;
110
110
  /** A clip cue at `at` seconds. */
111
111
  export declare function newClipCue(command: SceneCommand, at: number, mode?: TimelineCue['mode']): TimelineCue;
112
- /** Clip-local time (seconds, 0..clip.duration) of an entry at sequence time T. */
112
+ /** Clip-local time (seconds, 0..clip.duration) of an entry at sequence time T — backwards for a reversed step clip. */
113
113
  export declare function entryLocalTime(entry: ScheduledClip, T: number): number;
114
+ /**
115
+ * A clip's cue schedule played backwards: fire cues at duration − t; each on / off lane's
116
+ * spans mirrored (on over [a, b] becomes on over [D − b, D − a] — its apply / revert swap).
117
+ */
118
+ export declare function mirrorCueSchedule(schedule: Array<{
119
+ cue: TimelineCue;
120
+ subject: string;
121
+ time: number;
122
+ }>, duration: number): Array<{
123
+ cue: TimelineCue;
124
+ subject: string;
125
+ time: number;
126
+ }>;
114
127
  /**
115
128
  * Whether an entry writes at sequence time T: from its start on, and after its end
116
129
  * only if its clip holds ('hold'); a 'restore' clip lets go at its end.
@@ -1 +1 @@
1
- {"version":3,"file":"sequenceUtils.d.ts","sourceRoot":"","sources":["../../src/animationsV2/sequenceUtils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sequenceUtils.d.ts","sourceRoot":"","sources":["../../src/animationsV2/sequenceUtils.ts"],"names":[],"mappings":"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"}
@@ -39,7 +39,8 @@ export function resolveSequence(seq, clips) {
39
39
  for (const e of entries) {
40
40
  const speed = safeSpeed(e.stepClip.speed);
41
41
  const stepStart = steps[e.stepIndex].start;
42
- for (const c of clipCueSchedule(e.clip)) {
42
+ const own = clipCueSchedule(e.clip);
43
+ for (const c of e.stepClip.reverse ? mirrorCueSchedule(own, e.clip.duration) : own) {
43
44
  const time = Math.min(e.end, e.start + c.time / speed);
44
45
  const cue = { ...c.cue, id: `${e.id}:${c.cue.id}`, stepId: seq.steps[e.stepIndex].id, at: time - stepStart };
45
46
  cues.push({ cue, subject: c.subject, stepIndex: e.stepIndex, time, fromClip: e.id });
@@ -175,10 +176,32 @@ export function newCue(command, stepId, at, mode = 'apply') {
175
176
  export function newClipCue(command, at, mode = 'apply') {
176
177
  return { id: uid('cue'), command, at: Math.max(0, at), mode };
177
178
  }
178
- /** Clip-local time (seconds, 0..clip.duration) of an entry at sequence time T. */
179
+ /** Clip-local time (seconds, 0..clip.duration) of an entry at sequence time T — backwards for a reversed step clip. */
179
180
  export function entryLocalTime(entry, T) {
180
- const local = (T - entry.start) * safeSpeed(entry.stepClip.speed);
181
- return Math.min(entry.clip.duration, Math.max(0, local));
181
+ const local = Math.min(entry.clip.duration, Math.max(0, (T - entry.start) * safeSpeed(entry.stepClip.speed)));
182
+ return entry.stepClip.reverse ? entry.clip.duration - local : local;
183
+ }
184
+ /**
185
+ * A clip's cue schedule played backwards: fire cues at duration − t; each on / off lane's
186
+ * spans mirrored (on over [a, b] becomes on over [D − b, D − a] — its apply / revert swap).
187
+ */
188
+ export function mirrorCueSchedule(schedule, duration) {
189
+ const out = [];
190
+ const lanes = new Map();
191
+ for (const c of schedule) {
192
+ if (c.cue.mode === 'fire')
193
+ out.push({ ...c, time: duration - c.time, cue: { ...c.cue, at: duration - c.time } });
194
+ else if (!lanes.has(c.subject))
195
+ lanes.set(c.subject, c.cue);
196
+ }
197
+ for (const [subject, first] of lanes) {
198
+ cueSpans({ cues: schedule.filter(c => !c.cue.muted), duration }, subject).forEach((s, i) => {
199
+ const on = duration - s.to, off = duration - s.from;
200
+ out.push({ subject, time: on, cue: { ...first, id: `${first.id}:r${i}on`, mode: 'apply', at: on } });
201
+ out.push({ subject, time: off, cue: { ...first, id: `${first.id}:r${i}off`, mode: 'revert', at: off } });
202
+ });
203
+ }
204
+ return out.sort((a, b) => a.time - b.time);
182
205
  }
183
206
  /**
184
207
  * Whether an entry writes at sequence time T: from its start on, and after its end
@@ -64,6 +64,8 @@ export interface StepClip {
64
64
  speed: number;
65
65
  /** Muted: keeps its place and length in the step, but drives nothing and fires none of its cues. */
66
66
  muted?: boolean;
67
+ /** Plays the clip backwards (its end pose first); its cues are mirrored too. Same place and length. */
68
+ reverse?: boolean;
67
69
  }
68
70
  /** Clips that start together. The step ends when its longest clip ends; then the next step starts. */
69
71
  export interface SequenceStep {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/animationsV2/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/animationsV2/types.ts"],"names":[],"mappings":"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"}
@@ -132,7 +132,7 @@ const ObjectNode = ({ node, onObjectReady, onObjectDispose, handleObjectClick })
132
132
  landscape: (obj, onObjectReady) => (_jsx(LandscapeObject, { settings: obj, ...relabel(obj) }, obj.id)),
133
133
  effect: (obj, _onObjectReady, slot = 0) => (_jsx(EffectsGenerator, { settings: {
134
134
  config: obj.config,
135
- }, id: slot === 0 ? obj.id : undefined, keyframes: obj.animations?.effect?.animations, mouseMove: obj.mouseMove?.effect, ...(slot === 0 ? relabel(obj) : NO_READY) }, obj.id)),
135
+ }, id: obj.id /* every layout slot: each copy registers its live handle, so clips and drags reach the whole layout */, keyframes: obj.animations?.effect?.animations, mouseMove: obj.mouseMove?.effect, ...(slot === 0 ? relabel(obj) : NO_READY) }, obj.id)),
136
136
  gallery: (obj, onObjectReady) => (_jsx(GalleryLayout, { createdObject: obj, handleObjectClick: handleObjectClick, ...relabel(obj) })),
137
137
  light: (obj, _onObjectReady, slot = 0) => (_jsx(LightObject, { createdObject: slot === 0 ? obj : withoutShadow(obj), ...(slot === 0 ? relabel(obj) : NO_READY) })),
138
138
  environment: (obj, onObjectReady) => (_jsx(EnvironmentObject, { createdObject: obj, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose })),
@@ -1 +1 @@
1
- {"version":3,"file":"HarmonicShellEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/HarmonicShellEffect.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGpG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,mFAAmF;IACnF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,yEAAyE;IACzE,SAAS,CAAC,EAAE,eAAe,CAAC;CAC/B;AAoMD,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA6C3D,CAAC;AACF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"HarmonicShellEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/HarmonicShellEffect.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGpG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAQhE,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,mFAAmF;IACnF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,yEAAyE;IACzE,SAAS,CAAC,EAAE,eAAe,CAAC;CAC/B;AAwCD,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA8I3D,CAAC;AACF,eAAe,mBAAmB,CAAC"}
@@ -6,166 +6,23 @@ import { withConfigDefaults } from '../../utils/configAnimations.js';
6
6
  import { DEFAULT_GENERATIVE_EFFECTS, EFFECTS_ANIMATABLE } from "@3driseai/3drise-core";
7
7
  import { useObjectReady } from '../../hooks/useObjectReady.js';
8
8
  import { useConfigDriver } from '../../hooks/useConfigDriver.js';
9
+ import { findById } from '../../utils/sceneQuery.js';
10
+ import { shellAttach, shellAttached, shellRelease } from '../../operations/catalogue/inShell.js';
11
+ import { isOperationOn } from '../../operations/runtime.js';
12
+ import { SHELL_FRAG, SHELL_VERT, bakeObjectSdf, bakeShapeSdf, createHarmonicState, createShellUniforms, registerShellField, unregisterShellField, updateHarmonicWeights, } from './harmonicShellField.js';
9
13
  /**
10
14
  * An iridescent shell around the host, displaced by real spherical harmonics
11
15
  * up to l = 4 whose weights drift slowly. The colour is thin-film
12
16
  * interference, as on a soap bubble, and the lines where the harmonic field
13
17
  * crosses zero glow.
18
+ *
19
+ * With a target (text, a shape, or another scene object) the shell becomes it:
20
+ * `morph` 0 → 1 traces every vertex of the sphere onto the target's distance
21
+ * field (see harmonicShellField.ts), the harmonics keep breathing on it, and
22
+ * the triangles stretched across the gaps between letters fade out.
14
23
  */
15
- /** Band (l) of each of the 24 real-SH terms in the shader. */
16
- const SH_BANDS = [1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4];
17
- function mulberry32(seed) {
18
- return () => {
19
- seed |= 0;
20
- seed = (seed + 0x6d2b79f5) | 0;
21
- let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
22
- r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
23
- return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
24
- };
25
- }
26
- /** Fixed per-term frequencies and phases, so the same config always moves the same way. */
27
- function createHarmonicState() {
28
- const rand = mulberry32(7);
29
- const f1 = new Float32Array(24), f2 = new Float32Array(24), p1 = new Float32Array(24), p2 = new Float32Array(24);
30
- for (let i = 0; i < 24; i++) {
31
- f1[i] = 0.13 + 0.3 * rand();
32
- f2[i] = 0.07 + 0.2 * rand();
33
- p1[i] = rand() * Math.PI * 2;
34
- p2[i] = rand() * Math.PI * 2;
35
- }
36
- // per-band gate, eased so changing `degree` blends bands in and out
37
- return { f1, f2, p1, p2, gate: new Float32Array([0, 0, 0, 0, 0]) };
38
- }
39
- /**
40
- * Writes the 24 weights for time T into `out`. Bands above `degree` fade
41
- * out; the top band leads and lower bands sit at about half strength.
42
- */
43
- function updateHarmonicWeights(state, out, T, degree, dt) {
44
- const deg = Math.round(Math.min(Math.max(degree, 1), 4));
45
- const ease = Math.min(1, dt * 2.5);
46
- let energy = 0;
47
- for (let l = 1; l <= 4; l++) {
48
- // l = 1 only shifts the shell sideways, so it stays quiet unless it is the only band
49
- const target = l > deg ? 0 : l === deg ? 1 : l === 1 ? 0.2 : 0.55;
50
- state.gate[l] += (target - state.gate[l]) * ease;
51
- energy += state.gate[l] * state.gate[l] * (2 * l + 1);
52
- }
53
- const norm = 1 / Math.sqrt(Math.max(energy / 5, 1e-3));
54
- for (let i = 0; i < 24; i++) {
55
- const g = state.gate[SH_BANDS[i]];
56
- out[i] = g * norm * (0.7 * Math.sin(T * state.f1[i] + state.p1[i]) + 0.3 * Math.sin(T * state.f2[i] + state.p2[i]));
57
- }
58
- }
59
- const vert = /* glsl */ `
60
- uniform float uW[24];
61
- uniform float uAmplitude;
62
- uniform float uBound;
63
- uniform float uSizeMultiplier;
64
- varying vec3 vNormal;
65
- varying vec3 vViewDir;
66
- varying float vField;
67
-
68
- // Real spherical harmonics for l = 1..4, as polynomials in the unit vector,
69
- // each scaled so its peak is about 1. The CPU animates the 24 weights.
70
- float sh(vec3 n){
71
- float x = n.x, y = n.y, z = n.z;
72
- float x2 = x * x, y2 = y * y, z2 = z * z;
73
- float s = 0.0;
74
- s += uW[0] * y + uW[1] * z + uW[2] * x;
75
- s += uW[3] * 2.0 * x * y + uW[4] * 2.0 * y * z + uW[5] * (1.5 * z2 - 0.5)
76
- + uW[6] * 2.0 * x * z + uW[7] * (x2 - y2);
77
- s += uW[8] * y * (3.0 * x2 - y2)
78
- + uW[9] * 5.2 * x * y * z
79
- + uW[10] * 0.8 * y * (5.0 * z2 - 1.0)
80
- + uW[11] * 0.5 * z * (5.0 * z2 - 3.0)
81
- + uW[12] * 0.8 * x * (5.0 * z2 - 1.0)
82
- + uW[13] * 2.6 * z * (x2 - y2)
83
- + uW[14] * x * (x2 - 3.0 * y2);
84
- s += uW[15] * 4.0 * x * y * (x2 - y2)
85
- + uW[16] * 3.0 * y * z * (3.0 * x2 - y2)
86
- + uW[17] * 1.5 * x * y * (7.0 * z2 - 1.0)
87
- + uW[18] * 0.95 * y * z * (7.0 * z2 - 3.0)
88
- + uW[19] * 0.125 * (35.0 * z2 * z2 - 30.0 * z2 + 3.0)
89
- + uW[20] * 0.95 * x * z * (7.0 * z2 - 3.0)
90
- + uW[21] * 0.78 * (x2 - y2) * (7.0 * z2 - 1.0)
91
- + uW[22] * 3.0 * x * z * (x2 - 3.0 * y2)
92
- + uW[23] * (x2 * (x2 - 3.0 * y2) - y2 * (3.0 * x2 - y2));
93
- return s;
94
- }
95
-
96
- vec3 shellPoint(vec3 n, out float f){
97
- f = sh(n);
98
- return n * uBound * uSizeMultiplier * (1.0 + uAmplitude * f);
99
- }
100
-
101
- void main(){
102
- vec3 n = normalize(position);
103
- float f, f1, f2;
104
- vec3 p0 = shellPoint(n, f);
105
-
106
- // normal from two nearby points on the displaced surface
107
- vec3 up = abs(n.y) > 0.99 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);
108
- vec3 t1 = normalize(cross(n, up));
109
- vec3 t2 = cross(n, t1);
110
- vec3 p1 = shellPoint(normalize(n + t1 * 0.01), f1);
111
- vec3 p2 = shellPoint(normalize(n + t2 * 0.01), f2);
112
- vec3 nrm = normalize(cross(p1 - p0, p2 - p0));
113
- if (dot(nrm, n) < 0.0) nrm = -nrm;
114
-
115
- vField = f;
116
- vec4 mv = modelViewMatrix * vec4(p0, 1.0);
117
- vNormal = normalize(normalMatrix * nrm);
118
- vViewDir = normalize(-mv.xyz);
119
- gl_Position = projectionMatrix * mv;
120
- }
121
- `;
122
- const frag = /* glsl */ `
123
- uniform vec3 uColor;
124
- uniform vec3 uRimColor;
125
- uniform float uOpacity;
126
- uniform float uIntensity;
127
- uniform float uFilmThickness;
128
- uniform float uNodalLines;
129
- uniform float uTint;
130
- varying vec3 vNormal;
131
- varying vec3 vViewDir;
132
- varying float vField;
133
-
134
- const float TAU = 6.28318530718;
135
-
136
- void main(){
137
- vec3 N = normalize(vNormal);
138
- vec3 V = normalize(vViewDir);
139
- float ndv = abs(dot(N, V));
140
-
141
- // Thin-film interference, as on a soap bubble: a film of index 1.33 and
142
- // thickness d delays the inner reflection by 2*n*d*cos(theta_t). Each
143
- // wavelength interferes on its own, which splits white light into bands.
144
- // The film is thicker where the harmonic pushes the shell out, so the
145
- // colour bands follow the shape.
146
- float cosT = sqrt(max(1.0 - (1.0 - ndv * ndv) / (1.33 * 1.33), 0.0));
147
- float opd = 2.0 * 1.33 * uFilmThickness * (1.0 + 0.35 * vField) * cosT;
148
- vec3 film = 0.5 + 0.5 * cos(TAU * opd / vec3(650.0, 532.0, 450.0));
149
- // tint: keep the film's light and dark bands, carry them in the base colour
150
- float lum = dot(film, vec3(0.299, 0.587, 0.114));
151
- film = mix(film, lum * uColor * 1.7 + film * 0.12, uTint);
152
-
153
- float fres = pow(1.0 - ndv, 3.0);
154
- vec3 col = uColor * 0.25 + film * (0.2 + 0.8 * fres);
155
- col = mix(col, uRimColor, fres * 0.45);
156
-
157
- // nodal lines: where the harmonic field crosses zero
158
- float nd = abs(vField) / max(fwidth(vField), 1e-4);
159
- float nodal = (1.0 - smoothstep(0.5, 1.5, nd)) * uNodalLines;
160
- col += uRimColor * nodal * 1.5;
161
-
162
- float a = (0.06 + 0.9 * fres + nodal * 0.8) * uOpacity;
163
- if (a < 0.003) discard;
164
- gl_FragColor = vec4(col * uIntensity, a);
165
- }
166
- `;
167
- /** Nothing rebuilds the shell: the vertex shader displaces a unit sphere. */
168
- const STRUCTURAL = [];
24
+ /** Knobs that rebuild the target field (never driven per frame). The sphere itself never rebuilds. */
25
+ const STRUCTURAL = ['target', 'text', 'font', 'shape', 'targetObject'];
169
26
  /** Knobs a keyframe or pointer binding may drive per frame — exactly what applyConfig pushes. */
170
27
  const ANIMATABLE = new Set(EFFECTS_ANIMATABLE.harmonicShell.map((p) => p.value));
171
28
  /** The single push point: every config knob reaches its uniform here. */
@@ -173,15 +30,19 @@ function applyConfig(u, c, bound) {
173
30
  u.uSpeed.value = c.speed;
174
31
  u.uIntensity.value = c.intensity;
175
32
  u.uOpacity.value = c.opacity;
176
- u.uColor.value.set(c.color);
177
- u.uRimColor.value.set(c.rimColor);
178
- u.uAmplitude.value = c.amplitude;
179
- u.uBound.value = bound;
180
- u.uSizeMultiplier.value = c.sizeMultiplier;
181
- u.uFilmThickness.value = c.filmThickness;
33
+ u.uHshColor.value.set(c.color);
34
+ u.uHshRim.value.set(c.rimColor);
35
+ u.uHshAmplitude.value = c.amplitude;
36
+ u.uHshBound.value = bound;
37
+ u.uHshSize.value = c.sizeMultiplier;
38
+ u.uHshFilm.value = c.filmThickness;
182
39
  u.uNodalLines.value = c.nodalLines;
183
40
  u.uDegree.value = c.degree;
184
- u.uTint.value = c.tint ?? 0;
41
+ u.uHshTint.value = c.tint ?? 0;
42
+ u.uHshMorph.value = c.morph ?? 0;
43
+ u.uHshDepth.value = (c.depth ?? 0.25) * bound;
44
+ u.uBreath.value = c.breath ?? 0.12;
45
+ u.uWeb.value = (c.webCut ?? 0.02) * bound;
185
46
  }
186
47
  const HarmonicShellEffect = ({ boundingRadius, config, id, keyframes, mouseMove, onObjectReady, onObjectDispose }) => {
187
48
  const cfg = useMemo(() => withConfigDefaults(DEFAULT_GENERATIVE_EFFECTS.harmonicShell, config), [config]);
@@ -189,34 +50,165 @@ const HarmonicShellEffect = ({ boundingRadius, config, id, keyframes, mouseMove,
189
50
  useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
190
51
  const state = useMemo(createHarmonicState, []);
191
52
  const uniforms = useMemo(() => ({
192
- uTime: { value: 0 },
53
+ ...createShellUniforms(),
193
54
  uSpeed: { value: 1 },
194
55
  uIntensity: { value: 1 },
195
56
  uOpacity: { value: 1 },
196
- uColor: { value: new THREE.Color() },
197
- uRimColor: { value: new THREE.Color() },
198
- uAmplitude: { value: 0.22 },
199
- uBound: { value: 1 },
200
- uSizeMultiplier: { value: 1.35 },
201
- uFilmThickness: { value: 380 },
202
57
  uNodalLines: { value: 0.6 },
203
58
  uDegree: { value: 3 },
204
- uTint: { value: 0 },
205
- uW: { value: new Float32Array(24) },
59
+ uBreath: { value: 0.12 },
60
+ uWeb: { value: 0.02 },
206
61
  }), []);
207
- const material = useMemo(() => new THREE.ShaderMaterial({ vertexShader: vert, fragmentShader: frag, uniforms, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending }), [uniforms]);
62
+ const material = useMemo(() => new THREE.ShaderMaterial({ vertexShader: SHELL_VERT, fragmentShader: SHELL_FRAG, uniforms, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide }), [uniforms]);
208
63
  useEffect(() => () => material.dispose(), [material]);
209
64
  // the vertex shader displaces a unit sphere, so the mesh never needs rebuilding
210
65
  const geometry = useMemo(() => new THREE.SphereGeometry(1, 192, 128), []);
211
66
  useEffect(() => () => geometry.dispose(), [geometry]);
67
+ // the field other objects read (the inShell effect): registered while mounted
68
+ const field = useMemo(() => ({ root: null, uniforms: uniforms, inv: new THREE.Matrix4(), target: null }), [uniforms]);
69
+ useEffect(() => {
70
+ if (!rootRef.current)
71
+ return;
72
+ field.root = rootRef.current;
73
+ registerShellField(field);
74
+ return () => unregisterShellField(field);
75
+ }, [field]);
212
76
  const push = useCallback((merged) => {
213
77
  applyConfig(uniforms, merged, boundingRadius);
214
78
  }, [uniforms, boundingRadius]);
215
79
  useLayoutEffect(() => { push(cfg); }, [push, cfg]);
216
80
  useConfigDriver({ rootRef, id, family: 'effects', base: cfg, structural: STRUCTURAL, allowed: ANIMATABLE, keyframes, mouseMove, push });
81
+ // ── the target field: rebuilt when a structural knob changes ──
82
+ const targetKind = cfg.target ?? 'none';
83
+ const targetObjectId = cfg.targetObject ?? '';
84
+ const targetRef = useRef(null);
85
+ useEffect(() => {
86
+ let cancelled = false;
87
+ const u = uniforms;
88
+ targetRef.current = null;
89
+ field.target = null;
90
+ if (targetKind === 'text' || targetKind === 'shape') {
91
+ const kind = targetKind === 'text' ? 'text' : (cfg.shape ?? 'star');
92
+ // the field spans a little more than the resting shell, so letters can reach past the sphere
93
+ bakeShapeSdf(kind, String(cfg.text ?? ''), String(cfg.font ?? 'sans'), boundingRadius * cfg.sizeMultiplier * 1.15).then((sdf) => {
94
+ if (cancelled || !sdf)
95
+ return;
96
+ const old = u.uHshSdf.value;
97
+ u.uHshSdf.value = sdf.tex;
98
+ u.uHshSdfHalf.value.set(sdf.half, sdf.half);
99
+ u.uHshMode.value = 1;
100
+ if (old && old.image?.width > 1)
101
+ old.dispose();
102
+ });
103
+ }
104
+ else if (targetKind === 'object' && targetObjectId) {
105
+ // the object may land after us (a model loading): look again until it is there
106
+ let tries = 0;
107
+ const find = () => {
108
+ if (cancelled)
109
+ return;
110
+ const obj = findById(targetObjectId);
111
+ if (!obj) {
112
+ if (tries++ < 60)
113
+ setTimeout(find, 500);
114
+ else
115
+ console.warn(`[harmonicShell] target object "${targetObjectId}" not found in the scene`);
116
+ return;
117
+ }
118
+ bakeObjectSdf(obj).then((sdf) => {
119
+ if (cancelled || !sdf)
120
+ return;
121
+ targetRef.current = obj;
122
+ field.target = obj;
123
+ u.uHshSdf3.value = sdf.tex;
124
+ u.uHshSdf3Min.value.copy(sdf.min);
125
+ u.uHshSdf3Size.value.copy(sdf.size);
126
+ u.uHshMode.value = 2;
127
+ }).catch((err) => console.warn('[harmonicShell] object field bake failed', err));
128
+ };
129
+ find();
130
+ }
131
+ else {
132
+ u.uHshMode.value = 0;
133
+ }
134
+ return () => { cancelled = true; };
135
+ // eslint-disable-next-line react-hooks/exhaustive-deps
136
+ }, [targetKind, targetObjectId, cfg.text, cfg.font, cfg.shape, boundingRadius, uniforms, field]);
137
+ // ── objects crossing the shell show it: the inShell layer, attached and released at runtime ──
138
+ const inside = useMemo(() => ({ frame: 0, on: new Set(), box: new THREE.Box3(), sphere: new THREE.Sphere(), centre: new THREE.Vector3(), scale: new THREE.Vector3() }), []);
139
+ // the objects the shell affects (ids on the shell's config; nothing on them). Looked up each scan: they may land later or be replaced.
140
+ const insideIds = useMemo(() => (Array.isArray(cfg.insideObjects) ? cfg.insideObjects.filter((v) => typeof v === 'string' && v) : []), [cfg.insideObjects]);
141
+ const insideIdsRef = useRef(insideIds);
142
+ insideIdsRef.current = insideIds;
143
+ const look = useMemo(() => ({ look: cfg.insideLook === 'ghost' ? 'ghost' : 'film', bands: cfg.insideBands ?? 1, seam: cfg.insideSeam ?? 1, warp: cfg.insideWarp ?? 0.6, core: cfg.insideCore ?? 0.5 }), [cfg.insideLook, cfg.insideBands, cfg.insideSeam, cfg.insideWarp, cfg.insideCore]);
144
+ const lookRef = useRef(look);
145
+ lookRef.current = look;
146
+ // the look changed: push it to everything the shell is on
147
+ useEffect(() => { inside.on.forEach((o) => shellAttach(o, look)); }, [look, inside]);
148
+ useEffect(() => () => { inside.on.forEach((o) => shellRelease(o)); inside.on.clear(); }, [inside]);
149
+ // Objects not on the list are never touched. No separate pre-warm: the runtime's warm pass applies and quietly reverts the operation, and
150
+ // that revert would strip a layer the shell has just attached. The attach compiles its program
151
+ // in the background before anything shows (runProgress), and fires when the bounds touch — before
152
+ // any pixel is inside — so the compile is hidden on the first crossing.
153
+ const scanInside = () => {
154
+ const root = rootRef.current;
155
+ if (!root)
156
+ return;
157
+ const ids = insideIdsRef.current;
158
+ if (!ids.length) {
159
+ if (inside.on.size) {
160
+ inside.on.forEach((o) => shellRelease(o));
161
+ inside.on.clear();
162
+ }
163
+ return;
164
+ }
165
+ inside.centre.setFromMatrixPosition(root.matrixWorld);
166
+ root.matrixWorld.decompose(new THREE.Vector3(), new THREE.Quaternion(), inside.scale);
167
+ const R = uniforms.uHshBound.value * uniforms.uHshSize.value * (1 + uniforms.uHshAmplitude.value) * Math.max(inside.scale.x, inside.scale.y, inside.scale.z);
168
+ const seen = new Set();
169
+ for (const id of ids) {
170
+ const o = findById(id);
171
+ // objects with real meshes (mesh / model); the shell's own wrap target is the shell, not something inside it
172
+ if (!o || o.userData?.visualFx !== true || o.userData.objectType === 'group' || o === targetRef.current || !o.visible)
173
+ continue;
174
+ inside.box.setFromObject(o);
175
+ if (inside.box.isEmpty())
176
+ continue;
177
+ inside.box.getBoundingSphere(inside.sphere);
178
+ const d = inside.sphere.center.distanceTo(inside.centre);
179
+ const was = inside.on.has(o);
180
+ // a little hysteresis, so an object resting on the boundary doesn't flicker on and off
181
+ if (d < R + inside.sphere.radius * (was ? 1.15 : 1.0))
182
+ seen.add(o);
183
+ }
184
+ for (const o of seen)
185
+ if (!inside.on.has(o) && shellAttach(o, lookRef.current, isOperationOn('inShell', o)))
186
+ inside.on.add(o);
187
+ for (const o of inside.on)
188
+ if (!seen.has(o)) {
189
+ inside.on.delete(o);
190
+ shellRelease(o);
191
+ }
192
+ for (const o of inside.on)
193
+ if (!shellAttached(o))
194
+ inside.on.delete(o); // taken away underneath (an own effect reverted) → re-attached next scan
195
+ };
196
+ const _m = useMemo(() => new THREE.Matrix4(), []);
217
197
  useFrame((_, delta) => {
218
- uniforms.uTime.value += delta * uniforms.uSpeed.value;
219
- updateHarmonicWeights(state, uniforms.uW.value, uniforms.uTime.value, uniforms.uDegree.value, delta);
198
+ if (++inside.frame % 6 === 0)
199
+ scanInside();
200
+ uniforms.uHshTime.value += delta * uniforms.uSpeed.value;
201
+ updateHarmonicWeights(state, uniforms.uHshW.value, uniforms.uHshTime.value, uniforms.uDegree.value, delta);
202
+ const root = rootRef.current;
203
+ if (root) {
204
+ field.inv.copy(root.matrixWorld).invert();
205
+ const t = targetRef.current;
206
+ // shell-local → the target object's local space (where its field was baked), live: both may move
207
+ if (t && uniforms.uHshMode.value === 2) {
208
+ uniforms.uHshSdf3Inv.value.copy(_m.copy(t.matrixWorld).invert()).multiply(root.matrixWorld);
209
+ uniforms.uHshSdf3Mat.value.copy(uniforms.uHshSdf3Inv.value).invert();
210
+ }
211
+ }
220
212
  });
221
213
  return (_jsx("group", { ref: rootRef, children: _jsx("mesh", { geometry: geometry, material: material }) }));
222
214
  };