@3driseai/3drise-viewer 0.1.18 → 0.1.19

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 (37) hide show
  1. package/dist/components/AnimatedObject.d.ts.map +1 -1
  2. package/dist/components/AnimatedObject.js +3 -2
  3. package/dist/components/ObjectNode.d.ts.map +1 -1
  4. package/dist/components/ObjectNode.js +10 -6
  5. package/dist/components/ParticlesGenerator.js +5 -5
  6. package/dist/components/particles/BioluminescentWakeParticles.d.ts +5 -2
  7. package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
  8. package/dist/components/particles/BioluminescentWakeParticles.js +28 -6
  9. package/dist/components/particles/BrownianDustParticles.d.ts +5 -2
  10. package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
  11. package/dist/components/particles/BrownianDustParticles.js +28 -6
  12. package/dist/components/particles/ElectronOrbitalsParticles.d.ts +5 -2
  13. package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
  14. package/dist/components/particles/ElectronOrbitalsParticles.js +28 -6
  15. package/dist/components/particles/GrinderSparksParticles.d.ts +5 -2
  16. package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
  17. package/dist/components/particles/GrinderSparksParticles.js +37 -13
  18. package/dist/components/particles/TidalStreamParticles.d.ts +5 -2
  19. package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
  20. package/dist/components/particles/TidalStreamParticles.js +28 -6
  21. package/dist/constants.d.ts.map +1 -1
  22. package/dist/constants.js +2 -1
  23. package/dist/hooks/index.d.ts +2 -0
  24. package/dist/hooks/index.d.ts.map +1 -1
  25. package/dist/hooks/index.js +1 -0
  26. package/dist/hooks/useConfigLive.d.ts +54 -0
  27. package/dist/hooks/useConfigLive.d.ts.map +1 -0
  28. package/dist/hooks/useConfigLive.js +100 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/utils/configAnimations.d.ts +43 -0
  33. package/dist/utils/configAnimations.d.ts.map +1 -0
  34. package/dist/utils/configAnimations.js +73 -0
  35. package/dist/utils/particleTrails.d.ts.map +1 -1
  36. package/dist/utils/particleTrails.js +17 -8
  37. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"AnimatedObject.d.ts","sourceRoot":"","sources":["../../src/components/AnimatedObject.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAM7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACtG,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,iBAAiB,CAAC;IACtC,aAAa,EAAE,qBAAqB,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACtC,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IACjH,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACvD,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACvD,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAGD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAiEvD,CAAC;AAEH,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"AnimatedObject.d.ts","sourceRoot":"","sources":["../../src/components/AnimatedObject.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAM7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACtG,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,iBAAiB,CAAC;IACtC,aAAa,EAAE,qBAAqB,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACtC,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IACjH,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACvD,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACvD,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAGD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAqFvD,CAAC;AAEH,eAAe,cAAc,CAAC"}
@@ -64,12 +64,13 @@ visible = true, children, parentRef, handleObjectClick, onPointerEnter, onPointe
64
64
  }
65
65
  };
