@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.
- package/dist/animationsV2/appliers.d.ts.map +1 -1
- package/dist/animationsV2/appliers.js +11 -16
- package/dist/animationsV2/sequenceUtils.d.ts +14 -1
- package/dist/animationsV2/sequenceUtils.d.ts.map +1 -1
- package/dist/animationsV2/sequenceUtils.js +27 -4
- package/dist/animationsV2/types.d.ts +2 -0
- package/dist/animationsV2/types.d.ts.map +1 -1
- package/dist/components/ObjectNode.js +1 -1
- package/dist/components/effects/HarmonicShellEffect.d.ts.map +1 -1
- package/dist/components/effects/HarmonicShellEffect.js +165 -173
- package/dist/components/effects/harmonicShellField.d.ts +77 -0
- package/dist/components/effects/harmonicShellField.d.ts.map +1 -0
- package/dist/components/effects/harmonicShellField.js +752 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useConfigLive.d.ts +6 -0
- package/dist/hooks/useConfigLive.d.ts.map +1 -1
- package/dist/hooks/useConfigLive.js +16 -12
- package/dist/operations/catalogue/inShell.d.ts +22 -0
- package/dist/operations/catalogue/inShell.d.ts.map +1 -0
- package/dist/operations/catalogue/inShell.js +245 -0
- package/dist/operations/catalogue/shaderLayers.d.ts.map +1 -1
- package/dist/operations/catalogue/shaderLayers.js +10 -7
- package/dist/operations/registry.d.ts.map +1 -1
- package/dist/operations/registry.js +2 -1
- package/package.json +3 -2
|
@@ -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;
|
|
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,
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
138
|
-
if (!
|
|
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
|
-
|
|
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":"AAwBA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,QAAQ,CAAC;IACnB,8FAA8F;IAC9F,IAAI,EAAE,aAAa,CAAC;IACpB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,WAAW,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxC,uFAAuF;IACvF,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC,CAAC;AAK1G,eAAO,MAAM,OAAO,GAAI,OAAO,SAAS,aAAa,EAAE,KAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAC/C,CAAC;AAKrC,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,GAAG,gBAAgB,
|
|
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
|
-
|
|
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
|
|
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":"AAMA,wFAAwF;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,MAAM,QAAQ,GAChB,QAAQ,GAAG,MAAM,GACjB,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,aAAa,GAAG,cAAc,GAAG,gBAAgB,GACjD,aAAa,GAAG,cAAc,GAAG,gBAAgB,GACjD,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,gBAAgB,GAAG,eAAe,CAAC;AAEvC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,+BAA+B;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,QAAQ,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qJAAqJ;IACrJ,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,qFAAqF;IACrF,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,uFAAuF;IACvF,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IACrB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAMD,8FAA8F;AAC9F,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,oGAAoG;IACpG,KAAK,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/animationsV2/types.ts"],"names":[],"mappings":"AAMA,wFAAwF;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,MAAM,QAAQ,GAChB,QAAQ,GAAG,MAAM,GACjB,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,aAAa,GAAG,cAAc,GAAG,gBAAgB,GACjD,aAAa,GAAG,cAAc,GAAG,gBAAgB,GACjD,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,YAAY,GAAG,aAAa,GAAG,eAAe,GAC9C,gBAAgB,GAAG,eAAe,CAAC;AAEvC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,+BAA+B;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,QAAQ,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qJAAqJ;IACrJ,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,qFAAqF;IACrF,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,uFAAuF;IACvF,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IACrB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAMD,8FAA8F;AAC9F,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,oGAAoG;IACpG,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uGAAuG;IACvG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,sGAAsG;AACtG,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,0GAA0G;IAC1G,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,8FAA8F;AAC9F,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kFAAkF;AAClF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzE,MAAM,WAAW,sBAAsB;IACrC,+GAA+G;IAC/G,WAAW,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,eAAO,MAAM,uBAAuB,EAAE,sBAA2E,CAAC;AAElH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,6EAA6E;IAC7E,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,+BAA+B;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,oFAAoF;IACpF,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,sBAAsB,CAAC;IACvC,mFAAmF;IACnF,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,YAAY,EAAE,QAA2B,CAAC;AACvD,eAAO,MAAM,WAAW,2BAA4B,CAAC"}
|
|
@@ -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
|
|
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;
|
|
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
|
-
/**
|
|
16
|
-
const
|
|
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.
|
|
177
|
-
u.
|
|
178
|
-
u.
|
|
179
|
-
u.
|
|
180
|
-
u.
|
|
181
|
-
u.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
59
|
+
uBreath: { value: 0.12 },
|
|
60
|
+
uWeb: { value: 0.02 },
|
|
206
61
|
}), []);
|
|
207
|
-
const material = useMemo(() => new THREE.ShaderMaterial({ vertexShader:
|
|
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
|
-
|
|
219
|
-
|
|
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
|
};
|