@3driseai/3drise-viewer 0.1.18 → 0.1.20

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 (62) hide show
  1. package/dist/components/AnimatedObject.d.ts.map +1 -1
  2. package/dist/components/AnimatedObject.js +3 -2
  3. package/dist/components/CreatedObject.d.ts.map +1 -1
  4. package/dist/components/CreatedObject.js +18 -14
  5. package/dist/components/ModelMaterialPassContext.d.ts +7 -0
  6. package/dist/components/ModelMaterialPassContext.d.ts.map +1 -0
  7. package/dist/components/ModelMaterialPassContext.js +7 -0
  8. package/dist/components/ModelPrimitive.d.ts.map +1 -1
  9. package/dist/components/ModelPrimitive.js +7 -1
  10. package/dist/components/ObjectNode.d.ts.map +1 -1
  11. package/dist/components/ObjectNode.js +10 -6
  12. package/dist/components/ParticlesGenerator.d.ts.map +1 -1
  13. package/dist/components/ParticlesGenerator.js +11 -5
  14. package/dist/components/particles/BioluminescentWakeParticles.d.ts +5 -2
  15. package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
  16. package/dist/components/particles/BioluminescentWakeParticles.js +28 -6
  17. package/dist/components/particles/BrownianDustParticles.d.ts +5 -2
  18. package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
  19. package/dist/components/particles/BrownianDustParticles.js +28 -6
  20. package/dist/components/particles/ChladniSandParticles.d.ts +15 -0
  21. package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -0
  22. package/dist/components/particles/ChladniSandParticles.js +912 -0
  23. package/dist/components/particles/ElectronOrbitalsParticles.d.ts +5 -2
  24. package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
  25. package/dist/components/particles/ElectronOrbitalsParticles.js +28 -6
  26. package/dist/components/particles/FaradayPowderParticles.d.ts +15 -0
  27. package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -0
  28. package/dist/components/particles/FaradayPowderParticles.js +783 -0
  29. package/dist/components/particles/GrinderSparksParticles.d.ts +5 -2
  30. package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
  31. package/dist/components/particles/GrinderSparksParticles.js +37 -13
  32. package/dist/components/particles/NeonRainParticles.d.ts +15 -0
  33. package/dist/components/particles/NeonRainParticles.d.ts.map +1 -0
  34. package/dist/components/particles/NeonRainParticles.js +716 -0
  35. package/dist/components/particles/TidalStreamParticles.d.ts +5 -2
  36. package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
  37. package/dist/components/particles/TidalStreamParticles.js +28 -6
  38. package/dist/components/particles/index.d.ts +6 -0
  39. package/dist/components/particles/index.d.ts.map +1 -1
  40. package/dist/components/particles/index.js +3 -0
  41. package/dist/constants.d.ts.map +1 -1
  42. package/dist/constants.js +2 -1
  43. package/dist/hooks/index.d.ts +3 -0
  44. package/dist/hooks/index.d.ts.map +1 -1
  45. package/dist/hooks/index.js +2 -0
  46. package/dist/hooks/useConfigLive.d.ts +54 -0
  47. package/dist/hooks/useConfigLive.d.ts.map +1 -0
  48. package/dist/hooks/useConfigLive.js +100 -0
  49. package/dist/hooks/useMaterialApplication.d.ts.map +1 -1
  50. package/dist/hooks/useMaterialApplication.js +1 -0
  51. package/dist/hooks/useNodeOverrides.d.ts +13 -0
  52. package/dist/hooks/useNodeOverrides.d.ts.map +1 -0
  53. package/dist/hooks/useNodeOverrides.js +254 -0
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -0
  57. package/dist/utils/configAnimations.d.ts +43 -0
  58. package/dist/utils/configAnimations.d.ts.map +1 -0
  59. package/dist/utils/configAnimations.js +73 -0
  60. package/dist/utils/particleTrails.d.ts.map +1 -1
  61. package/dist/utils/particleTrails.js +17 -8
  62. package/package.json +2 -2
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
- import type { AnimationOptions, TidalStreamParticlesConfig } from '@3driseai/3drise-core';
2
+ import type { ObjectAnimations, TidalStreamParticlesConfig } from '@3driseai/3drise-core';
3
3
  import type { ObjectReadyProps } from '../../types/objectReady';
4
4
  export interface TidalStreamParticlesProps extends ObjectReadyProps {
5
+ /** Scene object id: the controller finds the live-edit handle by it. */
6
+ id?: string;
5
7
  /** World size of the object (units); the object's transform scales it further. */
6
8
  size: number;
7
9
  config: TidalStreamParticlesConfig;
8
- animations?: AnimationOptions[];
10
+ /** Per-domain keyframes; the `particles` domain drives this object's config knobs. */
11
+ animations?: ObjectAnimations;
9
12
  }