66
66
  if (createdObject.type === 'particles' || createdObject.type === 'gallery' || (!hasHoverFunctionalities && !hasMouseInteraction)) {
67
- return _jsx(_Fragment, { children: _jsxs("group", { ref: groupRef, position: position, scale: scale, rotation: rotation, visible: visible, onClick: onClick,
67
+ return _jsx(_Fragment, { children: _jsxs("group", { ref: groupRef, position: position, scale: scale, rotation: rotation, visible: visible,
68
+ // onClick={onClick}
68
69
  // onPointerEnter={onPointerEnter}
69
70
  // onPointerLeave={onPointerLeave}
70
71
  name: createdObject.meshSettings.name, userData: { id: createdObject.id }, children: [children, _jsx(EffectsGroup, { createdObject: createdObject, objectRef: groupRef, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose })] }) });
71
72
  }
72
- return (_jsx(_Fragment, { children: _jsxs("group", { ref: groupRef, position: position, scale: scale, rotation: rotation, visible: visible, onClick: onClick, onPointerEnter: onPointerEnter, onPointerLeave: onPointerLeave, name: createdObject.meshSettings.name, userData: { id: createdObject.id }, children: [children, _jsx(EffectsGroup, { createdObject: createdObject, objectRef: groupRef, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose })] }) }));
73
+ return (_jsxs("group", { ref: groupRef, position: position, scale: scale, rotation: rotation, visible: visible, onClick: onClick, onPointerEnter: onPointerEnter, onPointerLeave: onPointerLeave, name: createdObject.meshSettings.name, userData: { id: createdObject.id }, children: [children, _jsx(EffectsGroup, { createdObject: createdObject, objectRef: groupRef, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose })] }));
73
74
  });
74
75
  AnimatedObject.displayName = 'AnimatedObject';
75
76
  export default AnimatedObject;
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectNode.d.ts","sourceRoot":"","sources":["../../src/components/ObjectNode.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAe,qBAAqB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAiBrD,KAAK,eAAe,GAAG,gBAAgB,GAAG;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CAC7G,CAAC;AAkBF,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAqIzC,CAAC;AACF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"ObjectNode.d.ts","sourceRoot":"","sources":["../../src/components/ObjectNode.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAe,qBAAqB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAiBrD,KAAK,eAAe,GAAG,gBAAgB,GAAG;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CAC7G,CAAC;AAkBF,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAsIzC,CAAC;AACF,eAAe,UAAU,CAAC"}
@@ -8,8 +8,6 @@ import PathRenderer from './PathRenderer.js';
8
8
  import GalleryLayout from './GalleryLayout.js';
9
9
  import EffectsGenerator from './EffectsGenerator.js';
10
10
  import ParticlesGenerator from './ParticlesGenerator.js';
11
- import ParticlesR3f from "./ParticlesR3f.js";
12
- import Lines from "./Lines.js";
13
11
  import LightObject from "./LightObject.js";
14
12
  import EnvironmentObject from "./EnvironmentObject.js";
15
13
  import Galaxy from "./Galaxy.js";
@@ -95,10 +93,16 @@ const ObjectNode = ({ node, onObjectReady, onObjectDispose, handleObjectClick })
95
93
  particles: (obj, onObjectReady) => {
96
94
  // Generative particle objects (config.type) render through ParticlesGenerator;
97
95
  // the legacy shape particles keep their own path.
98
- if (isGenerativeParticlesConfig(obj.config))
99
- return (_jsx(ParticlesGenerator, { settings: { config: obj.config }, ...relabel(obj) }, obj.id));
100
- const particlesSettings = obj;
101
- return (_jsxs(_Fragment, { children: [particlesSettings.config?.particles && (_jsx(ParticlesR3f, { particlesSettings: particlesSettings, duration: 1, transitionSettings: particlesSettings.meshSettings, ...relabel(obj) })), particlesSettings.config?.paths && (_jsx(Lines, { createdObject: particlesSettings, ...relabel(obj) }))] }));
96
+ return (_jsx(ParticlesGenerator, { settings: { id: obj.id, config: obj.config, animations: obj.animations }, ...relabel(obj) }, obj.id));
97
+ // if (isGenerativeParticlesConfig(obj.config))
98
+ // return (<ParticlesGenerator key={obj.id} settings={{ config: obj.config }} {...relabel(obj)} />);
99
+ // const particlesSettings = obj;
100
+ // return (<>
101
+ // {particlesSettings.config?.particles && (
102
+ // <ParticlesR3f particlesSettings={particlesSettings} duration={1} transitionSettings={particlesSettings.meshSettings} {...relabel(obj)} />
103
+ // )}
104
+ // {particlesSettings.config?.paths && (<Lines createdObject={particlesSettings} {...relabel(obj)} />)}
105
+ // </>);
102
106
  },
103
107
  path: (obj, onObjectReady) => (_jsx(PathRenderer, { pathSettings: obj.config, ...relabel(obj) }, obj.id)),
104
108
  grid: (obj, onObjectReady) => (
@@ -12,11 +12,11 @@ const ParticlesGenerator = ({ settings, onObjectReady, onObjectDispose }) => {
12
12
  const size = config.size ?? 1;
13
13
  const ready = { onObjectReady, onObjectDispose };
14
14
  switch (config.type) {
15
- case 'tidalStream': return _jsx(TidalStreamParticles, { size: size, config: config, animations: animations, ...ready });
16
- case 'brownianDust': return _jsx(BrownianDustParticles, { size: size, config: config, animations: animations, ...ready });
17
- case 'bioluminescentWake': return _jsx(BioluminescentWakeParticles, { size: size, config: config, animations: animations, ...ready });
18
- case 'electronOrbitals': return _jsx(ElectronOrbitalsParticles, { size: size, config: config, animations: animations, ...ready });
19
- case 'grinderSparks': return _jsx(GrinderSparksParticles, { size: size, config: config, animations: animations, ...ready });
15
+ case 'tidalStream': return _jsx(TidalStreamParticles, { id: settings.id, size: size, config: config, animations: animations, ...ready });
16
+ case 'brownianDust': return _jsx(BrownianDustParticles, { id: settings.id, size: size, config: config, animations: animations, ...ready });
17
+ case 'bioluminescentWake': return _jsx(BioluminescentWakeParticles, { id: settings.id, size: size, config: config, animations: animations, ...ready });
18
+ case 'electronOrbitals': return _jsx(ElectronOrbitalsParticles, { id: settings.id, size: size, config: config, animations: animations, ...ready });
19
+ case 'grinderSparks': return _jsx(GrinderSparksParticles, { id: settings.id, size: size, config: config, animations: animations, ...ready });
20
20
  default: return null;
21
21
  }
22
22
  };
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
- import type { AnimationOptions, BioluminescentWakeParticlesConfig } from '@3driseai/3drise-core';
2
+ import type { ObjectAnimations, BioluminescentWakeParticlesConfig } from '@3driseai/3drise-core';
3
3
  import type { ObjectReadyProps } from '../../types/objectReady';
4
4
  export interface BioluminescentWakeParticlesProps 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: BioluminescentWakeParticlesConfig;
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 BioluminescentWakeParticles: React.FC<BioluminescentWakeParticlesProps>;
11
14
  export default BioluminescentWakeParticles;
@@ -1 +1 @@
1
- {"version":3,"file":"BioluminescentWakeParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BioluminescentWakeParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAKjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,gCAAiC,SAAQ,gBAAgB;IACtE,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iCAAiC,CAAC;IAC1C,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAo9BD,QAAA,MAAM,2BAA2B,EAAE,KAAK,CAAC,EAAE,CAAC,gCAAgC,CA6B3E,CAAC;AAEF,eAAe,2BAA2B,CAAC"}
1
+ {"version":3,"file":"BioluminescentWakeParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BioluminescentWakeParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAMjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,gCAAiC,SAAQ,gBAAgB;IACtE,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iCAAiC,CAAC;IAC1C,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA09BD,QAAA,MAAM,2BAA2B,EAAE,KAAK,CAAC,EAAE,CAAC,gCAAgC,CAiD3E,CAAC;AAEF,eAAe,2BAA2B,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
  * Sea Sparkle — Bioluminescent plankton that flash when the water shears: swell orbits, excitable cells that tire and recover, moon glitter; the cursor is a hand, falling pebbles or a hunting fish. Use for night sea and fantasy scenes.
11
13
  */
@@ -1097,9 +1099,12 @@ const DEFAULTS = {
1097
1099
  trailColor: '#2f7dff',
1098
1100
  trailShare: 1,
1099
1101
  };
1100
- const BioluminescentWakeParticles = ({ size, config, animations, onObjectReady, onObjectDispose }) => {
1102
+ /** Knobs that rebuild the simulation: they change on commit, never through the live path. */
1103
+ const STRUCTURAL = ['particleCount', 'seed', 'quality'];
1104
+ /** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
1105
+ const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.bioluminescentWake.map((p) => p.value));
1106
+ const BioluminescentWakeParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
1101
1107
  const rootRef = useRef(null);
1102
- const animCache = useRef({});
1103
1108
  useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
1104
1109
  const s = size > 0 ? size : 1;
1105
1110
  const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
@@ -1112,11 +1117,28 @@ const BioluminescentWakeParticles = ({ size, config, animations, onObjectReady,
1112
1117
  applyConfig(uniforms, cfg, s);
1113
1118
  instance.apply(cfg);
1114
1119
  }, [instance, uniforms, cfg, s]);
1120
+ // Slider drags and keyframe animations are both overrides on the committed config.
1121
+ // This is the one place they are merged and pushed, so they compose instead of
1122
+ // overwriting each other; the animation wins, because it is the active driver.
1123
+ const keyframes = animations?.particles?.animations;
1124
+ const animOverride = useRef({});
1125
+ const animFrom = useRef({});
1126
+ const animStart = useRef(0);
1127
+ // Editing a keyframe restarts its cycle rather than dropping the user mid-way.
1128
+ useEffect(() => { animStart.current = performance.now(); animFrom.current = {}; }, [keyframes]);
1129
+ const push = useCallback((overrides) => {
1130
+ const merged = { ...cfg, ...overrides, ...animOverride.current };
1131
+ const z = merged.size;
1132
+ applyConfig(uniforms, merged, z && z > 0 ? z : s);
1133
+ }, [cfg, uniforms, s]);
1134
+ const live = useConfigLiveHandle(rootRef, id, 'particles', cfg, STRUCTURAL, push);
1115
1135
  const pointer = usePointerField();
1116
1136
  useFrame((state, delta) => {
1117
1137
  instance.update(Math.min(delta, 0.1), state.camera, pointer);
1118
- if (animations?.length)
1119
- applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
1138
+ if (keyframes?.length) {
1139
+ applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
1140
+ push(live.current);
1141
+ }
1120
1142
  });
1121
1143
  return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
1122
1144
  };
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
- import type { AnimationOptions, BrownianDustParticlesConfig } from '@3driseai/3drise-core';
2
+ import type { ObjectAnimations, BrownianDustParticlesConfig } from '@3driseai/3drise-core';
3
3
  import type { ObjectReadyProps } from '../../types/objectReady';
4
4
  export interface BrownianDustParticlesProps 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: BrownianDustParticlesConfig;
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 BrownianDustParticles: React.FC<BrownianDustParticlesProps>;
11
14
  export default BrownianDustParticles;
@@ -1 +1 @@
1
- {"version":3,"file":"BrownianDustParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BrownianDustParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAK3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,0BAA2B,SAAQ,gBAAgB;IAChE,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,2BAA2B,CAAC;IACpC,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAsqBD,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CA6B/D,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"BrownianDustParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BrownianDustParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAM3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,0BAA2B,SAAQ,gBAAgB;IAChE,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,2BAA2B,CAAC;IACpC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA4qBD,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAiD/D,CAAC;AAEF,eAAe,qBAAqB,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
  * Sunbeam Dust — Dust drifting in window light: Langevin motion in Kolmogorov turbulence, mullion shadows, forward scattering, glints and bokeh; the cursor is a hand, a torch or a charged rod. Use for interiors and cinematic scenes.
11
13
  */
@@ -731,9 +733,12 @@ const DEFAULTS = {
731
733
  trailColor: '#ffb870',
732
734
  trailShare: 0.3,
733
735
  };
734
- const BrownianDustParticles = ({ size, config, animations, onObjectReady, onObjectDispose }) => {
736
+ /** Knobs that rebuild the simulation: they change on commit, never through the live path. */
737
+ const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.brownianDust.map((p) => p.value));
738
+ /** Knobs that rebuild the simulation: they change on commit, never through the live path. */
739
+ const STRUCTURAL = ['particleCount'];
740
+ const BrownianDustParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
735
741
  const rootRef = useRef(null);
736
- const animCache = useRef({});
737
742
  useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
738
743
  const s = size > 0 ? size : 1;
739
744
  const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
@@ -746,11 +751,28 @@ const BrownianDustParticles = ({ size, config, animations, onObjectReady, onObje
746
751
  applyConfig(uniforms, cfg, s);
747
752
  instance.apply(cfg);
748
753
  }, [instance, uniforms, cfg, s]);
754
+ // Slider drags and keyframe animations are both overrides on the committed config.
755
+ // This is the one place they are merged and pushed, so they compose instead of
756
+ // overwriting each other; the animation wins, because it is the active driver.
757
+ const keyframes = animations?.particles?.animations;
758
+ const animOverride = useRef({});
759
+ const animFrom = useRef({});
760
+ const animStart = useRef(0);
761
+ // Editing a keyframe restarts its cycle rather than dropping the user mid-way.
762
+ useEffect(() => { animStart.current = performance.now(); animFrom.current = {}; }, [keyframes]);
763
+ const push = useCallback((overrides) => {
764
+ const merged = { ...cfg, ...overrides, ...animOverride.current };
765
+ const z = merged.size;
766
+ applyConfig(uniforms, merged, z && z > 0 ? z : s);
767
+ }, [cfg, uniforms, s]);
768
+ const live = useConfigLiveHandle(rootRef, id, 'particles', cfg, STRUCTURAL, push);
749
769
  const pointer = usePointerField();
750
770
  useFrame((state, delta) => {
751
771
  instance.update(Math.min(delta, 0.1), state.camera, pointer);
752
- if (animations?.length)
753
- applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
772
+ if (keyframes?.length) {
773
+ applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
774
+ push(live.current);
775
+ }
754
776
  });
755
777
  return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
756
778
  };
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
- import type { AnimationOptions, ElectronOrbitalsParticlesConfig } from '@3driseai/3drise-core';
2
+ import type { ObjectAnimations, ElectronOrbitalsParticlesConfig } from '@3driseai/3drise-core';
3
3
  import type { ObjectReadyProps } from '../../types/objectReady';
4
4
  export interface ElectronOrbitalsParticlesProps 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: ElectronOrbitalsParticlesConfig;
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 ElectronOrbitalsParticles: React.FC<ElectronOrbitalsParticlesProps>;
11
14
  export default ElectronOrbitalsParticles;
@@ -1 +1 @@
1
- {"version":3,"file":"ElectronOrbitalsParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/ElectronOrbitalsParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AAK/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACpE,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,+BAA+B,CAAC;IACxC,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAoxBD,QAAA,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAAC,8BAA8B,CA6BvE,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
1
+ {"version":3,"file":"ElectronOrbitalsParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/ElectronOrbitalsParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AAM/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACpE,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,+BAA+B,CAAC;IACxC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA0xBD,QAAA,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAAC,8BAA8B,CAiDvE,CAAC;AAEF,eAAe,yBAAyB,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
  * Quantum Orbitals — A hydrogen electron cloud as Bohmian trajectories: a superposition of two orbitals breathing at the Bohr frequency, coloured by quantum phase; the cursor is a photon, a magnet or a detector. Use for science and tech scenes.
11
13
  */
@@ -918,9 +920,12 @@ const DEFAULTS = {
918
920
  trailColor: '#7a8cff',
919
921
  trailShare: 0.3,
920
922
  };
921
- const ElectronOrbitalsParticles = ({ size, config, animations, onObjectReady, onObjectDispose }) => {
923
+ /** Knobs that rebuild the simulation: they change on commit, never through the live path. */
924
+ const STRUCTURAL = ['particleCount', 'stateA', 'stateB', 'seed', 'quality'];
925
+ /** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
926
+ const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.electronOrbitals.map((p) => p.value));
927
+ const ElectronOrbitalsParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
922
928
  const rootRef = useRef(null);
923
- const animCache = useRef({});
924
929
  useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
925
930
  const s = size > 0 ? size : 1;
926
931
  const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
@@ -933,11 +938,28 @@ const ElectronOrbitalsParticles = ({ size, config, animations, onObjectReady, on
933
938
  applyConfig(uniforms, cfg, s);
934
939
  instance.apply(cfg);
935
940
  }, [instance, uniforms, cfg, s]);
941
+ // Slider drags and keyframe animations are both overrides on the committed config.
942
+ // This is the one place they are merged and pushed, so they compose instead of
943
+ // overwriting each other; the animation wins, because it is the active driver.
944
+ const keyframes = animations?.particles?.animations;
945
+ const animOverride = useRef({});
946
+ const animFrom = useRef({});
947
+ const animStart = useRef(0);
948
+ // Editing a keyframe restarts its cycle rather than dropping the user mid-way.
949
+ useEffect(() => { animStart.current = performance.now(); animFrom.current = {}; }, [keyframes]);
950
+ const push = useCallback((overrides) => {
951
+ const merged = { ...cfg, ...overrides, ...animOverride.current };
952
+ const z = merged.size;
953
+ applyConfig(uniforms, merged, z && z > 0 ? z : s);
954
+ }, [cfg, uniforms, s]);
955
+ const live = useConfigLiveHandle(rootRef, id, 'particles', cfg, STRUCTURAL, push);
936
956
  const pointer = usePointerField();
937
957
  useFrame((state, delta) => {
938
958
  instance.update(Math.min(delta, 0.1), state.camera, pointer);
939
- if (animations?.length)
940
- applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
959
+ if (keyframes?.length) {
960
+ applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
961
+ push(live.current);
962
+ }
941
963
  });
942
964
  return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
943
965
  };
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
- import type { AnimationOptions, GrinderSparksParticlesConfig } from '@3driseai/3drise-core';
2
+ import type { ObjectAnimations, GrinderSparksParticlesConfig } from '@3driseai/3drise-core';
3
3
  import type { ObjectReadyProps } from '../../types/objectReady';
4
4
  export interface GrinderSparksParticlesProps 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: GrinderSparksParticlesConfig;
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 GrinderSparksParticles: React.FC<GrinderSparksParticlesProps>;
11
14
  export default GrinderSparksParticles;
@@ -1 +1 @@
1
- {"version":3,"file":"GrinderSparksParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/GrinderSparksParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAK5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACjE,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,4BAA4B,CAAC;IACrC,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAspBD,QAAA,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CA6BjE,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"GrinderSparksParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/GrinderSparksParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAM5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACjE,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,4BAA4B,CAAC;IACrC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA8pBD,QAAA,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAiDjE,CAAC;AAEF,eAAe,sBAAsB,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
  * Grinder Sparks — Slow-motion grinder sparks: drag-limited ballistics, radiative cooling on the blackbody scale, carbon bursts, floor bounces and reflections; the cursor moves the grinder, aims the spray or sweeps the air. Use for industrial and cinematic scenes.
11
13
  */
@@ -133,9 +135,11 @@ const SLOWMO = 0.2; // physics seconds per shown second (a high-speed camera)
133
135
  const H_STEP = 1 / 120; // shown-time step (physics step = H_STEP * SLOWMO)
134
136
  const TR = 0.006; // physics seconds per trail unit (the shutter) for streaks
135
137
  const TR_MODE = { streak: TR, comet: 0.03, echo: 0.04 }; // arcs and smoulder look back further
136
- const C0 = [-0.8, 0.95, 0]; // contact point at rest (m)
137
- const SPRAY_MID = [0.1, 0.6, 0];
138
- const BOUNDS = { center: [0.2, 0.7, 0], radius: 2.3 };
138
+ // The rig is laid out so the spray (wheel on the left, sparks flying right) straddles the object origin:
139
+ // the editor's selection box and gizmo centre on the visible spray, not on the wheel.
140
+ const C0 = [-1.3, 0.95, 0]; // contact point at rest (m)
141
+ const SPRAY_MID = [-0.4, 0.6, 0];
142
+ const BOUNDS = { center: [-0.2, 0.7, 0], radius: 2.3 };
139
143
  const RHO_C = 7800 * 600; // steel density x heat capacity
140
144
  const SIGMA = 5.67e-8, EPS = 0.8, T_AIR = 300;
141
145
  const EL0 = -0.26; // rest elevation of the fan (rad)
@@ -223,7 +227,7 @@ void main() {
223
227
  vec4 mv = modelViewMatrix * vec4(p * uSize, 1.0);
224
228
  float lum = dot(L, vec3(0.3, 0.5, 0.2));
225
229
  float px = uSize * uViewH * (0.0045 + 0.004 * aD) * (1.0 + 0.8 * min(lum, 2.0)) / -mv.z;
226
- ${mirror ? 'px *= 1.6; float fade = uFloorReflect * 0.45 * exp(-position.y * 1.2) * smoothstep(3.0, 1.5, length(position.xz - vec2(0.4, 0.0)));' : 'float fade = 1.0;'}
230
+ ${mirror ? 'px *= 1.6; float fade = uFloorReflect * 0.45 * exp(-position.y * 1.2) * smoothstep(3.0, 1.5, length(position.xz - vec2(-0.1, 0.0)));' : 'float fade = 1.0;'}
227
231
  vCol = L * uIntensity;
228
232
  vA = uOpacity * fade * step(600.0, aT);
229
233
  gl_PointSize = clamp(px, 1.0, 40.0) * step(0.001, vA);
@@ -263,7 +267,7 @@ void main() {
263
267
  float r2 = dot(d, d);
264
268
  float E = uContact.w * max(d.y, 0.0) / pow(r2 + 0.02, 1.5);
265
269
  vec3 c = blackbody(2100.0); c /= max(max(c.r, c.g), c.b);
266
- float edge = smoothstep(2.8, 1.8, length(vP.xz - vec2(0.3, 0.0)));
270
+ float edge = smoothstep(2.8, 1.8, length(vP.xz - vec2(-0.2, 0.0)));
267
271
  vec3 rgb = c * uColor * E * 0.14 * uIntensity * edge;
268
272
  float I = max(max(rgb.r, rgb.g), rgb.b);
269
273
  if (I < 0.001) discard;
@@ -538,7 +542,7 @@ function build(u, c, size) {
538
542
  vel[j] = vx;
539
543
  vel[j + 1] = vy;
540
544
  vel[j + 2] = vz;
541
- if (t < 700 || age[i] > 6 || x > 3.5 || x < -2.5 || Math.abs(z) > 3 || y > 3.5) {
545
+ if (t < 700 || age[i] > 6 || x > 3.0 || x < -3.0 || Math.abs(z) > 3 || y > 3.5) {
542
546
  st[i] = 2;
543
547
  hold[i] = 0.05;
544
548
  T[i] = 0;
@@ -571,7 +575,7 @@ function build(u, c, size) {
571
575
  mirror.frustumCulled = false;
572
576
  const fgeo = new THREE.CircleGeometry(2.8, 64);
573
577
  fgeo.rotateX(-Math.PI / 2);
574
- fgeo.translate(0.3, 0.002, 0);
578
+ fgeo.translate(-0.2, 0.002, 0);
575
579
  const fmat = new THREE.ShaderMaterial({
576
580
  uniforms: u, vertexShader: floorVertex, fragmentShader: floorFragment,
577
581
  transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
@@ -718,9 +722,12 @@ const DEFAULTS = {
718
722
  trailColor: '#ff7a2e',
719
723
  trailShare: 1,
720
724
  };
721
- const GrinderSparksParticles = ({ size, config, animations, onObjectReady, onObjectDispose }) => {
725
+ /** Knobs that rebuild the simulation: they change on commit, never through the live path. */
726
+ const STRUCTURAL = ['particleCount', 'seed', 'quality'];
727
+ /** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
728
+ const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.grinderSparks.map((p) => p.value));
729
+ const GrinderSparksParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
722
730
  const rootRef = useRef(null);
723
- const animCache = useRef({});
724
731
  useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
725
732
  const s = size > 0 ? size : 1;
726
733
  const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
@@ -733,11 +740,28 @@ const GrinderSparksParticles = ({ size, config, animations, onObjectReady, onObj
733
740
  applyConfig(uniforms, cfg, s);
734
741
  instance.apply(cfg);
735
742
  }, [instance, uniforms, cfg, s]);
743
+ // Slider drags and keyframe animations are both overrides on the committed config.
744
+ // This is the one place they are merged and pushed, so they compose instead of
745
+ // overwriting each other; the animation wins, because it is the active driver.
746
+ const keyframes = animations?.particles?.animations;
747
+ const animOverride = useRef({});
748
+ const animFrom = useRef({});
749
+ const animStart = useRef(0);
750
+ // Editing a keyframe restarts its cycle rather than dropping the user mid-way.
751
+ useEffect(() => { animStart.current = performance.now(); animFrom.current = {}; }, [keyframes]);
752
+ const push = useCallback((overrides) => {
753
+ const merged = { ...cfg, ...overrides, ...animOverride.current };
754
+ const z = merged.size;
755
+ applyConfig(uniforms, merged, z && z > 0 ? z : s);
756
+ }, [cfg, uniforms, s]);
757
+ const live = useConfigLiveHandle(rootRef, id, 'particles', cfg, STRUCTURAL, push);
736
758
  const pointer = usePointerField();
737
759
  useFrame((state, delta) => {
738
760
  instance.update(Math.min(delta, 0.1), state.camera, pointer);
739
- if (animations?.length)
740
- applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
761
+ if (keyframes?.length) {
762
+ applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
763
+ push(live.current);
764
+ }
741
765
  });
742
766
  return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
743
767
  };
@@ -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
  };
@@ -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';
@@ -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';
@@ -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;
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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAG1F,OAAO,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG5F,cAAc,SAAS,CAAC;AAGxB,cAAc,cAAc,CAAC;AAG7B,cAAc,SAAS,CAAC;AAExB,YAAY,EAAE,oBAAoB,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGjG,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,GAAG,MAAM,iBAAiB,CAAC;AACrG,OAAO,EAAE,mBAAmB,IAAI,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACvF,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACvF,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,qBAAqB,EACrB,eAAe,EACf,8BAA8B,GAC/B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpG,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrF,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlF,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC5F,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,gBAAgB,EAChB,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,sBAAsB,EACtB,eAAe,EACf,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC"}
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ export { buildTextureArray, decodeHeightmap, loadImage } from './utils/landscape
26
26
  export { applyOceanUniforms, buildWaveTable, seaState } from './utils/oceanUniforms.js';
27
27
  export { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, applyThunderUniforms, } from './utils/cloudUniforms.js';
28
28
  export { applyCloudsKeyframeAnimations, applyCloudsMouseMove, resolveDeckConfig, } from './utils/handleCloudsAnimations.js';
29
+ export { applyConfigKeyframeAnimations, applyConfigMouseMove, hasConfigOverrides, resolveConfig, } from './utils/configAnimations.js';
29
30
  // Presets
30
31
  export { volumetricCloudPresets, cirrusPresets, getCloudPresetsForType, getCloudPresetByName, getCirrusPresetByName, searchCloudPresetsByTags, } from './presets/volumetricCloudPresets.js';
31
32
  export { terrainPresets, getTerrainPresetsForType, getTerrainPresetByName, searchTerrainPresetsByTags, } from './presets/terrainPresets.js';
@@ -0,0 +1,43 @@
1
+ import type { AnimationOptions, MouseMoveProperty } from '@3driseai/3drise-core';
2
+ /**
3
+ * Keyframe animations and mouse-move, applied to a *config object* rather than to
4
+ * materials — the shape every generative family shares (particles, effects, and the
5
+ * clouds / rain helpers this generalises).
6
+ *
7
+ * The renderer keeps one override record per frame and lays it over the committed
8
+ * config before pushing uniforms:
9
+ *
10
+ * applyConfigKeyframeAnimations(anims, startValues, config, override, elapsedMs, ANIMATABLE);
11
+ * applyConfig(uniforms, { ...config, ...live, ...override }, size);
12
+ *
13
+ * Only knobs in `allowed` are eligible: that set is generated from the component's
14
+ * own applyConfig (see core's GENERATIVE_ANIMATABLE), so a keyframe can never target
15
+ * something that would need a rebuild to take effect.
16
+ *
17
+ * Both numbers and colours work — `interpolateValue` is type-aware and reads hex
18
+ * strings as colours.
19
+ */
20
+ export type ConfigOverride = Record<string, number | string>;
21
+ /** Cache of the value a property started at, so `from`-less keyframes have an origin. */
22
+ export type ConfigStartValues = Record<string, number | string>;
23
+ /**
24
+ * @param elapsedMs Milliseconds since this animation set started — NOT `Date.now()`,
25
+ * and not seconds. `calculateTimeProgress` divides by `duration`, so a wall
26
+ * clock makes a one-shot finish on its first frame, and seconds make it crawl.
27
+ * Reset the origin (and `startValues`) whenever the animation list changes, so
28
+ * editing a keyframe restarts it instead of dropping the user mid-cycle.
29
+ *
30
+ * Clears `override` first: a keyframe that was removed must stop driving its knob.
31
+ * Call this before `applyConfigMouseMove`, which layers on top.
32
+ */
33
+ export declare function applyConfigKeyframeAnimations(animations: AnimationOptions[] | undefined, startValues: ConfigStartValues, config: Record<string, unknown>, override: ConfigOverride, elapsedMs: number, allowed?: ReadonlySet<string>): void;
34
+ /** Pointer-driven overrides, layered on top of whatever the keyframes produced. */
35
+ export declare function applyConfigMouseMove(properties: MouseMoveProperty[] | undefined, config: Record<string, unknown>, override: ConfigOverride, pointer: {
36
+ x: number;
37
+ y: number;
38
+ }, allowed?: ReadonlySet<string>): void;
39
+ /** True when anything is currently being driven. */
40
+ export declare function hasConfigOverrides(override: ConfigOverride): boolean;
41
+ /** Merge overrides into a config, returning the original when nothing is driving it. */
42
+ export declare function resolveConfig<T extends object>(config: T, ...overrides: (ConfigOverride | undefined)[]): T;
43
+ //# sourceMappingURL=configAnimations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configAnimations.d.ts","sourceRoot":"","sources":["../../src/utils/configAnimations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEjF;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAE7D,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEhE;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CACzC,UAAU,EAAE,gBAAgB,EAAE,GAAG,SAAS,EAC1C,WAAW,EAAE,iBAAiB,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,IAAI,CAqBN;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAChC,UAAU,EAAE,iBAAiB,EAAE,GAAG,SAAS,EAC3C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACjC,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,IAAI,CAYN;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAGpE;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,SAAS,EAAE,CAAC,cAAc,GAAG,SAAS,CAAC,EAAE,GAAG,CAAC,CAK1G"}
@@ -0,0 +1,73 @@
1
+ import { calculateTimeProgress, applyEasing, gsapEasingMap, interpolateValue, interpolateMouseMoveProperty } from '@3driseai/3drise-core';
2
+ /**
3
+ * @param elapsedMs Milliseconds since this animation set started — NOT `Date.now()`,
4
+ * and not seconds. `calculateTimeProgress` divides by `duration`, so a wall
5
+ * clock makes a one-shot finish on its first frame, and seconds make it crawl.
6
+ * Reset the origin (and `startValues`) whenever the animation list changes, so
7
+ * editing a keyframe restarts it instead of dropping the user mid-cycle.
8
+ *
9
+ * Clears `override` first: a keyframe that was removed must stop driving its knob.
10
+ * Call this before `applyConfigMouseMove`, which layers on top.
11
+ */
12
+ export function applyConfigKeyframeAnimations(animations, startValues, config, override, elapsedMs, allowed) {
13
+ for (const k of Object.keys(override))
14
+ delete override[k];
15
+ if (!animations?.length)
16
+ return;
17
+ for (const anim of animations) {
18
+ const { property, to, duration, ease, loop = false, yoyo = false, active } = anim;
19
+ if (active === false || to === undefined)
20
+ continue;
21
+ if (allowed && !allowed.has(property))
22
+ continue;
23
+ let from = anim.from;
24
+ if (from === undefined) {
25
+ from = startValues[property] ??= config[property];
26
+ }
27
+ if (from === undefined || from === null)
28
+ continue;
29
+ let p = calculateTimeProgress(elapsedMs, duration, yoyo, loop);
30
+ if (ease) {
31
+ const fn = gsapEasingMap[ease] || ease;
32
+ p = applyEasing(fn, p);
33
+ }
34
+ override[property] = interpolateValue(property, from, to, p);
35
+ }
36
+ }
37
+ /** Pointer-driven overrides, layered on top of whatever the keyframes produced. */
38
+ export function applyConfigMouseMove(properties, config, override, pointer, allowed) {
39
+ if (!properties?.length)
40
+ return;
41
+ for (const { property, enabled, propertySettings } of properties) {
42
+ if (!enabled)
43
+ continue;
44
+ if (allowed && !allowed.has(property))
45
+ continue;
46
+ const { axis = 'both', sensitivity = 1, inverted = false, min, max } = propertySettings || {};
47
+ // Compose with the keyframed value when there is one, so the two stack instead
48
+ // of the pointer snapping back to the config every frame.
49
+ const base = override[property] ?? config[property];
50
+ if (base === undefined)
51
+ continue;
52
+ override[property] = interpolateMouseMoveProperty(base, min, max, pointer, axis, sensitivity, inverted);
53
+ }
54
+ }
55
+ /** True when anything is currently being driven. */
56
+ export function hasConfigOverrides(override) {
57
+ for (const k in override)
58
+ if (override[k] !== undefined)
59
+ return true;
60
+ return false;
61
+ }
62
+ /** Merge overrides into a config, returning the original when nothing is driving it. */
63
+ export function resolveConfig(config, ...overrides) {
64
+ let dirty = false;
65
+ for (const o of overrides)
66
+ if (o && hasConfigOverrides(o)) {
67
+ dirty = true;
68
+ break;
69
+ }
70
+ if (!dirty)
71
+ return config;
72
+ return Object.assign({}, config, ...overrides.filter(Boolean));
73
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"particleTrails.d.ts","sourceRoot":"","sources":["../../src/utils/particleTrails.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,eAAO,MAAM,WAAW,UAAsC,CAAC;AAK/D,wBAAgB,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;EAWlC;AAED,wBAAgB,gBAAgB,CAAC,CAAC,KAAA,EAAE,CAAC,KAAA,QAKpC;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,MAAM,KAAA;;;YAES,CAAC,MAAM,CAAC,MAAM,CAAC,SAAU;YAAE,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS;YAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAQ;;;;;;;;;;;;;;;;;;;;EAMjH;AA6HD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,KAAA,EAAE,IAAI,KAAA;;;;;;;EAyJxC"}
1
+ {"version":3,"file":"particleTrails.d.ts","sourceRoot":"","sources":["../../src/utils/particleTrails.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,eAAO,MAAM,WAAW,UAAsC,CAAC;AAK/D,wBAAgB,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;EAWlC;AAED,wBAAgB,gBAAgB,CAAC,CAAC,KAAA,EAAE,CAAC,KAAA,QAKpC;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,MAAM,KAAA;;;YAES,CAAC,MAAM,CAAC,MAAM,CAAC,SAAU;YAAE,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS;YAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAQ;;;;;;;;;;;;;;;;;;;;EAMjH;AAuID;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,KAAA,EAAE,IAAI,KAAA;;;;;;;EAyJxC"}
@@ -94,6 +94,13 @@ function finish(sizePx) {
94
94
  gl_Position = projectionMatrix * vec4(mv, 1.0);
95
95
  `;
96
96
  }
97
+ // The per-vertex template parameter lives in its own attribute (aT). `position` must exist for
98
+ // InstancedBufferGeometry to draw, but it stays all zeros: Box3.expandByObject(obj, true) reads it,
99
+ // and a 0..10 template in position.x would drag an editor's selection gizmo ten units sideways.
100
+ function setTemplate(g, tpl) {
101
+ g.setAttribute('position', new THREE.BufferAttribute(new Float32Array(tpl.length * 3), 3));
102
+ g.setAttribute('aT', new THREE.BufferAttribute(tpl, 1));
103
+ }
97
104
  const K_ON = '#define TRAIL_K aK\nattribute float aK;\n';
98
105
  const K_OFF = '#define TRAIL_K 1.0\n';
99
106
  function streakShader(glsl, sizePx, spacing, kdef) {
@@ -102,12 +109,13 @@ function streakShader(glsl, sizePx, spacing, kdef) {
102
109
  S,
103
110
  vertex: /* glsl */ `
104
111
  ${HEAD}
112
+ attribute float aT;
105
113
  attribute vec3 aP;
106
114
  attribute vec3 aV;
107
115
  ${kdef}
108
116
  ${glsl}
109
117
  void main() {
110
- float f = position.x; // 0 head .. 1 tail
118
+ float f = aT; // 0 head .. 1 tail
111
119
  vec3 dv = aV * uVelScale * uTrailLength;
112
120
  float len = length(dv);
113
121
  vec3 p = aP - dv * f;
@@ -123,6 +131,7 @@ function cometShader(glsl, sizePx, spacing, kdef, SUB) {
123
131
  const pick = Array.from({ length: SLOTS }, (_, i) => ` ${i === 0 ? 'if' : 'else if'} (i == ${i}) return h${i};`).join('\n');
124
132
  return /* glsl */ `
125
133
  ${HEAD}
134
+ attribute float aT;
126
135
  attribute vec3 aP;
127
136
  ${slots}
128
137
  ${kdef}
@@ -142,7 +151,7 @@ vec3 catmull(vec3 a, vec3 b, vec3 c, vec3 d, float t) {
142
151
  return 0.5 * ((2.0 * b) + (-a + c) * t + (2.0 * a - 5.0 * b + 4.0 * c - d) * t2 + (-a + 3.0 * b - 3.0 * c + d) * t3);
143
152
  }
144
153
  void main() {
145
- float s = position.x; // 0 .. ${SLOTS} along the chain
154
+ float s = aT; // 0 .. ${SLOTS} along the chain
146
155
  int k = int(floor(s));
147
156
  float t = s - float(k);
148
157
  vec3 p0 = L(k - 1), p1 = L(k), p2 = L(k + 1), p3 = L(k + 2);
@@ -204,10 +213,10 @@ export function createCpuTrail(mode, opts) {
204
213
  if (mode === 'streak') {
205
214
  const sh = streakShader(glsl, sizePx, spacing, kdef);
206
215
  const g = new THREE.InstancedBufferGeometry();
207
- const tpl = new Float32Array(sh.S * 3);
216
+ const tpl = new Float32Array(sh.S);
208
217
  for (let i = 0; i < sh.S; i++)
209
- tpl[i * 3] = i / (sh.S - 1);
210
- g.setAttribute('position', new THREE.BufferAttribute(tpl, 3));
218
+ tpl[i] = i / (sh.S - 1);
219
+ setTemplate(g, tpl);
211
220
  const aP = new THREE.InstancedBufferAttribute(pos, 3).setUsage(THREE.DynamicDrawUsage);
212
221
  const aV = new THREE.InstancedBufferAttribute(vel, 3).setUsage(THREE.DynamicDrawUsage);
213
222
  g.setAttribute('aP', aP);
@@ -232,10 +241,10 @@ export function createCpuTrail(mode, opts) {
232
241
  const g = new THREE.InstancedBufferGeometry();
233
242
  const SUBn = opts.sub || SUB; // spline samples per history segment (fast particles need more)
234
243
  const M = SLOTS * SUBn + 1;
235
- const tpl = new Float32Array(M * 3);
244
+ const tpl = new Float32Array(M);
236
245
  for (let i = 0; i < M; i++)
237
- tpl[i * 3] = i / SUBn;
238
- g.setAttribute('position', new THREE.BufferAttribute(tpl, 3));
246
+ tpl[i] = i / SUBn;
247
+ setTemplate(g, tpl);
239
248
  const aP = new THREE.InstancedBufferAttribute(pos, 3).setUsage(THREE.DynamicDrawUsage);
240
249
  g.setAttribute('aP', aP);
241
250
  const aK = kArr ? new THREE.InstancedBufferAttribute(kArr, 1).setUsage(THREE.DynamicDrawUsage) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3driseai/3drise-viewer",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,6 +39,6 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@3driseai/3drise-common": "^1.0.1",
42
- "@3driseai/3drise-core": "^1.0.12"
42
+ "@3driseai/3drise-core": "^1.0.13"
43
43
  }
44
44
  }