@3driseai/3drise-core 1.0.25 → 1.0.27

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.
Files changed (38) hide show
  1. package/dist/data/effectsDefaults.d.ts.map +1 -1
  2. package/dist/data/effectsDefaults.js +3 -1
  3. package/dist/data/index.d.ts +1 -0
  4. package/dist/data/index.d.ts.map +1 -1
  5. package/dist/data/index.js +1 -0
  6. package/dist/data/lightShadowDefaults.d.ts +14 -0
  7. package/dist/data/lightShadowDefaults.d.ts.map +1 -0
  8. package/dist/data/lightShadowDefaults.js +92 -0
  9. package/dist/properties/registry.d.ts.map +1 -1
  10. package/dist/properties/registry.js +7 -1
  11. package/dist/properties/tables/effectsProperties.d.ts.map +1 -1
  12. package/dist/properties/tables/effectsProperties.js +1 -0
  13. package/dist/types/generativeEffects.d.ts +2 -0
  14. package/dist/types/generativeEffects.d.ts.map +1 -1
  15. package/dist/types/scene3d.d.ts +31 -2
  16. package/dist/types/scene3d.d.ts.map +1 -1
  17. package/dist/utils/animationTargets.d.ts +57 -0
  18. package/dist/utils/animationTargets.d.ts.map +1 -0
  19. package/dist/utils/animationTargets.js +356 -0
  20. package/dist/utils/cdnUrls.d.ts +5 -0
  21. package/dist/utils/cdnUrls.d.ts.map +1 -0
  22. package/dist/utils/cdnUrls.js +8 -0
  23. package/dist/utils/handleMaterialEffects.d.ts +3 -0
  24. package/dist/utils/handleMaterialEffects.d.ts.map +1 -1
  25. package/dist/utils/handleMaterialEffects.js +29 -0
  26. package/dist/utils/handleTransformEffects.d.ts +14 -0
  27. package/dist/utils/handleTransformEffects.d.ts.map +1 -1
  28. package/dist/utils/handleTransformEffects.js +57 -30
  29. package/dist/utils/index.d.ts +2 -0
  30. package/dist/utils/index.d.ts.map +1 -1
  31. package/dist/utils/index.js +2 -0
  32. package/dist/utils/materialApplicationUtils.d.ts +5 -1
  33. package/dist/utils/materialApplicationUtils.d.ts.map +1 -1
  34. package/dist/utils/materialApplicationUtils.js +9 -2
  35. package/dist/utils/objectEffects.d.ts +13 -0
  36. package/dist/utils/objectEffects.d.ts.map +1 -1
  37. package/dist/utils/objectEffects.js +53 -0
  38. package/package.json +1 -1