10
13
  declare const TidalStreamParticles: React.FC<TidalStreamParticlesProps>;
11
14
  export default TidalStreamParticles;
@@ -1 +1 @@
1
- {"version":3,"file":"TidalStreamParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/TidalStreamParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAK1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;IACnC,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AA6sBD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA6B7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"TidalStreamParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/TidalStreamParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAM1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;IACnC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAmtBD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAiD7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
2
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
3
3
  import * as THREE from 'three';
4
4
  import { useFrame } from '@react-three/fiber';
5
- import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
5
+ import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
6
+ import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
6
7
  import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
7
8
  import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
8
9
  import { useObjectReady } from '../../hooks/useObjectReady.js';
10
+ import { useConfigLiveHandle } from '../../hooks/useConfigLive.js';
9
11
  /**
10
12
  * Tidal Stream — A spiral galaxy shredding dwarf satellites into tidal streams: symplectic orbits, density-wave arms, Doppler-shifted blackbody stars; the cursor is a subhalo, black hole or spectroscope. Use for space scenes.
11
13
  */
@@ -762,9 +764,12 @@ const DEFAULTS = {
762
764
  trailColor: '#7fa8ff',
763
765
  trailShare: 0.25,
764
766
  };
765
- const TidalStreamParticles = ({ size, config, animations, onObjectReady, onObjectDispose }) => {
767
+ /** Knobs that rebuild the simulation: they change on commit, never through the live path. */
768
+ const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.tidalStream.map((p) => p.value));
769
+ /** Knobs that rebuild the simulation: they change on commit, never through the live path. */
770
+ const STRUCTURAL = ['particleCount', 'satelliteCount', 'orbitEccentricity', 'streamAge', 'discStars'];
771
+ const TidalStreamParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
766
772
  const rootRef = useRef(null);
767
- const animCache = useRef({});
768
773
  useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
769
774
  const s = size > 0 ? size : 1;
770
775
  const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
@@ -777,11 +782,28 @@ const TidalStreamParticles = ({ size, config, animations, onObjectReady, onObjec
777
782
  applyConfig(uniforms, cfg, s);
778
783
  instance.apply(cfg);
779
784
  }, [instance, uniforms, cfg, s]);
785
+ // Slider drags and keyframe animations are both overrides on the committed config.
786
+ // This is the one place they are merged and pushed, so they compose instead of
787
+ // overwriting each other; the animation wins, because it is the active driver.
788
+ const keyframes = animations?.particles?.animations;
789
+ const animOverride = useRef({});
790
+ const animFrom = useRef({});
791
+ const animStart = useRef(0);
792
+ // Editing a keyframe restarts its cycle rather than dropping the user mid-way.
793
+ useEffect(() => { animStart.current = performance.now(); animFrom.current = {}; }, [keyframes]);
794
+ const push = useCallback((overrides) => {
795
+ const merged = { ...cfg, ...overrides, ...animOverride.current };
796
+ const z = merged.size;
797
+ applyConfig(uniforms, merged, z && z > 0 ? z : s);
798
+ }, [cfg, uniforms, s]);
799
+ const live = useConfigLiveHandle(rootRef, id, 'particles', cfg, STRUCTURAL, push);
780
800
  const pointer = usePointerField();
781
801
  useFrame((state, delta) => {
782
802
  instance.update(Math.min(delta, 0.1), state.camera, pointer);
783
- if (animations?.length)
784
- applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
803
+ if (keyframes?.length) {
804
+ applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
805
+ push(live.current);
806
+ }
785
807
  });
786
808
  return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
787
809
  };
@@ -8,4 +8,10 @@ export { default as ElectronOrbitalsParticles } from './ElectronOrbitalsParticle
8
8
  export type { ElectronOrbitalsParticlesProps } from './ElectronOrbitalsParticles';
9
9
  export { default as GrinderSparksParticles } from './GrinderSparksParticles';
10
10
  export type { GrinderSparksParticlesProps } from './GrinderSparksParticles';
11
+ export { default as ChladniSandParticles } from './ChladniSandParticles';
12
+ export type { ChladniSandParticlesProps } from './ChladniSandParticles';
13
+ export { default as FaradayPowderParticles } from './FaradayPowderParticles';
14
+ export type { FaradayPowderParticlesProps } from './FaradayPowderParticles';
15
+ export { default as NeonRainParticles } from './NeonRainParticles';
16
+ export type { NeonRainParticlesProps } from './NeonRainParticles';
11
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/particles/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/particles/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -4,3 +4,6 @@ export { default as BrownianDustParticles } from './BrownianDustParticles.js';
4
4
  export { default as BioluminescentWakeParticles } from './BioluminescentWakeParticles.js';
