@3driseai/3drise-viewer 0.1.6 → 0.1.7

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.
@@ -76,7 +76,7 @@ const CustomPrimitive = memo(({ createdObject, onObjectReady, onObjectDispose, h
76
76
  // mode = 'outline';
77
77
  // }
78
78
  if (createdObject.type === 'mesh')
79
- return _jsx("mesh", { ref: meshRef, name: createdObject.name, children: _jsx(GeometryMesh, { config: createdObject.config, materialSettings: createdObject.materialSettings, animations: createdObject.animations, onObjectReady: (_object, ref) => onObjectReady && onObjectReady(createdObject, ref), onObjectDispose: (_object, ref) => onObjectDispose && onObjectDispose(createdObject, ref), modeConfig: createdObject.modeConfig || { mode: 'solid', count: 1000, symmetry: 3, spacing: 0.1, slices: 8, linkRadius: 0.05, angle: 18, lineWidth: 1 } }) }, createdObject.id);
79
+ return _jsx("mesh", { ref: meshRef, name: createdObject.name, children: _jsx(GeometryMesh, { config: createdObject.config, materialSettings: createdObject.materialSettings, animations: createdObject.animations, onObjectReady: (_object, ref) => onObjectReady && onObjectReady(createdObject, ref), onObjectDispose: (_object, ref) => onObjectDispose && onObjectDispose(createdObject, ref), modeConfig: createdObject.modeConfig || { mode: 'solid', count: 1000, symmetry: 3, spacing: 0.1, slices: 8, linkRadius: 0.05, angle: 18, lineWidth: 1 }, children: useDreiMaterials && createdObject.meshSettings.visible && (_jsx(MaterialLibrary, { materialSettings: createdObject.materialSettings })) }) }, createdObject.id);
80
80
  if (!resolvedObject) {
81
81
  return null;
82
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GeometryShape.d.ts","sourceRoot":"","sources":["../../src/components/GeometryShape.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAI5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACjH,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnC,KAAK,QAAQ,GAAG;IACZ,IAAI,EAAE,MAAM,CAAC;CAChB,GAAG;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,YAAY,CAAC;CAC3B,GAAG;IACA,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,EAAE,EAAE,CAAC;CAChB,GAAG;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,YAAY,CAAC;IACxB,QAAQ,EAAE,EAAE,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACvD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AA4BD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,GAAG,cAAc,CAShG;AA4BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW,GAAG,YAAY,CAyBpG;AACD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAiC/E;AACD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAiB9F;AACD,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAgC7F;AACD,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAS9D;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,EAAE,CAKnE;AACD,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE,CAkBnD;AACD,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE,CAKnD;AACD,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,GAAG,EAAE,EAAE,CAuBzF;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAU,GAAG;IAC7F,SAAS,EAAE,YAAY,CAAC;IACxB,QAAQ,EAAE,EAAE,EAAE,CAAC;CAClB,CAoCJ;AACD,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,EAAE,UAAU,GAAG,QAAQ,CAkBxE;AAMD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAA8B,EAAE,KAAiB,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,EAAE,iBAAiB,+BAkBpL;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAY,EAAE,QAAQ,EAAE,EAAE;IACnD,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB,GAQ2E,GAAG,CACtF"}
1
+ {"version":3,"file":"GeometryShape.d.ts","sourceRoot":"","sources":["../../src/components/GeometryShape.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAI5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAQjH,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnC,KAAK,QAAQ,GAAG;IACZ,IAAI,EAAE,MAAM,CAAC;CAChB,GAAG;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,YAAY,CAAC;CAC3B,GAAG;IACA,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,EAAE,EAAE,CAAC;CAChB,GAAG;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,YAAY,CAAC;IACxB,QAAQ,EAAE,EAAE,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACvD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AA4BD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,GAAG,cAAc,CAShG;AA4BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW,GAAG,YAAY,CAyBpG;AACD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAiC/E;AACD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAiB9F;AACD,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAgC7F;AACD,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAS9D;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,EAAE,CAKnE;AACD,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE,CAkBnD;AACD,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE,CAKnD;AACD,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,GAAG,EAAE,EAAE,CAuBzF;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAU,GAAG;IAC7F,SAAS,EAAE,YAAY,CAAC;IACxB,QAAQ,EAAE,EAAE,EAAE,CAAC;CAClB,CAoCJ;AACD,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,EAAE,UAAU,GAAG,QAAQ,CAkBxE;AAMD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAA8B,EAAE,KAAiB,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,EAAE,iBAAiB,+BA2BpL;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAY,EAAE,QAAQ,EAAE,EAAE;IACnD,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB,GAQ2E,GAAG,CACtF"}
@@ -2,9 +2,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef } from 'react';
3
3
  import { useObjectReady } from '../hooks/useObjectReady.js';
4
4
  import { EdgesGeometry, WireframeGeometry, Mesh, MeshBasicMaterial, Raycaster, Vector3, DoubleSide, } from 'three';
5
- import { Line } from '@react-three/drei';
5
+ import { buildSegmentQuadGeometry } from '@3driseai/3drise-core';
6
6
  import { shapeGenerators } from '../utils/shapes.js';
7
7
  import PointCloud from './PointCloud.js';
8
+ /**
9
+ * World-space half-thickness of a line ribbon at `lineWidth: 1`. Line modes are
10
+ * built as quad meshes rather than line primitives so that materials apply to
11
+ * them — see buildSegmentQuadGeometry — which means width is a world unit here,
12
+ * not a screen-space pixel count. Matches the edges builder's default.
13
+ */
14
+ const LINE_THICKNESS_UNIT = 0.008;
8
15
  const defaultModeConfig = {
9
16
  mode: 'solid',
10
17
  count: 1000,
@@ -312,13 +319,22 @@ export function GeometryMesh({ config, materialSettings, animations, modeConfig
312
319
  const geometry = useShapeGeometry(config);
313
320
  const safeModeConfig = normalizeModeConfig(modeConfig);
314
321
  const data = useModeData(geometry, safeModeConfig);
322
+ // Line modes are ribbons of quads, not line primitives, so the material
323
+ // system reaches them the same way it reaches edges. Built before the
324
+ // branches below because hooks cannot run conditionally.
325
+ const lineSegments = data.kind === 'lines' ? data.points : data.kind === 'plexus' ? data.segments : null;
326
+ const lineThickness = (safeModeConfig.lineWidth ?? 1) * LINE_THICKNESS_UNIT;
327
+ const lineGeometry = useMemo(() => (lineSegments && lineSegments.length
328
+ ? buildSegmentQuadGeometry(lineSegments, lineThickness)
329
+ : null), [lineSegments, lineThickness]);
330
+ useEffect(() => () => { lineGeometry?.dispose(); }, [lineGeometry]);
315
331
  // if (data.kind === 'points') return <PointCloud positions={data.positions} color={color} size={modeConfig.mode === 'vertices' ? 0.035 : 0.02} />;
316
332
  if (data.kind === 'points')
317
333
  return _jsx(PointCloud, { positions: data.positions, animations: animations, materialSettings: materialSettings, onObjectReady: reportReady, onObjectDispose: reportDispose });
318
334
  if (data.kind === 'lines')
319
- return data.points.length ? _jsx(Line, { ref: rootRef, points: data.points, segments: true, color: color, lineWidth: safeModeConfig.lineWidth }) : null;
335
+ return lineGeometry ? (_jsx("mesh", { ref: rootRef, geometry: lineGeometry, children: children ?? _jsx("meshBasicMaterial", { color: color, side: DoubleSide }) })) : null;
320
336
  if (data.kind === 'plexus')
321
- return (_jsxs("group", { ref: rootRef, children: [_jsx(PointCloud, { positions: data.positions, animations: animations, materialSettings: materialSettings }), data.segments.length > 0 && _jsx(Line, { points: data.segments, segments: true, color: color, lineWidth: safeModeConfig.lineWidth, transparent: true, opacity: 0.45 })] }));
337
+ return (_jsxs("group", { ref: rootRef, children: [_jsx(PointCloud, { positions: data.positions, animations: animations, materialSettings: materialSettings }), lineGeometry && (_jsx("mesh", { geometry: lineGeometry, children: children ?? _jsx("meshBasicMaterial", { color: color, side: DoubleSide, transparent: true, opacity: 0.45 }) }))] }));
322
338
  return (_jsx("mesh", { ref: rootRef, geometry: geometry, children: children ?? _jsx("meshPhongMaterial", { color: color, side: DoubleSide }) }));
323
339
  }
324
340
  export function FitGroup({ geometry, radius = 1.7, children }) {
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectNode.d.ts","sourceRoot":"","sources":["../../src/components/ObjectNode.tsx"],"names":[],"mappings":"AAcA,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;AACrD,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;AA0BF,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAyGzC,CAAC;AACF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"ObjectNode.d.ts","sourceRoot":"","sources":["../../src/components/ObjectNode.tsx"],"names":[],"mappings":"AAcA,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;AAErD,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;AAgBF,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAyGzC,CAAC;AACF,eAAe,UAAU,CAAC"}
@@ -13,15 +13,7 @@ import LightObject from "./LightObject.js";
13
13
  import EnvironmentObject from "./EnvironmentObject.js";
14
14
  import Galaxy from "./Galaxy.js";
15
15
  import GridGenerator from "./GridsGenerator.js";
16
- const SELF_MANAGED_MATERIAL = new Set([
17
- 'effect',
18
- // 'gallery',
19
- 'earth',
20
- 'space',
21
- 'shootingStars',
22
- 'grid',
23
- // 'moon'
24
- ]);
16
+ import { SELF_MANAGED_MATERIAL } from "../constants.js";
25
17
  // const EffectsLayer: React.FC<{ effects: CreatedObjectSettings['effects'] }> = ({ effects }) =>
26
18
  // effects?.length ? (
27
19
  // <>
@@ -1 +1 @@
1
- {"version":3,"file":"ParticlesR3f.d.ts","sourceRoot":"","sources":["../../src/components/ParticlesR3f.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAO7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,UAAU,iBAAkB,SAAQ,gBAAgB;IAChD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChD;AAMD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAkG7C,CAAC;AACF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"ParticlesR3f.d.ts","sourceRoot":"","sources":["../../src/components/ParticlesR3f.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAO7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG/D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,UAAU,iBAAkB,SAAQ,gBAAgB;IAChD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChD;AAMD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAoJ7C,CAAC;AACF,eAAe,YAAY,CAAC"}
@@ -5,8 +5,9 @@ import { useFrame } from '@react-three/fiber';
5
5
  import { useParticlePositions } from '../hooks/useParticlePositions.js';
6
6
  import { useParticlesColors } from '../hooks/useParticlesColors.js';
7
7
  import { useParticlesTextures } from '../hooks/useParticlesTextures.js';
8
- import { handleShapeAnimation, buildShaderEffectsMaterial, structureKey } from '@3driseai/3drise-core';
8
+ import { handleShapeAnimation, buildShaderEffectsMaterial, structureKey, SELF_OWNED_MATERIAL_KEY, applyVertexEffects, partitionShaderEffects, LOCAL_POINTER_SCALE } from '@3driseai/3drise-core';
9
9
  import { update3driseShaderUniforms } from '../hooks/use3driseShader.js';
10
+ import { ownsShaderEffectsMaterial } from '../utils/pointsMaterialOwnership.js';
10
11
  function normalizeEffects(raw) {
11
12
  return Array.isArray(raw) ? raw : [];
12
13
  }
@@ -71,42 +72,92 @@ const ParticlesR3f = ({ particlesSettings, duration = 1, transitionSettings, onO
71
72
  const activeEffects = useMemo(() => normalizeEffects(particlesSettings.animations?.material?.shaderEffects), [particlesSettings.animations?.material?.shaderEffects]);
72
73
  const pointerRadius = particlesSettings.animations?.material
73
74
  ?.shaderEffectsPointerRadius ?? 0.4;
74
- const effectsKey = useMemo(() => structureKey(activeEffects), [activeEffects]);
75
- const material = useMemo(() => buildShaderEffectsMaterial({
76
- effects: activeEffects,
75
+ // Position effects displace the geometry from the CPU and leave `.material`
76
+ // alone, so they compose with any material. Only the rest still need the
77
+ // shader shell — and only the shell takes ownership of `.material`.
78
+ const { position: positionEffects, shell: shellEffects } = useMemo(() => partitionShaderEffects(activeEffects), [activeEffects]);
79
+ const hasPositionEffects = positionEffects.some(effect => effect.enabled);
80
+ const effectsKey = useMemo(() => structureKey(shellEffects), [shellEffects]);
81
+ const ownsMaterial = ownsShaderEffectsMaterial(particlesSettings.materialSettings, shellEffects);
82
+ const material = useMemo(() => ownsMaterial ? buildShaderEffectsMaterial({
83
+ effects: shellEffects,
77
84
  initialColor: color,
78
85
  initialOpacity: opacity,
79
86
  initialPointSize: pointSize,
80
87
  initialParticleUVScale: particleUVScale,
81
88
  initialPointerEffectRadius: pointerRadius,
82
- }),
89
+ }) : null,
83
90
  // Rebuild when the structural set of effects changes; not on parameter tweaks.
84
91
  // eslint-disable-next-line react-hooks/exhaustive-deps
85
- [effectsKey]);
92
+ [effectsKey, ownsMaterial]);
93
+ // With position effects on, the attribute gets its own buffer: the shape
94
+ // animation writes this frame's morph into it, and the effects displace from
95
+ // a snapshot of that into the same buffer. Without a separate buffer the
96
+ // shape animation would read back its own displaced output next frame.
97
+ const displayPositions = useMemo(() => (hasPositionEffects && basePositions.current
98
+ ? new Float32Array(basePositions.current)
99
+ : basePositions.current), [hasPositionEffects, basePositions.current]);
100
+ const frameBaseRef = useRef(null);
86
101
  useEffect(() => {
87
102
  materialRef.current = material;
88
103
  return () => {
89
- material.dispose();
104
+ material?.dispose();
90
105
  };
91
106
  }, [material]);
107
+ // Tell useMaterialApplication's traversal to leave this object alone while we
108
+ // own its material. Set imperatively rather than through a `userData` prop:
109
+ // R3F replaces the whole object on every render, which would wipe the
110
+ // applyTexture handle stashed above.
111
+ useEffect(() => {
112
+ const points = pointsRef.current;
113
+ if (!points)
114
+ return;
115
+ points.userData[SELF_OWNED_MATERIAL_KEY] = ownsMaterial;
116
+ return () => {
117
+ if (points.userData)
118
+ delete points.userData[SELF_OWNED_MATERIAL_KEY];
119
+ };
120
+ }, [ownsMaterial]);
92
121
  useFrame((state, delta) => {
93
- const mat = materialRef.current;
94
- if (!mat || !pointsRef.current || !basePositions.current)
122
+ if (!pointsRef.current || !basePositions.current)
95
123
  return;
124
+ const mat = materialRef.current;
96
125
  if (meshSettings.visible) {
126
+ // Geometry animation is independent of who owns the material.
97
127
  handleShapeAnimation(delta, speed, animationProgress, pointsRef, basePositions, targetPositions);
98
- handleColorsAnimation(materialRef, delta, speed);
128
+ if (mat)
129
+ handleColorsAnimation(materialRef, delta, speed);
99
130
  }
131
+ if (hasPositionEffects && displayPositions && displayPositions !== basePositions.current) {
132
+ if (!frameBaseRef.current || frameBaseRef.current.length !== displayPositions.length)
133
+ frameBaseRef.current = new Float32Array(displayPositions.length);
134
+ // Snapshot the shape-animated positions as this frame's baseline so
135
+ // the effects do not compound over time.
136
+ frameBaseRef.current.set(displayPositions);
137
+ const moved = applyVertexEffects(frameBaseRef.current, displayPositions, positionEffects, {
138
+ time: state.clock.getElapsedTime(),
139
+ pointerX: state.pointer.x * LOCAL_POINTER_SCALE,
140
+ pointerY: state.pointer.y * LOCAL_POINTER_SCALE,
141
+ pointerRadius: pointerRadius * LOCAL_POINTER_SCALE,
142
+ });
143
+ if (moved) {
144
+ const attribute = pointsRef.current.geometry?.attributes?.position;
145
+ if (attribute)
146
+ attribute.needsUpdate = true;
147
+ }
148
+ }
149
+ if (!mat)
150
+ return;
100
151
  if (prevTexture.current)
101
152
  mat.uniforms.uTexture.value = prevTexture.current;
102
153
  if (currentTexture.current)
103
154
  mat.uniforms.uTargetTexture.value = currentTexture.current;
104
155
  mat.uniforms.uHasTexture.value = !!currentTexture.current;
105
156
  handleTextureAnimation(materialRef, delta, speed);
106
- update3driseShaderUniforms(mat.uniforms, activeEffects, state.clock.getElapsedTime(), { x: state.pointer.x, y: state.pointer.y }, { pointerRadius });
157
+ update3driseShaderUniforms(mat.uniforms, shellEffects, state.clock.getElapsedTime(), { x: state.pointer.x, y: state.pointer.y }, { pointerRadius });
107
158
  });
108
159
  if (!basePositions.current)
109
160
  return null;
110
- return (_jsxs("points", { name: name, ref: pointsRef, raycast: () => null, position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], children: [_jsxs("bufferGeometry", { children: [_jsx("bufferAttribute", { attach: "attributes-position", count: basePositions.current.length / 3, array: basePositions.current, itemSize: 3, args: [basePositions.current, 3] }), uvs && (_jsx("bufferAttribute", { attach: "attributes-aUV", count: uvs.length / 2, array: uvs, itemSize: 2, args: [uvs, 2] }))] }), _jsx("primitive", { object: material, attach: "material" })] }));
161
+ return (_jsxs("points", { name: name, ref: pointsRef, raycast: () => null, position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], children: [_jsxs("bufferGeometry", { children: [_jsx("bufferAttribute", { attach: "attributes-position", count: (displayPositions ?? basePositions.current).length / 3, array: displayPositions ?? basePositions.current, itemSize: 3, args: [displayPositions ?? basePositions.current, 3] }), uvs && (_jsx("bufferAttribute", { attach: "attributes-aUV", count: uvs.length / 2, array: uvs, itemSize: 2, args: [uvs, 2] }))] }), material && _jsx("primitive", { object: material, attach: "material" })] }));
111
162
  };
112
163
  export default ParticlesR3f;
@@ -1 +1 @@
1
- {"version":3,"file":"PointCloud.d.ts","sourceRoot":"","sources":["../../src/components/PointCloud.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAM7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,UAAU,eAAgB,SAAQ,gBAAgB;IAC9C,SAAS,EAAE,YAAY,CAAC;IACxB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAyBD,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAmFzC,CAAC;AACF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"PointCloud.d.ts","sourceRoot":"","sources":["../../src/components/PointCloud.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAO7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,UAAU,eAAgB,SAAQ,gBAAgB;IAC9C,SAAS,EAAE,YAAY,CAAC;IACxB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAyBD,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA0HzC,CAAC;AACF,eAAe,UAAU,CAAC"}
@@ -4,7 +4,8 @@ import { useEffect, useMemo, useRef } from 'react';
4
4
  import { useFrame } from '@react-three/fiber';
5
5
  import { useParticlesColors } from '../hooks/useParticlesColors.js';
6
6
  import { useParticlesTextures } from '../hooks/useParticlesTextures.js';
7
- import { buildShaderEffectsMaterial, structureKey } from '@3driseai/3drise-core';
7
+ import { buildShaderEffectsMaterial, structureKey, SELF_OWNED_MATERIAL_KEY, applyVertexEffects, partitionShaderEffects, LOCAL_POINTER_SCALE } from '@3driseai/3drise-core';
8
+ import { ownsShaderEffectsMaterial } from '../utils/pointsMaterialOwnership.js';
8
9
  import { update3driseShaderUniforms } from '../hooks/use3driseShader.js';
9
10
  function normalizeEffects(raw) {
10
11
  return Array.isArray(raw) ? raw : [];
@@ -77,27 +78,66 @@ const PointCloud = ({ positions, materialSettings, animations, onObjectReady, on
77
78
  const activeEffects = useMemo(() => normalizeEffects(animations?.material?.shaderEffects), [animations?.material?.shaderEffects]);
78
79
  const pointerRadius = animations?.material
79
80
  ?.shaderEffectsPointerRadius ?? 0.4;
80
- const effectsKey = useMemo(() => structureKey(activeEffects), [activeEffects]);
81
- const material = useMemo(() => buildShaderEffectsMaterial({
82
- effects: activeEffects,
81
+ // Position effects displace the geometry from the CPU and leave `.material`
82
+ // alone, so they compose with any material. Only the rest still need the
83
+ // shader shell — and only the shell takes ownership of `.material`.
84
+ const { position: positionEffects, shell: shellEffects } = useMemo(() => partitionShaderEffects(activeEffects), [activeEffects]);
85
+ const hasPositionEffects = positionEffects.some(effect => effect.enabled);
86
+ const effectsKey = useMemo(() => structureKey(shellEffects), [shellEffects]);
87
+ const ownsMaterial = ownsShaderEffectsMaterial(materialSettings, shellEffects);
88
+ // A separate buffer to displace into, so `positions` stays the authored
89
+ // baseline and effects never accumulate frame over frame.
90
+ const displayPositions = useMemo(() => (hasPositionEffects && positions ? new Float32Array(positions) : positions), [positions, hasPositionEffects]);
91
+ const material = useMemo(() => ownsMaterial ? buildShaderEffectsMaterial({
92
+ effects: shellEffects,
83
93
  initialColor: color,
84
94
  initialOpacity: opacity,
85
95
  initialPointSize: pointSize,
86
96
  initialParticleUVScale: particleUVScale,
87
97
  initialPointerEffectRadius: pointerRadius,
88
- }),
98
+ }) : null,
89
99
  // Rebuild when the structural set of effects changes; not on parameter tweaks.
90
100
  // eslint-disable-next-line react-hooks/exhaustive-deps
91
- [effectsKey]);
101
+ [effectsKey, ownsMaterial]);
92
102
  useEffect(() => {
93
103
  materialRef.current = material;
94
104
  return () => {
95
- material.dispose();
105
+ material?.dispose();
96
106
  };
97
107
  }, [material]);
108
+ // Tell useMaterialApplication's traversal to leave this object alone while we
109
+ // own its material. Set imperatively rather than through a `userData` prop:
110
+ // R3F replaces the whole object on every render, which would wipe the
111
+ // applyTexture handle stashed above.
112
+ useEffect(() => {
113
+ const points = pointsRef.current;
114
+ if (!points)
115
+ return;
116
+ points.userData[SELF_OWNED_MATERIAL_KEY] = ownsMaterial;
117
+ return () => {
118
+ if (points.userData)
119
+ delete points.userData[SELF_OWNED_MATERIAL_KEY];
120
+ };
121
+ }, [ownsMaterial]);
98
122
  useFrame((state, delta) => {
123
+ const points = pointsRef.current;
124
+ if (!points)
125
+ return;
126
+ if (hasPositionEffects && displayPositions !== positions) {
127
+ const moved = applyVertexEffects(positions, displayPositions, positionEffects, {
128
+ time: state.clock.getElapsedTime(),
129
+ pointerX: state.pointer.x * LOCAL_POINTER_SCALE,
130
+ pointerY: state.pointer.y * LOCAL_POINTER_SCALE,
131
+ pointerRadius: pointerRadius * LOCAL_POINTER_SCALE,
132
+ });
133
+ if (moved) {
134
+ const attribute = points.geometry?.attributes?.position;
135
+ if (attribute)
136
+ attribute.needsUpdate = true;
137
+ }
138
+ }
99
139
  const mat = materialRef.current;
100
- if (!mat || !pointsRef.current)
140
+ if (!mat)
101
141
  return;
102
142
  handleColorsAnimation(materialRef, delta, speed);
103
143
  if (prevTexture.current)
@@ -106,10 +146,10 @@ const PointCloud = ({ positions, materialSettings, animations, onObjectReady, on
106
146
  mat.uniforms.uTargetTexture.value = currentTexture.current;
107
147
  mat.uniforms.uHasTexture.value = !!currentTexture.current;
108
148
  handleTextureAnimation(materialRef, delta, speed);
109
- update3driseShaderUniforms(mat.uniforms, activeEffects, state.clock.getElapsedTime(), { x: state.pointer.x, y: state.pointer.y }, { pointerRadius });
149
+ update3driseShaderUniforms(mat.uniforms, shellEffects, state.clock.getElapsedTime(), { x: state.pointer.x, y: state.pointer.y }, { pointerRadius });
110
150
  });
111
151
  if (!positions)
112
152
  return null;
113
- return (_jsxs("points", { ref: pointsRef, raycast: () => null, position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], children: [_jsxs("bufferGeometry", { children: [_jsx("bufferAttribute", { attach: "attributes-position", count: positions.length / 3, array: positions, itemSize: 3, args: [positions, 3] }), uvs && (_jsx("bufferAttribute", { attach: "attributes-aUV", count: uvs.length / 2, array: uvs, itemSize: 2, args: [uvs, 2] }))] }), _jsx("primitive", { object: material, attach: "material" })] }));
153
+ return (_jsxs("points", { ref: pointsRef, raycast: () => null, position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], children: [_jsxs("bufferGeometry", { children: [_jsx("bufferAttribute", { attach: "attributes-position", count: displayPositions.length / 3, array: displayPositions, itemSize: 3, args: [displayPositions, 3] }), uvs && (_jsx("bufferAttribute", { attach: "attributes-aUV", count: uvs.length / 2, array: uvs, itemSize: 2, args: [uvs, 2] }))] }), material && _jsx("primitive", { object: material, attach: "material" })] }));
114
154
  };
115
155
  export default PointCloud;
@@ -1,2 +1,3 @@
1
1
  export declare const dataCenterServer = "https://3drise.ai/api/data/";
2
+ export declare const SELF_MANAGED_MATERIAL: Set<string>;
2
3
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,gBAAgB,gCAAgC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAC9D,eAAO,MAAM,qBAAqB,aAKhC,CAAC"}
package/dist/constants.js CHANGED
@@ -4,3 +4,9 @@ const isLocalHostRuntime = typeof window !== 'undefined' &&
4
4
  (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
5
5
  // export const dataCenterServer = isLocalHostRuntime ? LOCAL_DATA_CENTER_SERVER : PRODUCTION_DATA_CENTER_SERVER;
6
6
  export const dataCenterServer = PRODUCTION_DATA_CENTER_SERVER;
7
+ export const SELF_MANAGED_MATERIAL = new Set([
8
+ 'effect',
9
+ 'space',
10
+ 'grid',
11
+ // 'moon'
12
+ ]);
@@ -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;AAI/D,eAAO,MAAM,sBAAsB,GAAI,QAAQ,cAAc,EAAE,kBAAkB,gBAAgB,EAAE,4BAA4B,OAAO,EAAE,YAAY,MAAM,IAAI,KAAG,IAqIhK,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,IAuIhK,CAAC"}
@@ -8,8 +8,6 @@
8
8
  import { useEffect, useRef } from 'react';
9
9
  import { createAndApplyMaterial, MATERIAL_INSTANCE_KEY, MaterialRegistryAPI, ObjectManager, resolveTextures, traverseSkippingEdgesAndEffects } from '@3driseai/3drise-core';
10
10
  import { resolveObject3D } from "../utils/resolveObject3D.js";
11
- // TEST: 0=raw defaults, 1=+DoubleSide, 2=+transparent, 3=+metalness/roughness
12
- const TEST_MATERIAL_MODE = 1;
13
11
  export const useMaterialApplication = (source, materialSettings, shouldApply3driseMaterial, onApplied) => {
14
12
  // Track all material instances we've created so we can release them on change/unmount
15
13
  // const { getTypeByName } = useObjectTypes();
@@ -29,7 +27,8 @@ export const useMaterialApplication = (source, materialSettings, shouldApply3dri
29
27
  // attached refs, so this is the first point the object reliably exists.
30
28
  const obj = resolveObject3D(source);
31
29
  if (!obj || !materialSettings || materialSettings.materialType === 'advanced' ||
32
- materialSettings.materialVariant === '3dRiseShader' || (shouldApply3driseMaterial === false))
30
+ materialSettings.materialVariant === '3dRiseShader'
31
+ || (shouldApply3driseMaterial === false))
33
32
  return;
34
33
  handleSettingsChange(obj, materialSettings);
35
34
  return () => {
@@ -1,5 +1,5 @@
1
1
  import { MaterialRegistryAPI } from '@3driseai/3drise-core';
2
- import type { CreatedObjectType, DisplayMode, MaterialSettings, SelectOptions } from '@3driseai/3drise-core';
2
+ import type { CreatedObjectType, MaterialSettings, ModeGroup, SelectOptions } from '@3driseai/3drise-core';
3
3
  type RegistryMaterialType = Exclude<Parameters<typeof MaterialRegistryAPI.getVariants>[0], undefined>;
4
4
  export interface UseMaterialOptionsReturn {
5
5
  selectedType: RegistryMaterialType;
@@ -9,6 +9,6 @@ export interface UseMaterialOptionsReturn {
9
9
  handleTypeChange: (type: RegistryMaterialType) => void;
10
10
  handleVariantChange: (variant: string) => void;
11
11
  }
12
- export declare const useMaterialOptions: (initialSettings: MaterialSettings, onSettingsChange?: (newSettings: MaterialSettings) => void, objectType?: CreatedObjectType, displayMode?: DisplayMode) => UseMaterialOptionsReturn;
12
+ export declare const useMaterialOptions: (initialSettings: MaterialSettings, onSettingsChange?: (newSettings: MaterialSettings) => void, objectType?: CreatedObjectType, surface?: ModeGroup) => UseMaterialOptionsReturn;
13
13
  export {};
14
14
  //# sourceMappingURL=useMaterialOptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMaterialOptions.d.ts","sourceRoot":"","sources":["../../src/hooks/useMaterialOptions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE7G,KAAK,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAEtG,MAAM,WAAW,wBAAwB;IACrC,YAAY,EAAE,oBAAoB,CAAC;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,aAAa,CAAC;IACnC,cAAc,EAAE,aAAa,CAAC;IAC9B,gBAAgB,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACvD,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED,eAAO,MAAM,kBAAkB,GAAI,iBAAiB,gBAAgB,EAAE,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,KAAK,IAAI,EAAE,aAAY,iBAA0B,EAAE,cAAa,WAAqB,KAAG,wBA4G9M,CAAC"}
1
+ {"version":3,"file":"useMaterialOptions.d.ts","sourceRoot":"","sources":["../../src/hooks/useMaterialOptions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAA2B,MAAM,uBAAuB,CAAC;AACrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3G,KAAK,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAEtG,MAAM,WAAW,wBAAwB;IACrC,YAAY,EAAE,oBAAoB,CAAC;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,aAAa,CAAC;IACnC,cAAc,EAAE,aAAa,CAAC;IAC9B,gBAAgB,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACvD,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED,eAAO,MAAM,kBAAkB,GAAI,iBAAiB,gBAAgB,EAAE,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,KAAK,IAAI,EAAE,aAAY,iBAA0B,EAAE,UAAS,SAAkB,KAAG,wBA0FvM,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { useState, useEffect } from 'react';
2
- import { MaterialRegistryAPI } from '@3driseai/3drise-core';
2
+ import { MaterialRegistryAPI, materialTypeFitsSurface } from '@3driseai/3drise-core';
3
3
  import { FrontSide } from 'three';
4
- export const useMaterialOptions = (initialSettings, onSettingsChange, objectType = 'mesh', displayMode = 'solid') => {
4
+ export const useMaterialOptions = (initialSettings, onSettingsChange, objectType = 'mesh', surface = 'mesh') => {
5
5
  // Ensure we always have a valid material type
6
6
  const validType = (initialSettings.materialType || 'basic');
7
7
  const [selectedType, setSelectedType] = useState(validType);
@@ -18,35 +18,17 @@ export const useMaterialOptions = (initialSettings, onSettingsChange, objectType
18
18
  setSelectedVariant(initialSettings.materialVariant || variants[0] || '');
19
19
  }, [initialSettings.materialType, initialSettings.materialVariant]);
20
20
  const allmaterialTypes = MaterialRegistryAPI.getTypes();
21
- const is3dRiseShaderMaterial = initialSettings.materialType === 'particles' || initialSettings.materialType === 'custom';
22
- const isLineMaterial = initialSettings.materialType === 'line';
23
- const meshMaterialTypes = allmaterialTypes.filter(type => type !== 'particles' && type !== 'custom' && type !== 'line');
24
- const lineMaterialTypes = allmaterialTypes.filter(type => type === 'line');
25
- const dRiseShaderMaterialTypes = allmaterialTypes.filter(type => type === 'particles' || type === 'custom');
26
- const materialTypeOptions = () => {
27
- if (objectType === 'mesh') {
28
- return allmaterialTypes.map((type) => ({
29
- value: type,
30
- label: type.charAt(0).toUpperCase() + type.slice(1)
31
- }));
32
- }
33
- if (is3dRiseShaderMaterial) {
34
- return dRiseShaderMaterialTypes.map((type) => ({
35
- value: type,
36
- label: type.charAt(0).toUpperCase() + type.slice(1)
37
- }));
38
- }
39
- if (isLineMaterial) {
40
- return lineMaterialTypes.map((type) => ({
41
- value: type,
42
- label: type.charAt(0).toUpperCase() + type.slice(1)
43
- }));
44
- }
45
- return meshMaterialTypes.map((type) => ({
46
- value: type,
47
- label: type.charAt(0).toUpperCase() + type.slice(1)
48
- }));
49
- };
21
+ // Offer only the families that can actually draw on this object's surface.
22
+ // A <points> takes the `particles` family and nothing else; every other
23
+ // surface takes everything except it. Note this keys off the surface, never
24
+ // off the currently selected material the previous version did the latter,
25
+ // which meant an object could never get back to a family once it left it.
26
+ const materialTypeOptions = () => allmaterialTypes
27
+ .filter((type) => materialTypeFitsSurface(type, surface))
28
+ .map((type) => ({
29
+ value: type,
30
+ label: type.charAt(0).toUpperCase() + type.slice(1)
31
+ }));
50
32
  // is3dRiseShaderMaterial ?
51
33
  // dRiseShaderMaterialTypes.map((type: string) => ({
52
34
  // value: type,
@@ -1 +1 @@
1
- {"version":3,"file":"useObjectEdges.d.ts","sourceRoot":"","sources":["../../src/hooks/useObjectEdges.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAiK/D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,cAAc,GAAG,IAAI,CA6ChH"}
1
+ {"version":3,"file":"useObjectEdges.d.ts","sourceRoot":"","sources":["../../src/hooks/useObjectEdges.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAmH/D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,cAAc,GAAG,IAAI,CA6ChH"}
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useMemo, useRef, useState } from 'react';
2
- import { BoxGeometry, BufferGeometry, CylinderGeometry, EdgesGeometry, Group, LineSegments, Mesh, MeshBasicMaterial, Points, PointsMaterial, Quaternion, Vector3, Float32BufferAttribute, } from 'three';
2
+ import { BoxGeometry, CylinderGeometry, EdgesGeometry, Group, LineSegments, Mesh, MeshBasicMaterial, Points, PointsMaterial, Quaternion, Vector3, } from 'three';
3
3
  import { useFrame } from '@react-three/fiber';
4
- import { collectMeshes, computeThreshold, buildSubdividedEdgeGeometry, EDGES_GROUP_KEY, animateUniforms, cacheChildrenWithMaterials, findById } from '@3driseai/3drise-core';
4
+ import { collectMeshes, computeThreshold, buildSubdividedEdgeGeometry, buildSegmentQuadGeometry, EDGES_GROUP_KEY, animateUniforms, cacheChildrenWithMaterials, findById } from '@3driseai/3drise-core';
5
5
  import { useMaterialApplication } from './useMaterialApplication.js';
6
6
  import { resolveObject3D } from '../utils/resolveObject3D.js';
7
7
  ;
@@ -9,52 +9,6 @@ import { resolveObject3D } from '../utils/resolveObject3D.js';
9
9
  const DEFAULT_TUBE_RADIUS = 0.01;
10
10
  const DEFAULT_CUBE_SIZE = 0.02;
11
11
  // ─── Builders ────────────────────────────────────────────────────────────────
12
- function edgesGeoToQuadMesh(edgesGeo, thickness = 0.008) {
13
- const src = edgesGeo.attributes.position;
14
- const segCount = src.count / 2; // each segment = 2 vertices
15
- const positions = [];
16
- const uvs = [];
17
- const indices = [];
18
- const start = new Vector3();
19
- const end = new Vector3();
20
- const dir = new Vector3();
21
- const up = new Vector3(0, 1, 0);
22
- const right = new Vector3();
23
- for (let i = 0; i < segCount; i++) {
24
- start.fromBufferAttribute(src, i * 2);
25
- end.fromBufferAttribute(src, i * 2 + 1);
26
- dir.subVectors(end, start).normalize();
27
- // Perpendicular axis — fallback to X if segment is vertical
28
- right.crossVectors(dir, up).normalize();
29
- if (right.lengthSq() < 0.0001) {
30
- right.set(1, 0, 0);
31
- }
32
- const rx = right.x * thickness;
33
- const ry = right.y * thickness;
34
- const rz = right.z * thickness;
35
- const base = i * 4;
36
- // v0 start-bottom uv(0,0)
37
- positions.push(start.x - rx, start.y - ry, start.z - rz);
38
- uvs.push(0, 0);
39
- // v1 end-bottom uv(1,0)
40
- positions.push(end.x - rx, end.y - ry, end.z - rz);
41
- uvs.push(1, 0);
42
- // v2 start-top uv(0,1)
43
- positions.push(start.x + rx, start.y + ry, start.z + rz);
44
- uvs.push(0, 1);
45
- // v3 end-top uv(1,1)
46
- positions.push(end.x + rx, end.y + ry, end.z + rz);
47
- uvs.push(1, 1);
48
- indices.push(base, base + 1, base + 2);
49
- indices.push(base + 1, base + 3, base + 2);
50
- }
51
- const geo = new BufferGeometry();
52
- geo.setAttribute("position", new Float32BufferAttribute(positions, 3));
53
- geo.setAttribute("uv", new Float32BufferAttribute(uvs, 2));
54
- geo.setIndex(indices);
55
- geo.computeVertexNormals();
56
- return new Mesh(geo);
57
- }
58
12
  function buildLineGroup(object, localParent) {
59
13
  const g = new Group();
60
14
  for (const { mesh, matrixRelativeToRoot } of collectMeshes(object, localParent)) {
@@ -64,7 +18,7 @@ function buildLineGroup(object, localParent) {
64
18
  const quaternion = new Quaternion();
65
19
  const scale = new Vector3();
66
20
  matrixRelativeToRoot.decompose(position, quaternion, scale);
67
- const quadMesh = edgesGeoToQuadMesh(edgesGeo);
21
+ const quadMesh = new Mesh(buildSegmentQuadGeometry(edgesGeo.attributes.position));
68
22
  quadMesh.position.copy(position);
69
23
  quadMesh.quaternion.copy(quaternion);
70
24
  quadMesh.scale.copy(scale);
@@ -0,0 +1,9 @@
1
+ import type { MaterialSettings } from '@3driseai/3drise-core';
2
+ import type { ShaderEffect } from '@3driseai/3drise-core';
3
+ /** The one particles variant that is built dynamically rather than by the registry. */
4
+ export declare const SHADER_EFFECTS_VARIANT = "3dRiseShader";
5
+ /** True when at least one shader effect is switched on. */
6
+ export declare function hasActiveShaderEffects(effects?: ShaderEffect[] | null): boolean;
7
+ export declare function ownsShaderEffectsMaterial(materialSettings?: MaterialSettings | null, effects?: ShaderEffect[] | null): boolean;
8
+ export default ownsShaderEffectsMaterial;
9
+ //# sourceMappingURL=pointsMaterialOwnership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pointsMaterialOwnership.d.ts","sourceRoot":"","sources":["../../src/utils/pointsMaterialOwnership.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,uFAAuF;AACvF,eAAO,MAAM,sBAAsB,iBAAiB,CAAC;AAErD,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,OAAO,CAE/E;AAED,wBAAgB,yBAAyB,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,OAAO,CAM9H;AAED,eAAe,yBAAyB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /** The one particles variant that is built dynamically rather than by the registry. */
2
+ export const SHADER_EFFECTS_VARIANT = '3dRiseShader';
3
+ /** True when at least one shader effect is switched on. */
4
+ export function hasActiveShaderEffects(effects) {
5
+ return Array.isArray(effects) && effects.some((effect) => effect?.enabled);
6
+ }
7
+ export function ownsShaderEffectsMaterial(materialSettings, effects) {
8
+ // Effects only exist inside the shell, so their presence decides ownership.
9
+ if (hasActiveShaderEffects(effects))
10
+ return true;
11
+ const variant = materialSettings?.materialVariant;
12
+ return !variant || variant === SHADER_EFFECTS_VARIANT;
13
+ }
14
+ export default ownsShaderEffectsMaterial;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3driseai/3drise-viewer",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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.3"
42
+ "@3driseai/3drise-core": "^1.0.4"
43
43
  }
44
44
  }