@@ -0,0 +1,356 @@
1
+ // =============================================================================
2
+ // Animation targets — Self / Each / Targets on groups and models
3
+ // =============================================================================
4
+ //
5
+ // An Animation-tab row on a group or a model reaches (AnimationOptions.scope):
6
+ // self the object as one piece — the old path, `to` absolute (not handled here)
7
+ // each every part on its own: a group's leaf child objects (full depth, through
8
+ // nested groups), a model's meshes
9
+ // targets only the chosen ones (AnimationOptions.targets), each on its own:
10
+ // group → child objects by id (a model child optionally narrowed to nodes),
11
+ // model → nodes by name (every layout copy)
12
+ //
13
+ // Transform `to` on each / targets is RELATIVE to the target's own rest value:
14
+ // position / rotation are added, scale is multiplied. Material `to` stays absolute.
15
+ //
16
+ // Rest values:
17
+ // a scene object root (a group child) → `userData.baseTransform`, written by the
18
+ // viewer's AnimatedObject from the object's settings, so editing the child in the
19
+ // studio moves its rest too;
20
+ // a model node / mesh → snapshotted before the first write (it already carries any
21
+ // saved node override).
22
+ // A target the rows stop driving (row removed, switched off, scope changed) is put
23
+ // back on its rest at the end of the frame (`AnimationTargetState.endFrame`).
24
+ //
25
+ // Materials: a model node whose material is shared with a mesh outside the targets
26
+ // gets its own clone before the first write (otherwise the whole model changes);
27
+ // the original is put back when nothing drives that node's material any more.
28
+ //
29
+ // Stagger (AnimationOptions.stagger): each target starts `delay` seconds later, its
30
+ // delay = total · rank / (n − 1), rank by tree order / axis / centre-out / seeded random.
31
+ //
32
+ import { Box3, Vector3 } from 'three';
33
+ import { findNodesByName } from './nodeOverrides.js';
34
+ import { getMaterialPropertyValue, applyMaterialPropertyToObject } from './materialUtils.js';
35
+ /** What kind of root the scope works on; null = scope does not apply (self). */
36
+ export function targetRootKind(root) {
37
+ const t = root.userData?.objectType;
38
+ return t === 'group' || t === 'model' ? t : null;
39
+ }
40
+ /** The scope a row really runs with on this root ('self' when it cannot apply). */
41
+ export function effectiveScope(root, anim) {
42
+ const scope = anim.scope ?? 'self';
43
+ if (scope === 'self' || !targetRootKind(root))
44
+ return 'self';
45
+ if (scope === 'targets' && !anim.targets?.length)
46
+ return 'self';
47
+ return scope;
48
+ }
49
+ const isFxHelper = (o) => !!o.userData?.__opFxHelper;
50
+ /** Leaf object roots under a group (through nested groups), tree order. */
51
+ function groupLeaves(root) {
52
+ const out = [];
53
+ const walk = (n) => {
54
+ for (const c of n.children) {
55
+ if (isFxHelper(c))
56
+ continue;
57
+ if (c.userData?.id) {
58
+ if (c.userData.objectType === 'group')
59
+ walk(c);
60
+ else
61
+ out.push(c);
62
+ }
63
+ else
64
+ walk(c);
65
+ }
66
+ };
67
+ walk(root);
68
+ return out;
69
+ }
70
+ /** The object root with this id under `root` (any depth). */
71
+ function findObjectRoot(root, id) {
72
+ let hit = null;
73
+ root.traverse((c) => { if (!hit && c !== root && c.userData?.id === id)
74
+ hit = c; });
75
+ return hit;
76
+ }
77
+ /** A model's meshes, tree order (effect helpers left out). */
78
+ function meshesOf(root) {
79
+ const out = [];
80
+ const walk = (n) => {
81
+ for (const c of n.children) {
82
+ if (isFxHelper(c))
83
+ continue;
84
+ if (c.isMesh)
85
+ out.push(c);
86
+ walk(c);
87
+ }
88
+ };
89
+ walk(root);
90
+ return out;
91
+ }
92
+ const nodesNamed = (root, name) => findNodesByName(root, name).filter(n => n !== root);
93
+ /**
94
+ * The Object3Ds a row reaches on `root`, in tree order, without duplicates.
95
+ * 'self' (or a root that is neither a group nor a model) → [root].
96
+ */
97
+ export function resolveAnimationTargets(root, scope, targets) {
98
+ const kind = targetRootKind(root);
99
+ if (!kind || !scope || scope === 'self')
100
+ return [root];
101
+ let out;
102
+ if (kind === 'group') {
103
+ if (scope === 'each')
104
+ out = groupLeaves(root);
105
+ else {
106
+ out = [];
107
+ for (const t of targets ?? []) {
108
+ const child = findObjectRoot(root, t.id);
109
+ if (!child)
110
+ continue;
111
+ if (t.nodes?.length)
112
+ for (const n of t.nodes)
113
+ out.push(...nodesNamed(child, n));
114
+ else
115
+ out.push(child);
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ if (scope === 'each')
121
+ out = meshesOf(root);
122
+ else {
123
+ out = [];
124
+ for (const t of targets ?? [])
125
+ out.push(...nodesNamed(root, t.id));
126
+ }
127
+ }
128
+ return [...new Set(out)];
129
+ }
130
+ // -----------------------------------------------------------------------------
131
+ // Stagger
132
+ // -----------------------------------------------------------------------------
133
+ const hash = (s) => { let h = 2166136261; for (let i = 0; i < s.length; i++) {
134
+ h ^= s.charCodeAt(i);
135
+ h = Math.imul(h, 16777619);
136
+ } return h >>> 0; };
137
+ function mulberry32(seed) {
138
+ return () => {
139
+ seed |= 0;
140
+ seed = (seed + 0x6d2b79f5) | 0;
141
+ let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
142
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
143
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
144
+ };
145
+ }
146
+ /** Seconds each target waits, same order as `objs`. No stagger / total 0 / one target → all 0. */
147
+ export function staggerDelays(root, objs, stagger, seed) {
148
+ const n = objs.length;
149
+ const total = Math.max(0, Number(stagger?.total) || 0);
150
+ if (!total || n < 2)
151
+ return objs.map(() => 0);
152
+ const order = stagger?.order ?? 'tree';
153
+ let ranked; // indices into objs, first goes first
154
+ if (order === 'tree')
155
+ ranked = objs.map((_, i) => i);
156
+ else if (order === 'random') {
157
+ const rnd = mulberry32(hash(seed));
158
+ ranked = objs.map((_, i) => i);
159
+ for (let i = n - 1; i > 0; i--) {
160
+ const j = Math.floor(rnd() * (i + 1));
161
+ [ranked[i], ranked[j]] = [ranked[j], ranked[i]];
162
+ }
163
+ }
164
+ else {
165
+ root.updateWorldMatrix(true, true);
166
+ const box = new Box3();
167
+ const centres = objs.map((o) => {
168
+ const c = new Vector3();
169
+ box.setFromObject(o);
170
+ if (box.isEmpty())
171
+ o.getWorldPosition(c);
172
+ else
173
+ box.getCenter(c);
174
+ return root.worldToLocal(c);
175
+ });
176
+ let key;
177
+ if (order === 'radial') {
178
+ const mid = centres.reduce((a, c) => a.add(c), new Vector3()).divideScalar(n);
179
+ key = (c) => c.distanceTo(mid);
180
+ }
181
+ else
182
+ key = (c) => c[order];
183
+ ranked = objs.map((_, i) => i).sort((a, b) => key(centres[a]) - key(centres[b]) || a - b);
184
+ }
185
+ const delays = new Array(n).fill(0);
186
+ ranked.forEach((idx, rank) => { delays[idx] = (total * rank) / (n - 1); });
187
+ return delays;
188
+ }
189
+ /** Re-resolve a row's targets this often (frames) — children join / leave, models load. */
190
+ const RESOLVE_EVERY = 60;
191
+ export class AnimationTargetState {
192
+ root;
193
+ frame = 0;
194
+ resolved = new Map();
195
+ /** Transform rest snapshots of nodes (object roots use userData.baseTransform). */
196
+ restT = new Map();
197
+ /** Material rest values per target and property. */
198
+ restM = new Map();
199
+ /** Written this frame / last frame: `${uuid}|t|position` → target. */
200
+ touched = new Map();
201
+ last = new Map();
202
+ /** Material clones made per target node. */
203
+ owned = new Map();
204
+ constructor(root) {
205
+ this.root = root;
206
+ }
207
+ get object() { return this.root; }
208
+ beginFrame() {
209
+ this.frame++;
210
+ this.last = this.touched;
211
+ this.touched = new Map();
212
+ }
213
+ /** Targets + delays of a row, cached. */
214
+ targetsOf(anim, scope) {
215
+ const key = `${anim.property}|${scope}|${JSON.stringify(anim.targets ?? null)}|${JSON.stringify(anim.stagger ?? null)}`;
216
+ const hit = this.resolved.get(key);
217
+ if (hit && this.frame - hit.frame < RESOLVE_EVERY && hit.objs.every(o => o.parent))
218
+ return hit;
219
+ const objs = resolveAnimationTargets(this.root, scope, anim.targets);
220
+ const same = hit && hit.objs.length === objs.length && hit.objs.every((o, i) => o === objs[i]);
221
+ const delays = same ? hit.delays : staggerDelays(this.root, objs, anim.stagger, `${this.root.userData?.id ?? this.root.uuid}|${anim.property}`);
222
+ const next = { objs, delays, frame: this.frame };
223
+ this.resolved.set(key, next);
224
+ return next;
225
+ }
226
+ /** Keep a target on what it has (a hover row while not hovered): not restored this frame. */
227
+ hold(obj, domain, prop) {
228
+ const k = `${obj.uuid}|${domain}|${prop}`;
229
+ const prev = this.last.get(k);
230
+ if (prev)
231
+ this.touched.set(k, prev);
232
+ }
233
+ // ---- transform ----------------------------------------------------------
234
+ transformRest(obj, prop) {
235
+ const base = obj.userData?.baseTransform?.[prop];
236
+ if (Array.isArray(base) && base.length >= 3)
237
+ return [base[0], base[1], base[2]];
238
+ let snap = this.restT.get(obj);
239
+ if (!snap) {
240
+ snap = {};
241
+ this.restT.set(obj, snap);
242
+ }
243
+ if (!snap[prop]) {
244
+ const v = obj[prop];
245
+ snap[prop] = [v.x, v.y, v.z];
246
+ }
247
+ return snap[prop];
248
+ }
249
+ writeTransform(obj, prop, v) {
250
+ obj[prop].set(v[0], v[1], v[2]);
251
+ this.touched.set(`${obj.uuid}|t|${prop}`, { obj, domain: 't', prop });
252
+ }
253
+ /** Mark a transform as driven without writing (keyframe rows write themselves). */
254
+ touchTransform(obj, prop) {
255
+ this.transformRest(obj, prop);
256
+ this.touched.set(`${obj.uuid}|t|${prop}`, { obj, domain: 't', prop });
257
+ }
258
+ // ---- material -----------------------------------------------------------
259
+ /** The target's own value before the first write (what it goes back to). */
260
+ materialRest(obj, prop) {
261
+ let rec = this.restM.get(obj);
262
+ if (!rec) {
263
+ rec = {};
264
+ this.restM.set(obj, rec);
265
+ }
266
+ if (!(prop in rec))
267
+ rec[prop] = getMaterialPropertyValue(obj, prop);
268
+ return rec[prop];
269
+ }
270
+ writeMaterial(obj, prop, value) {
271
+ if (!obj.userData?.id)
272
+ this.ownMaterials(obj);
273
+ applyMaterialPropertyToObject(obj, prop, value);
274
+ this.touched.set(`${obj.uuid}|m|${prop}`, { obj, domain: 'm', prop });
275
+ }
276
+ /** A model node: clone the materials it shares with meshes outside it (once). */
277
+ ownMaterials(node) {
278
+ if (this.owned.has(node))
279
+ return;
280
+ const inside = new Set();
281
+ node.traverse(c => inside.add(c));
282
+ const sharedOutside = new Set();
283
+ this.root.traverse((c) => {
284
+ if (inside.has(c))
285
+ return;
286
+ const m = c.material;
287
+ if (!m)
288
+ return;
289
+ for (const x of Array.isArray(m) ? m : [m])
290
+ if (x)
291
+ sharedOutside.add(x);
292
+ });
293
+ const list = [];
294
+ node.traverse((c) => {
295
+ const m = c.material;
296
+ if (!m || isFxHelper(c))
297
+ return;
298
+ const arr = Array.isArray(m) ? m : [m];
299
+ if (!arr.some(x => sharedOutside.has(x)))
300
+ return;
301
+ list.push({ mesh: c, original: m });
302
+ c.material = Array.isArray(m) ? m.map(x => (sharedOutside.has(x) ? x.clone() : x)) : m.clone();
303
+ });
304
+ this.owned.set(node, list);
305
+ }
306
+ releaseMaterials(node) {
307
+ const list = this.owned.get(node);
308
+ if (!list)
309
+ return;
310
+ for (const { mesh, original } of list) {
311
+ const cur = mesh.material;
312
+ for (const x of Array.isArray(cur) ? cur : [cur]) {
313
+ if (x && !(Array.isArray(original) ? original.includes(x) : original === x))
314
+ x.dispose?.();
315
+ }
316
+ mesh.material = original;
317
+ }
318
+ this.owned.delete(node);
319
+ this.restM.delete(node);
320
+ }
321
+ // ---- end of frame -------------------------------------------------------
322
+ /** Put back on their rest the targets driven last frame and not this frame. */
323
+ endFrame() {
324
+ for (const [k, t] of this.last) {
325
+ if (this.touched.has(k))
326
+ continue;
327
+ if (t.domain === 't') {
328
+ const prop = t.prop;
329
+ const r = this.transformRest(t.obj, prop);
330
+ t.obj[prop].set(r[0], r[1], r[2]);
331
+ this.restT.get(t.obj) && delete this.restT.get(t.obj)[prop];
332
+ }
333
+ else {
334
+ const rec = this.restM.get(t.obj);
335
+ if (rec && t.prop in rec)
336
+ applyMaterialPropertyToObject(t.obj, t.prop, rec[t.prop]);
337
+ if (rec)
338
+ delete rec[t.prop];
339
+ const stillDriven = [...this.touched.values()].some(x => x.obj === t.obj && x.domain === 'm');
340
+ if (!stillDriven)
341
+ this.releaseMaterials(t.obj);
342
+ }
343
+ }
344
+ this.last = new Map();
345
+ }
346
+ /** Everything back on its rest (the object goes away / the hook stops). */
347
+ dispose() {
348
+ this.beginFrame();
349
+ this.endFrame();
350
+ for (const node of [...this.owned.keys()])
351
+ this.releaseMaterials(node);
352
+ this.resolved.clear();
353
+ this.restT.clear();
354
+ this.restM.clear();
355
+ }
356
+ }
@@ -0,0 +1,5 @@
1
+ export declare const SPACES_ORIGIN_HOST = ".sfo3.digitaloceanspaces.com/";
2
+ export declare const SPACES_CDN_HOST = ".sfo3.cdn.digitaloceanspaces.com/";
3
+ /** Origin Spaces URL → CDN URL. Anything else (CDN, data:, blob:, relative, null) passes through. */
4
+ export declare const toCdn: <T extends string | null | undefined>(url: T) => T;
5
+ //# sourceMappingURL=cdnUrls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdnUrls.d.ts","sourceRoot":"","sources":["../../src/utils/cdnUrls.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,kBAAkB,kCAAkC,CAAC;AAClE,eAAO,MAAM,eAAe,sCAAsC,CAAC;AAEnE,qGAAqG;AACrG,eAAO,MAAM,KAAK,GAAI,CAAC,SAAS,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,CAAC,KAAG,CACuB,CAAC"}
@@ -0,0 +1,8 @@
1
+ // Asset URLs are stored with the Spaces origin host (generatePublicUrl in data-center).
2
+ // Anything that DISPLAYS or FETCHES an asset goes through the Spaces CDN instead. The DB keeps
3
+ // the origin URL so string matches (templatesUsingUrl, delete key extraction) are unaffected —
4
+ // so call toCdn only where a URL is shown or fetched, never on a value that gets saved back.
5
+ export const SPACES_ORIGIN_HOST = '.sfo3.digitaloceanspaces.com/';
6
+ export const SPACES_CDN_HOST = '.sfo3.cdn.digitaloceanspaces.com/';
7
+ /** Origin Spaces URL → CDN URL. Anything else (CDN, data:, blob:, relative, null) passes through. */
8
+ export const toCdn = (url) => (typeof url === 'string' ? url.replace(SPACES_ORIGIN_HOST, SPACES_CDN_HOST) : url);
@@ -1,10 +1,13 @@
1
1
  import { Object3D } from "three";
2
2
  import { AnimationOptions, ColorEffectV2, EffectState, MaterialSettings } from "../types";
3
+ import { type AnimationTargetState } from "./animationTargets";
3
4
  export interface MaterialAnimationContext {
4
5
  object: Object3D;
5
6
  materialSettings: MaterialSettings;
6
7
  /** Cache of start values for animations (property -> value) */
7
8
  startValuesCache: Record<string, any>;
9
+ /** Groups / models: rows scoped each / targets run through it (see animationTargets.ts). */
10
+ targets?: AnimationTargetState;
8
11
  }
9
12
  export declare const DEBUG_MATERIAL_EFFECTS = false;
10
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"handleMaterialEffects.d.ts","sourceRoot":"","sources":["../../src/utils/handleMaterialEffects.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC1F,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,QAAQ,CAAC;IACjB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACzC;AAMD,eAAO,MAAM,sBAAsB,QAAQ,CAAC;AAmC5C;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,wBAAwB,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CA6B/I;AAID;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,wBAAwB,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAWnJ;AAID;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE;IACnH,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAC/B,EAAE,eAAe,GAAE,OAAe,GAAG,IAAI,CAcjD"}
1
+ {"version":3,"file":"handleMaterialEffects.d.ts","sourceRoot":"","sources":["../../src/utils/handleMaterialEffects.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC1F,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,QAAQ,CAAC;IACjB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtC,4FAA4F;IAC5F,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAClC;AAMD,eAAO,MAAM,sBAAsB,QAAQ,CAAC;AAmC5C;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,wBAAwB,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CA6B/I;AAID;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,wBAAwB,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAgBnJ;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE;IACnH,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAC/B,EAAE,eAAe,GAAE,OAAe,GAAG,IAAI,CAcjD"}
@@ -10,6 +10,7 @@ import { calculateTimeProgress, getMaterialPropertyValue, interpolateValue, appl
10
10
  import { applyEasing, gsapEasingMap } from "./animationsUtils.js";
11
11
  import { createDefaultEffectState, } from "./utils.js";
12
12
  import { applyAllColorEffects } from "./frameAnimations.js";
13
+ import { effectiveScope } from "./animationTargets.js";
13
14
  // =============================================================================
14
15
  // DEBUG
15
16
  // =============================================================================
@@ -98,9 +99,37 @@ export function handleMaterialAnimations(ctx, animations, currentTime, hovered)
98
99
  if (property === "position" || property === "rotation" || property === "scale") {
99
100
  return;
100
101
  }
102
+ const scope = ctx.targets ? effectiveScope(ctx.object, animation) : 'self';
103
+ if (scope !== 'self') {
104
+ animateMaterialTargets(ctx.targets, animation, scope, currentTime, hovered);
105
+ return;
106
+ }
101
107
  animateMaterialProperty(ctx, animation, currentTime, hovered);
102
108
  });
103
109
  }
110
+ /** A row scoped each / targets: each target from its own value to `to` (absolute), `delay` later each (stagger). */
111
+ function animateMaterialTargets(state, animation, scope, currentTime, hovered) {
112
+ const { property, to, duration, ease, loop = false, yoyo = false } = animation;
113
+ if (to === undefined)
114
+ return;
115
+ const { objs, delays } = state.targetsOf(animation, scope);
116
+ if (animation.triggerMode === "hover" && !hovered) {
117
+ for (const o of objs)
118
+ state.hold(o, 'm', property);
119
+ return;
120
+ }
121
+ const easing = ease ? (gsapEasingMap[ease] || ease) : null;
122
+ objs.forEach((target, i) => {
123
+ const rest = state.materialRest(target, property);
124
+ const from = animation.from !== undefined ? animation.from : rest;
125
+ if (from === null || from === undefined)
126
+ return;
127
+ let t = calculateTimeProgress(currentTime - delays[i] * 1000, duration, yoyo, loop);
128
+ if (easing)
129
+ t = applyEasing(easing, t);
130
+ state.writeMaterial(target, property, interpolateValue(property, from, to, t));
131
+ });
132
+ }
104
133
  // =============================================================================
105
134
  // HANDLE MATERIAL EFFECTS (COLOR EFFECTS)
106
135
  // =============================================================================
@@ -2,10 +2,24 @@ import type { Object3D } from 'three';
2
2
  import type { AnimationOptions, Vector3Array } from "../types/scene3d";
3
3
  import type { TransformEffectV2 } from "../types/objectSettings";
4
4
  import type { EffectState } from "../types/effects";
5
+ import { type AnimationTargetState } from "./animationTargets";
5
6
  export interface TransformAnimationContext {
6
7
  object: Object3D;
7
8
  meshSettings: any;
8
9
  pathIsActive: boolean;
10
+ /** Groups / models: rows scoped each / targets run through it (see animationTargets.ts). Absent = every row on the object itself. */
11
+ targets?: AnimationTargetState;
12
+ /**
13
+ * A timeline clip driving a row's `to` (by its index in the list): laid over the authored value
14
+ * while the clip runs — live, never written to the settings. Absent / undefined = the row's own.
15
+ */
16
+ liveTo?: (index: number) => Vector3Array | undefined;
17
+ /**
18
+ * A timeline clip has taken this property over (the object itself: target null; a group's /
19
+ * model's child: that child). The clip has full control while it is active: the row stops on
20
+ * that target — nothing is added, nothing is restored — and runs again once the clip ends.
21
+ */
22
+ takenOver?: (target: Object3D | null, property: 'position' | 'rotation' | 'scale') => boolean;
9
23
  }
10
24
  export declare const DEBUG_TRANSFORM_EFFECTS = false;
11
25
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"handleTransformEffects.d.ts","sourceRoot":"","sources":["../../src/utils/handleTransformEffects.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAKtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,MAAM,WAAW,yBAAyB;IACtC,MAAM,EAAE,QAAQ,CAAC;IACjB,YAAY,EAAE,GAAG,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACzB;AAMD,eAAO,MAAM,uBAAuB,QAAQ,CAAC;AAS7C;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,IAAI,CAwBpO;AAID;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,yBAAyB,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA0DrK;AAID;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE;IACtJ,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAC/B,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAqBjC"}
1
+ {"version":3,"file":"handleTransformEffects.d.ts","sourceRoot":"","sources":["../../src/utils/handleTransformEffects.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAKtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,MAAM,WAAW,yBAAyB;IACtC,MAAM,EAAE,QAAQ,CAAC;IACjB,YAAY,EAAE,GAAG,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,qIAAqI;IACrI,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,GAAG,SAAS,CAAC;IACrD;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,KAAK,OAAO,CAAC;CACjG;AAMD,eAAO,MAAM,uBAAuB,QAAQ,CAAC;AAS7C;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,IAAI,CAwBpO;AAID;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,yBAAyB,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA2CrK;AAyCD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE;IACtJ,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAC/B,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAqBjC"}
@@ -10,6 +10,7 @@ import { applyEasing, gsapEasingMap } from "./animationsUtils.js";
10
10
  import { applyTransformEffects } from "../utils/frameAnimations.js";
11
11
  import { calculateTimeProgress } from "./materialUtils.js";
12
12
  import { createDefaultEffectState } from "./utils.js";
13
+ import { effectiveScope } from "./animationTargets.js";
13
14
  // =============================================================================
14
15
  // DEBUG
15
16
  // =============================================================================
@@ -64,20 +65,27 @@ export function handleTransformAnimations(ctx, animations, currentTime, hovered,
64
65
  // Without a delta (older callers) one frame, the old per-frame behaviour.
65
66
  const frames = delta === undefined ? 1 : Math.min(Math.max(delta, 0), 0.1) * 60;
66
67
  // console.log("handleTransformAnimations called with animations:", animations);
67
- animations.forEach((animation) => {
68
- const { property, ease, duration, loop = false, to, yoyo = false, active, type = "interpolation", objectIds, triggerMode = "always", } = animation;
68
+ animations.forEach((row, index) => {
69
+ const live = ctx.liveTo?.(index);
70
+ const animation = live ? { ...row, to: live } : row;
71
+ const { property, ease, duration, loop = false, to, yoyo = false, active, type = "interpolation", triggerMode = "always", } = animation;
69
72
  if (!active)
70
73
  return;
71
- if (triggerMode === "hover" && !hovered)
74
+ // Only handle transform properties
75
+ if (property !== "position" && property !== "rotation" && property !== "scale") {
72
76
  return;
73
- // Skip position/rotation if path animation is active (path controls these)
74
- if (pathIsActive &&
75
- (property === "position" || property === "rotation") &&
76
- (!objectIds || objectIds.length === 0)) {
77
+ }
78
+ const scope = ctx.targets ? effectiveScope(object, animation) : 'self';
79
+ if (scope !== 'self') {
80
+ animateTargets(ctx.targets, animation, property, scope, currentTime, hovered, frames, ctx.takenOver);
77
81
  return;
78
82
  }
79
- // Only handle transform properties
80
- if (property !== "position" && property !== "rotation" && property !== "scale") {
83
+ if (ctx.takenOver?.(null, property))
84
+ return; // a clip drives it: the clip has full control
85
+ if (triggerMode === "hover" && !hovered)
86
+ return;
87
+ // Skip position/rotation if path animation is active (path controls these)
88
+ if (pathIsActive && (property === "position" || property === "rotation")) {
81
89
  return;
82
90
  }
83
91
  // Calculate animation progress
@@ -88,32 +96,51 @@ export function handleTransformAnimations(ctx, animations, currentTime, hovered,
88
96
  timeProgress = applyEasing(easingFunctionName, timeProgress);
89
97
  }
90
98
  const animationSpeed = 1000 / duration;
91
- // Determine which objects to animate
92
- const targetsToAnimate = [];
93
- if (objectIds && objectIds.length > 0) {
94
- // Animate specific children by name
95
- // Every match, not getObjectByName's first: a model repeated by
96
- // meshSettings.layout has the same part name once per copy.
97
- const names = new Set(objectIds);
98
- object.traverse((child) => {
99
- if (child.name && names.has(child.name)) {
100
- targetsToAnimate.push(child);
101
- }
102
- });
103
- log("animating specific targets", { objectIds, found: targetsToAnimate.length });
104
- }
105
- else {
106
- // Animate the parent object
107
- targetsToAnimate.push(object);
108
- }
109
99
  // Get start value from meshSettings
110
100
  const start = meshSettings[property];
111
101
  if (!start || !to)
112
102
  return;
113
- // Apply animation to each target
114
- targetsToAnimate.forEach((target) => {
115
- animateTransformProperty(target, property, start, to, timeProgress, type, animationSpeed, frames);
103
+ animateTransformProperty(object, property, start, to, timeProgress, type, animationSpeed, frames);
104
+ });
105
+ }
106
+ /**
107
+ * A row scoped each / targets: every target on its own, `to` relative to the target's rest
108
+ * (position / rotation added, scale multiplied), each target `delay` later (stagger).
109
+ */
110
+ function animateTargets(state, animation, property, scope, currentTime, hovered, frames, takenOver) {
111
+ const { ease, duration, loop = false, to, yoyo = false, type = "interpolation", triggerMode = "always" } = animation;
112
+ const { objs, delays } = state.targetsOf(animation, scope);
113
+ if (triggerMode === "hover" && !hovered) {
114
+ for (const o of objs)
115
+ state.hold(o, 't', property);
116
+ return;
117
+ }
118
+ if (!Array.isArray(to) || to.length < 3)
119
+ return;
120
+ const isScale = property === "scale";
121
+ const animationSpeed = 1000 / duration;
122
+ const easing = ease ? (gsapEasingMap[ease] || ease) : null;
123
+ objs.forEach((target, i) => {
124
+ // a clip drives this target: leave it to the clip (held — not put back to its rest either)
125
+ if (takenOver?.(target, property)) {
126
+ state.hold(target, 't', property);
127
+ return;
128
+ }
129
+ if (type === "keyframe") {
130
+ // Continuous: the same increment per frame on each target (a stagger has nothing to offset).
131
+ state.touchTransform(target, property);
132
+ animateTransformProperty(target, property, [0, 0, 0], to, 0, "keyframe", animationSpeed, frames);
133
+ return;
134
+ }
135
+ let t = calculateTimeProgress(currentTime - delays[i] * 1000, duration, yoyo, loop);
136
+ if (easing)
137
+ t = applyEasing(easing, t);
138
+ const rest = state.transformRest(target, property);
139
+ const v = rest.map((r, k) => {
140
+ const d = Number(to[k]) || 0;
141
+ return isScale ? r * MathUtils.lerp(1, d, t) : r + MathUtils.lerp(0, d, t);
116
142
  });
143
+ state.writeTransform(target, property, v);
117
144
  });
118
145
  }
119
146
  // =============================================================================
@@ -21,6 +21,7 @@ export * from './utils';
21
21
  export * from './edgeUtils';
22
22
  export * from './surfaceUtils';
23
23
  export * from './objectTreeUtils';
24
+ export * from './animationTargets';
24
25
  export * from './handleTransformEffects';
25
26
  export * from './handleEdgesAnimations';
26
27
  export * from './handleLightAnimations';
@@ -47,4 +48,5 @@ export * from './actionKeys';
47
48
  export * from './objectEffects';
48
49
  export * from './cameraControls';
49
50
  export * from './materialVariant';
51
+ export * from './cdnUrls';
50
52
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC"}
@@ -21,6 +21,7 @@ export * from './utils.js';
21
21
  export * from './edgeUtils.js';
22
22
  export * from './surfaceUtils.js';
23
23
  export * from './objectTreeUtils.js';
24
+ export * from './animationTargets.js';
24
25
  export * from './handleTransformEffects.js';
25
26
  export * from './handleEdgesAnimations.js';
26
27
  export * from './handleLightAnimations.js';
@@ -47,3 +48,4 @@ export * from './actionKeys.js';
47
48
  export * from './objectEffects.js';
48
49
  export * from './cameraControls.js';
49
50
  export * from './materialVariant.js';
51
+ export * from './cdnUrls.js';
@@ -17,8 +17,12 @@ export declare const SELF_OWNED_MATERIAL_KEY = "__ownsItsMaterial";
17
17
  * with EDGES_GROUP_KEY, GEREATIVE_EFFECTS_KEY or SELF_OWNED_MATERIAL_KEY — unless obj itself
18
18
  * carries that mark (so an edges group or generative effects group can still
19
19
  * apply its own material via useMaterialApplication).
20
+ * `stopAtObjects`: also stop at the roots of child scene objects — an object's material
21
+ * never reaches the objects parented under it (they apply their own).
20
22
  */
21
- export declare function traverseSkippingEdgesAndEffects(obj: Object3D, isRoot: boolean, callback: (node: Object3D) => void): void;
23
+ export declare function traverseSkippingEdgesAndEffects(obj: Object3D, isRoot: boolean, callback: (node: Object3D) => void, stopAtObjects?: boolean): void;
24
+ /** The root of a scene object (the viewer's AnimatedObject tags it with its id and type). */
25
+ export declare const isSceneObjectRoot: (obj: Object3D) => boolean;
22
26
  /**
23
27
  * Create a fresh material instance for a variant and apply settings to it.
24
28
  */