5
5
  export { default as ElectronOrbitalsParticles } from './ElectronOrbitalsParticles.js';
6
6
  export { default as GrinderSparksParticles } from './GrinderSparksParticles.js';
7
+ export { default as ChladniSandParticles } from './ChladniSandParticles.js';
8
+ export { default as FaradayPowderParticles } from './FaradayPowderParticles.js';
9
+ export { default as NeonRainParticles } from './NeonRainParticles.js';
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,gBAAgB,oCAA2B,CAAC;AACzD,eAAO,MAAM,qBAAqB,aAchC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,gBAAgB,oCAA2B,CAAC;AACzD,eAAO,MAAM,qBAAqB,aAehC,CAAC"}
package/dist/constants.js CHANGED
@@ -12,10 +12,11 @@ export const SELF_MANAGED_MATERIAL = new Set([
12
12
  'effect',
13
13
  'space',
14
14
  'grid',
15
+ 'particles',
15
16
  // The water surface owns its shader — one material for both hosts, built
16
17
  // around the wave field. Left out of this set, useMaterialApplication
17
18
  // overwrites it with whatever the registry has under materialType, which for
18
19
  // a legacy ocean is the old flat-plane water material.
19
- 'ocean',
20
+ // 'ocean',
20
21
  // 'moon'
21
22
  ]);
@@ -17,6 +17,8 @@ export { useClipmap } from './useClipmap';
17
17
  export type { OceanLiveHandle } from './useOceanLive';
18
18
  export { OCEAN_LIVE_KEY, useOceanLive } from './useOceanLive';
19
19
  export { useOceanMaterial } from './useOceanMaterial';
20
+ export type { ConfigLiveHandle, ConfigLiveValue, ConfigLiveFamily } from './useConfigLive';
21
+ export { CONFIG_LIVE_KEY, useConfigLive, useConfigLiveHandle } from './useConfigLive';
20
22
  export type { UseOceanMaterialOptions, OceanMaterialHandle } from './useOceanMaterial';
21
23
  export type { UseContinuousEffectsOptions, UseContinuousEffectsResult } from './useContinuousEffects';
22
24
  export { useContinuousEffects } from './useContinuousEffects';
@@ -55,6 +57,7 @@ export { useObjectEdges } from './useObjectEdges';
55
57
  export type { ObjectEffectsFunctions, ObjectEffectsState, UseObjectEffectsReturn } from './useObjectEffects';
56
58
  export { useObjectEffects } from './useObjectEffects';
57
59
  export { useModelObject } from './useModelObject';
60
+ export { useNodeOverrides, reconcileNodeOverrides, restoreNodeOverrides } from './useNodeOverrides';
58
61
  export { useObjectReady, useObjectReadyReporter } from './useObjectReady';
59
62
  export type { GridSurfaceOptions, GridSurfaceUniforms } from './useGridSurface';
60
63
  export { useGridSurface } from './useGridSurface';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ export { useTerrainClipmap } from './useTerrainClipmap.js';
13
13
  export { useClipmap } from './useClipmap.js';
14
14
  export { OCEAN_LIVE_KEY, useOceanLive } from './useOceanLive.js';
15
15
  export { useOceanMaterial } from './useOceanMaterial.js';
16
+ export { CONFIG_LIVE_KEY, useConfigLive, useConfigLiveHandle } from './useConfigLive.js';
16
17
  export { useContinuousEffects } from './useContinuousEffects.js';
17
18
  export { useCubeCamera } from './useCubeCamera.js';
18
19
  export { useEnvironmentProbe } from './useEnvironmentProbe.js';
@@ -39,6 +40,7 @@ export { useObjectAnimation } from './useObjectAnimation.js';
39
40
  export { useObjectEdges } from './useObjectEdges.js';
40
41
  export { useObjectEffects } from './useObjectEffects.js';
41
42
  export { useModelObject } from './useModelObject.js';
43
+ export { useNodeOverrides, reconcileNodeOverrides, restoreNodeOverrides } from './useNodeOverrides.js';
42
44
  export { useObjectReady, useObjectReadyReporter } from './useObjectReady.js';
43
45
  export { useGridSurface } from './useGridSurface.js';
44
46
  export { useOrbitControls } from './useOrbitControls.js';
@@ -0,0 +1,54 @@
1
+ import { type MutableRefObject, type RefObject } from 'react';
2
+ import type { Object3D } from 'three';
3
+ /**
4
+ * Live config editing for the generative families (particles now, effects next),
5
+ * the counterpart to `useOceanLive` / `useTerrainLive`.
6
+ *
7
+ * A slider drag fires on every pointer-move. Routing that through Redux hands the
8
+ * component a new config sixty times a second; every continuous knob only touches
9
+ * uniforms, so the controller writes straight into them instead, and Redux only
10
+ * hears about it on slider end.
11
+ *
12
+ * const live = useConfigLive(settings.id);
13
+ * <ParticlesControllerUi onLiveChange={live.set} onSettingsChange={dispatch} ... />
14
+ *
15
+ * The renderer side registers the handle and owns the merge:
16
+ *
17
+ * const liveRef = useConfigLiveHandle(rootRef, id, 'particles', STRUCTURAL, push);
18
+ * // push(overrides) -> applyConfig(uniforms, { ...config, ...overrides, ...animated }, size)
19
+ *
20
+ * Structural knobs rebuild the object, so the handle ignores them: they change on
21
+ * release, through the committed config, exactly once.
22
+ *
23
+ * The handle is found by the object's id, resolved at call time (see the note on
24
+ * useOceanLive: an object resolved once while the panel opens before the object
25
+ * exists stays null forever). It lives on its own userData key and carries its own
26
+ * id, so it never competes with the scene's `userData.id` used by selection.
27
+ */
28
+ /** Where a generative component stashes its handle on its root group's userData. */
29
+ export declare const CONFIG_LIVE_KEY = "__configLive";
30
+ export type ConfigLiveValue = number | string | boolean;
31
+ /** Generative families that support live config editing. */
32
+ export type ConfigLiveFamily = 'particles' | 'effects';
33
+ export interface ConfigLiveHandle {
34
+ id: string;
35
+ family: ConfigLiveFamily;
36
+ /** False once the component has unmounted or re-registered. */
37
+ alive: boolean;
38
+ set(key: string, value: ConfigLiveValue): void;
39
+ }
40
+ /** Client side: a stable `set(key, value)` for the generative object with this id. */
41
+ export declare function useConfigLive(id?: string | null): {
42
+ set(key: string, value: ConfigLiveValue): void;
43
+ };
44
+ /**
45
+ * Renderer side: registers the live handle and returns the ref holding everything
46
+ * dragged since the last commit. `apply` is called with that ref's contents on every
47
+ * live change; the component owns the merge, so keyframe animations compose with it.
48
+ *
49
+ * Pass `config` so a committed config drops the drag state — the commit *is* the
50
+ * same value, and keeping the override would pin the knob against later edits.
51
+ */
52
+ export declare function useConfigLiveHandle(rootRef: RefObject<Object3D | null>, id: string | undefined, family: ConfigLiveFamily, config: object, structural: readonly string[], apply: (overrides: Record<string, ConfigLiveValue>) => void): MutableRefObject<Record<string, ConfigLiveValue>>;
53
+ export default useConfigLive;
54
+ //# sourceMappingURL=useConfigLive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConfigLive.d.ts","sourceRoot":"","sources":["../../src/hooks/useConfigLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAChG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,oFAAoF;AACpF,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAExD,4DAA4D;AAC5D,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,SAAS,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,+DAA+D;IAC/D,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;CAClD;AAgBD,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAAA;CAAE,CAYpG;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC,EACnC,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,IAAI,GAC5D,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CA8BnD;AAED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,100 @@
1
+ import { useLayoutEffect, useMemo, useRef } from 'react';
2
+ import { SceneStore } from '@3driseai/3drise-core';
3
+ /**
4
+ * Live config editing for the generative families (particles now, effects next),
5
+ * the counterpart to `useOceanLive` / `useTerrainLive`.
6
+ *
7
+ * A slider drag fires on every pointer-move. Routing that through Redux hands the
8
+ * component a new config sixty times a second; every continuous knob only touches
9
+ * uniforms, so the controller writes straight into them instead, and Redux only
10
+ * hears about it on slider end.
11
+ *
12
+ * const live = useConfigLive(settings.id);
13
+ * <ParticlesControllerUi onLiveChange={live.set} onSettingsChange={dispatch} ... />
14
+ *
15
+ * The renderer side registers the handle and owns the merge:
16
+ *
17
+ * const liveRef = useConfigLiveHandle(rootRef, id, 'particles', STRUCTURAL, push);
18
+ * // push(overrides) -> applyConfig(uniforms, { ...config, ...overrides, ...animated }, size)
19
+ *
20
+ * Structural knobs rebuild the object, so the handle ignores them: they change on
21
+ * release, through the committed config, exactly once.
22
+ *
23
+ * The handle is found by the object's id, resolved at call time (see the note on
24
+ * useOceanLive: an object resolved once while the panel opens before the object
25
+ * exists stays null forever). It lives on its own userData key and carries its own
26
+ * id, so it never competes with the scene's `userData.id` used by selection.
27
+ */
28
+ /** Where a generative component stashes its handle on its root group's userData. */
29
+ export const CONFIG_LIVE_KEY = '__configLive';
30
+ /** Keys that never go through the live path, whatever the family. */
31
+ const ALWAYS_COMMIT = ['trailMode', 'trailShare', 'enabled', 'type'];
32
+ function findHandle(id) {
33
+ if (!id || !SceneStore.scene)
34
+ return undefined;
35
+ let found;
36
+ SceneStore.scene.traverse((o) => {
37
+ if (found)
38
+ return;
39
+ const h = o.userData?.[CONFIG_LIVE_KEY];
40
+ if (h && h.id === id)
41
+ found = h;
42
+ });
43
+ return found;
44
+ }
45
+ /** Client side: a stable `set(key, value)` for the generative object with this id. */
46
+ export function useConfigLive(id) {
47
+ return useMemo(() => {
48
+ let cached;
49
+ return {
50
+ set: (key, value) => {
51
+ if (!id)
52
+ return;
53
+ // one scene traversal per drag, not per pointer-move
54
+ if (!cached || !cached.alive || cached.id !== id)
55
+ cached = findHandle(id);
56
+ cached?.set(key, value);
57
+ },
58
+ };
59
+ }, [id]);
60
+ }
61
+ /**
62
+ * Renderer side: registers the live handle and returns the ref holding everything
63
+ * dragged since the last commit. `apply` is called with that ref's contents on every
64
+ * live change; the component owns the merge, so keyframe animations compose with it.
65
+ *
66
+ * Pass `config` so a committed config drops the drag state — the commit *is* the
67
+ * same value, and keeping the override would pin the knob against later edits.
68
+ */
69
+ export function useConfigLiveHandle(rootRef, id, family, config, structural, apply) {
70
+ const live = useRef({});
71
+ const applyRef = useRef(apply);
72
+ applyRef.current = apply;
73
+ // the committed config supersedes whatever the drag wrote
74
+ useLayoutEffect(() => { live.current = {}; }, [config]);
75
+ useLayoutEffect(() => {
76
+ const root = rootRef.current;
77
+ if (!root || !id)
78
+ return;
79
+ const skip = new Set([...structural, ...ALWAYS_COMMIT]);
80
+ const handle = {
81
+ id,
82
+ family,
83
+ alive: true,
84
+ set(key, value) {
85
+ if (!handle.alive || skip.has(key))
86
+ return;
87
+ live.current = { ...live.current, [key]: value };
88
+ applyRef.current(live.current);
89
+ },
90
+ };
91
+ root.userData[CONFIG_LIVE_KEY] = handle;
92
+ return () => {
93
+ handle.alive = false;
94
+ if (root.userData[CONFIG_LIVE_KEY] === handle)
95
+ delete root.userData[CONFIG_LIVE_KEY];
96
+ };
97
+ }, [rootRef, id, family, structural]);
98
+ return live;
99
+ }
100
+ export default useConfigLive;
@@ -1 +1 @@
1
- {"version":3,"file":"useMaterialApplication.d.ts","sourceRoot":"","sources":["../../src/hooks/useMaterialApplication.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D,eAAO,MAAM,sBAAsB,GAAI,QAAQ,cAAc,EAAE,kBAAkB,gBAAgB,EAAE,4BAA4B,OAAO,EAAE,YAAY,MAAM,IAAI,KAAG,IAuIhK,CAAC"}
1
+ {"version":3,"file":"useMaterialApplication.d.ts","sourceRoot":"","sources":["../../src/hooks/useMaterialApplication.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D,eAAO,MAAM,sBAAsB,GAAI,QAAQ,cAAc,EAAE,kBAAkB,gBAAgB,EAAE,4BAA4B,OAAO,EAAE,YAAY,MAAM,IAAI,KAAG,IAwIhK,CAAC"}
@@ -93,6 +93,7 @@ export const useMaterialApplication = (source, materialSettings, shouldApply3dri
93
93
  applyMaterialToChild(child, variant, resolvedSettings);
94
94
  }
95
95
  }));
96
+ onApplied?.();
96
97
  return;
97
98
  }
98
99
  traverseSkippingEdgesAndEffects(obj, true, (child) => {
@@ -0,0 +1,13 @@
1
+ import type { Object3D } from 'three';
2
+ import type { NodeOverrides } from '@3driseai/3drise-core';
3
+ export declare function reconcileNodeOverrides(root: Object3D, overrides: NodeOverrides | undefined, master?: Object3D | null): void;
4
+ /** Put every node this root touched back to its original state and free our materials. */
5
+ export declare function restoreNodeOverrides(root: Object3D, master?: Object3D | null): void;
6
+ /**
7
+ * @param object the mounted model clone (one layout slot)
8
+ * @param overrides createdObject.config.nodeOverrides (getNodeOverrides)
9
+ * @param master the cache master scene (peekModelAsset(url)?.scene) — source of originals
10
+ */
11
+ export declare function useNodeOverrides(object: Object3D | null, overrides: NodeOverrides | undefined, master?: Object3D | null): void;
12
+ export default useNodeOverrides;
13
+ //# sourceMappingURL=useNodeOverrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNodeOverrides.d.ts","sourceRoot":"","sources":["../../src/hooks/useNodeOverrides.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,OAAO,CAAC;AAUhD,OAAO,KAAK,EAAwD,aAAa,EAAgB,MAAM,uBAAuB,CAAC;AAiM/H,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CA0C3H;AAED,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAEnF;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,EAAE,SAAS,EAAE,aAAa,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAa9H;AAED,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,254 @@
1
+ // =============================================================================
2
+ // useNodeOverrides — apply per-mesh overrides to a mounted model instance
3
+ // =============================================================================
4
+ //
5
+ // Reconciles `createdObject.config.nodeOverrides` (keyed by node name, or "/i/j" index path for
6
+ // unnamed nodes — see core nodeOverrides.ts) onto one model clone:
7
+ // visibility, local transform, and material (tweak the mesh's own material, or replace it
8
+ // with a full 3drise material).
9
+ //
10
+ // Reconcile, not patch: every run computes the desired state for every node it has ever
11
+ // touched, so removing an override (or one field of it) restores the original value.
12
+ //
13
+ // Originals:
14
+ // - transform / visibility come from the cache master (the never-mounted GLB scene), so
15
+ // a gizmo that moved the live node before the override was saved cannot corrupt them.
16
+ // - material "base" is whatever the model-level pass left on the mesh (GLB material, or
17
+ // the object's materialSettings when `apply` is on). Materials this hook creates are
18
+ // tagged so they are never mistaken for a base.
19
+ //
20
+ import { useContext, useEffect } from 'react';
21
+ import { MaterialRegistryAPI, NODE_OVERRIDE_MATERIAL_KEY, applyMaterialTweak, collectOverrideTargets, createAndApplyMaterial, resolveNodeKey, resolveTextures, } from '@3driseai/3drise-core';
22
+ import { ModelMaterialPassContext } from '../components/ModelMaterialPassContext.js';
23
+ const nodeStates = new WeakMap();
24
+ const touchedByRoot = new WeakMap();
25
+ const stateOf = (node) => {
26
+ let s = nodeStates.get(node);
27
+ if (!s) {
28
+ s = {};
29
+ nodeStates.set(node, s);
30
+ }
31
+ return s;
32
+ };
33
+ const isOwn = (material) => {
34
+ if (!material)
35
+ return false;
36
+ const list = Array.isArray(material) ? material : [material];
37
+ return list.some(m => m?.userData?.[NODE_OVERRIDE_MATERIAL_KEY]);
38
+ };
39
+ const tag = (m) => {
40
+ m.userData[NODE_OVERRIDE_MATERIAL_KEY] = true;
41
+ return m;
42
+ };
43
+ const disposeOwn = (s) => {
44
+ if (s.ownInstance) {
45
+ try {
46
+ MaterialRegistryAPI.releaseMaterial(s.ownInstance);
47
+ }
48
+ catch { /* ignore */ }
49
+ }
50
+ else if (s.ownMaterial) {
51
+ (Array.isArray(s.ownMaterial) ? s.ownMaterial : [s.ownMaterial]).forEach(m => m?.dispose());
52
+ }
53
+ s.ownMaterial = undefined;
54
+ s.ownInstance = undefined;
55
+ s.materialKey = undefined;
56
+ };
57
+ /** Original local transform + visibility for `node`: master first, snapshot fallback. */
58
+ const originalOf = (node) => {
59
+ const m = stateOf(node).masterNode;
60
+ if (m) {
61
+ return {
62
+ position: [m.position.x, m.position.y, m.position.z],
63
+ rotation: [m.rotation.x, m.rotation.y, m.rotation.z],
64
+ scale: [m.scale.x, m.scale.y, m.scale.z],
65
+ visible: m.visible,
66
+ };
67
+ }
68
+ const s = stateOf(node);
69
+ if (!s.snapshot) {
70
+ s.snapshot = {
71
+ position: [node.position.x, node.position.y, node.position.z],
72
+ rotation: [node.rotation.x, node.rotation.y, node.rotation.z],
73
+ scale: [node.scale.x, node.scale.y, node.scale.z],
74
+ visible: node.visible,
75
+ };
76
+ }
77
+ return s.snapshot;
78
+ };
79
+ // ── Transform / visibility ──────────────────────────────────────────────────
80
+ const reconcileTransform = (node, override) => {
81
+ const s = stateOf(node);
82
+ const hasTransform = !!(override?.position || override?.rotation || override?.scale);
83
+ const hasVisible = override?.visible !== undefined;
84
+ if (!hasTransform && !hasVisible && !s.transformTouched && !s.visibleTouched)
85
+ return;
86
+ const orig = originalOf(node);
87
+ if (hasTransform || s.transformTouched) {
88
+ node.position.set(...(override?.position ?? orig.position));
89
+ node.rotation.set(...(override?.rotation ?? orig.rotation));
90
+ node.scale.set(...(override?.scale ?? orig.scale));
91
+ s.transformTouched = hasTransform;
92
+ }
93
+ if (hasVisible || s.visibleTouched) {
94
+ node.visible = override?.visible ?? orig.visible;
95
+ s.visibleTouched = hasVisible;
96
+ }
97
+ };
98
+ // ── Material ────────────────────────────────────────────────────────────────
99
+ const assign = (mesh, material) => {
100
+ mesh.material = material;
101
+ };
102
+ const reconcileMaterial = (mesh, desired) => {
103
+ const current = mesh.material;
104
+ if (!current)
105
+ return;
106
+ const s = stateOf(mesh);
107
+ // A material that isn't ours is a fresh base (GLB, or the model-level pass replaced
108
+ // what we had). Ours still on the mesh means the stored base is still valid.
109
+ if (!isOwn(current)) {
110
+ if (s.ownMaterial || s.ownInstance) {
111
+ // Our material was swapped out from under us — release it, it's orphaned.
112
+ disposeOwn(s);
113
+ }
114
+ s.baseMaterial = current;
115
+ }
116
+ const base = s.baseMaterial ?? current;
117
+ if (!desired) {
118
+ if (isOwn(current)) {
119
+ assign(mesh, base);
120
+ disposeOwn(s);
121
+ }
122
+ s.token = (s.token ?? 0) + 1; // cancel any in-flight replace
123
+ return;
124
+ }
125
+ const key = JSON.stringify(desired.mode === 'replace' ? { r: desired.settings } : { t: desired.tweak });
126
+ if (isOwn(current) && s.materialKey === key)
127
+ return; // already up to date
128
+ const token = (s.token ?? 0) + 1;
129
+ s.token = token;
130
+ if (desired.mode === 'tweak') {
131
+ const make = (m) => {
132
+ const copy = tag(m.clone());
133
+ applyMaterialTweak(copy, desired.tweak);
134
+ return copy;
135
+ };
136
+ const next = Array.isArray(base) ? base.map(make) : make(base);
137
+ const prev = { ownMaterial: s.ownMaterial, ownInstance: s.ownInstance };
138
+ assign(mesh, next);
139
+ disposeOwn({ ...prev });
140
+ s.ownMaterial = next;
141
+ s.ownInstance = undefined;
142
+ s.materialKey = key;
143
+ return;
144
+ }
145
+ // replace — full 3drise material
146
+ const settings = desired.settings;
147
+ if (!settings) {
148
+ if (isOwn(current)) {
149
+ assign(mesh, base);
150
+ disposeOwn(s);
151
+ }
152
+ return;
153
+ }
154
+ const typeDef = MaterialRegistryAPI.getTypeDefinition(settings.materialType);
155
+ if (!typeDef)
156
+ return;
157
+ const variant = settings.materialVariant || Object.keys(typeDef.variants)[0];
158
+ resolveTextures(variant, settings).then(resolved => {
159
+ if (stateOf(mesh).token !== token)
160
+ return; // superseded
161
+ let instance;
162
+ try {
163
+ instance = createAndApplyMaterial(variant, resolved);
164
+ }
165
+ catch {
166
+ return;
167
+ }
168
+ tag(instance.material);
169
+ const prev = { ownMaterial: s.ownMaterial, ownInstance: s.ownInstance };
170
+ assign(mesh, instance.material);
171
+ disposeOwn({ ...prev });
172
+ s.ownInstance = instance;
173
+ s.ownMaterial = instance.material;
174
+ s.materialKey = key;
175
+ });
176
+ };
177
+ /** A named node's material targets: itself if it has one, else every descendant mesh. */
178
+ const materialTargetsOf = (node) => {
179
+ if (node.material)
180
+ return [node];
181
+ const out = [];
182
+ node.traverse(child => {
183
+ if (child !== node && child.material)
184
+ out.push(child);
185
+ });
186
+ return out;
187
+ };
188
+ // ── Reconcile / restore ─────────────────────────────────────────────────────
189
+ export function reconcileNodeOverrides(root, overrides, master) {
190
+ let touched = touchedByRoot.get(root);
191
+ if (!touched) {
192
+ touched = new Set();
193
+ touchedByRoot.set(root, touched);
194
+ }
195
+ const targets = collectOverrideTargets(root, overrides);
196
+ const desiredNode = new Map();
197
+ const desiredMaterial = new Map();
198
+ // Parents come first in traversal order, so a child's own material override wins
199
+ // over one set on its group.
200
+ targets.forEach((nodes, key) => {
201
+ const override = overrides[key];
202
+ // Resolve the master counterpart once per node, by the same key.
203
+ const masterNodes = master ? resolveNodeKey(master, key) : [];
204
+ nodes.forEach((node, i) => {
205
+ const s = stateOf(node);
206
+ if (master && s.masterNode === undefined)
207
+ s.masterNode = masterNodes[i] ?? masterNodes[0] ?? null;
208
+ desiredNode.set(node, override);
209
+ if (override.material) {
210
+ materialTargetsOf(node).forEach(mesh => desiredMaterial.set(mesh, override.material));
211
+ }
212
+ });
213
+ });
214
+ const all = new Set([...touched, ...desiredNode.keys(), ...desiredMaterial.keys()]);
215
+ all.forEach(node => {
216
+ reconcileTransform(node, desiredNode.get(node));
217
+ if (node.material)
218
+ reconcileMaterial(node, desiredMaterial.get(node));
219
+ });
220
+ // Keep tracking only what still differs from the original.
221
+ const stillTouched = new Set();
222
+ all.forEach(node => {
223
+ const s = nodeStates.get(node);
224
+ if (s && (s.transformTouched || s.visibleTouched || s.ownMaterial || s.ownInstance || desiredMaterial.has(node))) {
225
+ stillTouched.add(node);
226
+ }
227
+ });
228
+ touchedByRoot.set(root, stillTouched);
229
+ }
230
+ /** Put every node this root touched back to its original state and free our materials. */
231
+ export function restoreNodeOverrides(root, master) {
232
+ reconcileNodeOverrides(root, {}, master);
233
+ }
234
+ // ── Hook ────────────────────────────────────────────────────────────────────
235
+ /**
236
+ * @param object the mounted model clone (one layout slot)
237
+ * @param overrides createdObject.config.nodeOverrides (getNodeOverrides)
238
+ * @param master the cache master scene (peekModelAsset(url)?.scene) — source of originals
239
+ */
240
+ export function useNodeOverrides(object, overrides, master) {
241
+ const materialPass = useContext(ModelMaterialPassContext);
242
+ useEffect(() => {
243
+ if (!object)
244
+ return;
245
+ reconcileNodeOverrides(object, overrides, master);
246
+ }, [object, overrides, master, materialPass]);
247
+ // Unmount / instance swap: leave the instance as the GLB made it.
248
+ useEffect(() => {
249
+ if (!object)
250
+ return;
251
+ return () => restoreNodeOverrides(object, master);
252
+ }, [object, master]);
253
+ }
254
+ export default useNodeOverrides;
package/dist/index.d.ts CHANGED
@@ -30,6 +30,8 @@ export { applyOceanUniforms, buildWaveTable, seaState } from './utils/oceanUnifo
30
30
  export type { WaveTable } from './utils/oceanUniforms';
31
31
  export { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, applyThunderUniforms, } from './utils/cloudUniforms';
32
32
  export { applyCloudsKeyframeAnimations, applyCloudsMouseMove, resolveDeckConfig, } from './utils/handleCloudsAnimations';
33
+ export { applyConfigKeyframeAnimations, applyConfigMouseMove, hasConfigOverrides, resolveConfig, } from './utils/configAnimations';
34
+ export type { ConfigOverride, ConfigStartValues } from './utils/configAnimations';
33
35
  export { volumetricCloudPresets, cirrusPresets, getCloudPresetsForType, getCloudPresetByName, getCirrusPresetByName, searchCloudPresetsByTags, } from './presets/volumetricCloudPresets';
34
36
  export type { VolumetricCloudPreset, CirrusPreset } from './presets/volumetricCloudPresets';
35
37
  export { terrainPresets, getTerrainPresetsForType, getTerrainPresetByName, searchTerrainPresetsByTags, } from './presets/terrainPresets';