@crystaldesign/real-time-viewer-next 26.11.0-beta.22 → 26.11.0-beta.24

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.
@@ -1,4 +1,4 @@
1
- import { p as parseElementKey, u as useRTVStore, b as bootPhase, D as DECK_COLLAR_LOAD_ID } from './index-920dd901.js';
1
+ import { p as parseElementKey, u as useRTVStore, b as bootPhase, D as DECK_COLLAR_LOAD_ID } from './index-fa3c5a24.js';
2
2
  import { useMemo, useRef, useEffect, useState, useCallback } from 'react';
3
3
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
4
4
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
@@ -1,5 +1,5 @@
1
1
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
2
- import './index-920dd901.js';
2
+ import { f as flushPendingBVH, e as collectCollisionTargets } from './index-fa3c5a24.js';
3
3
  import { useMemo, useEffect, useRef } from 'react';
4
4
  import { Billboard } from '@react-three/drei';
5
5
  import { useThree, useFrame } from '@react-three/fiber';
@@ -191,23 +191,29 @@ var OCCLUSION_MAX_DISTANCE = 10000;
191
191
  var OCCLUSION_NEAR = 0.1;
192
192
  /** Approximate angular radius for visibility sampling around the sun center ray. */
193
193
  var OCCLUSION_SAMPLE_RADIUS = 0.018;
194
- /** Smooth temporal response (higher = faster, lower = smoother). */
195
- var OCCLUSION_VISIBILITY_LERP = 0.2;
194
+ /**
195
+ * Temporal response toward LESS visibility (higher = faster, 1 = instant). Asymmetric on purpose:
196
+ * a light source winks out the moment something blocks it, so the fade-out has to feel immediate,
197
+ * while the partial-occlusion gradations still get a little smoothing against tap noise.
198
+ */
199
+ var OCCLUSION_HIDE_LERP = 0.55;
200
+ /**
201
+ * Temporal response toward MORE visibility. Slower than {@link OCCLUSION_HIDE_LERP}: the flare
202
+ * re-emerging along a geometry edge eases in instead of popping/flickering with every tap that
203
+ * clears.
204
+ */
205
+ var OCCLUSION_REVEAL_LERP = 0.2;
206
+ /** Temporal response of the anchor centroid shift — jitter matters more than speed here. */
207
+ var OCCLUSION_CENTROID_LERP = 0.2;
196
208
  /** Tiny alpha threshold to fully hide flare when effectively occluded. */
197
209
  var OCCLUSION_MIN_VISIBLE_ALPHA = 0.01;
198
210
  /**
199
211
  * Run the 9-tap occlusion kernel only every Nth frame. Visibility is already
200
- * temporally smoothed (see {@link OCCLUSION_VISIBILITY_LERP}), so a 3-frame
201
- * cadence is visually indistinguishable from per-frame sampling — but cuts the
202
- * raycast cost by ~3x.
212
+ * temporally smoothed (see {@link OCCLUSION_HIDE_LERP} / {@link OCCLUSION_REVEAL_LERP}),
213
+ * so a 3-frame cadence is visually indistinguishable from per-frame sampling —
214
+ * but cuts the raycast cost by ~3x.
203
215
  */
204
216
  var OCCLUSION_SAMPLE_INTERVAL = 3;
205
- /**
206
- * How many frames an occluder list is reused before being rebuilt. The scene
207
- * graph rarely changes between frames, so walking it every frame just to
208
- * collect raycastable meshes is wasted work.
209
- */
210
- var OCCLUDER_LIST_REBUILD_INTERVAL = 12;
211
217
  /**
212
218
  * How much the flare anchor shifts toward the visible side of the sun disc.
213
219
  * 0 = disabled, 1 = full centroid shift.
@@ -228,6 +234,11 @@ var OCCLUSION_SAMPLE_OFFSETS = [[0, 0, 2], [1, 0, 1], [-1, 0, 1], [0, 1, 1], [0,
228
234
  * Does **not** use `three/examples/jsm/objects/Lensflare.js`: that helper reads the framebuffer for
229
235
  * occlusion (`copyFramebufferToTexture`), which fails or zeroes alpha when the scene is drawn through
230
236
  * pmndrs `EffectComposer` / multisampled targets.
237
+ *
238
+ * Occlusion taps run against `collectCollisionTargets` (article + BVH decor) — the same scoped set
239
+ * the camera-collision probe uses, and for the same reason: raycasting the whole scene puts every
240
+ * blocked ray on three.js' O(triangles) `Mesh.raycast`, which stalls weak CPUs exactly while the
241
+ * sun sits behind geometry. Visual aids, drag previews and the sky shells drop out with it.
231
242
  */
232
243
  function SunLensflare(_ref) {
233
244
  var sunDirection = _ref.sunDirection,
@@ -238,7 +249,9 @@ function SunLensflare(_ref) {
238
249
  _ref$sizeScale = _ref.sizeScale,
239
250
  sizeScale = _ref$sizeScale === void 0 ? 1.0 : _ref$sizeScale,
240
251
  _ref$occlusionEnabled = _ref.occlusionEnabled,
241
- occlusionEnabled = _ref$occlusionEnabled === void 0 ? true : _ref$occlusionEnabled;
252
+ occlusionEnabled = _ref$occlusionEnabled === void 0 ? true : _ref$occlusionEnabled,
253
+ _ref$occludersRevisio = _ref.occludersRevision,
254
+ occludersRevision = _ref$occludersRevisio === void 0 ? 0 : _ref$occludersRevisio;
242
255
  var scene = useThree(function (s) {
243
256
  return s.scene;
244
257
  });
@@ -290,16 +303,23 @@ function SunLensflare(_ref) {
290
303
  var sunAnchorPos = useMemo(function () {
291
304
  return new Vector3();
292
305
  }, []);
306
+ // near/far never change, and `firstHitOnly` (three-mesh-bvh) makes every mesh with a
307
+ // `boundsTree` report at most one hit — the taps ask a boolean, so any hit past the
308
+ // first is wasted work.
293
309
  var raycaster = useMemo(function () {
294
- return new Raycaster();
310
+ var r = new Raycaster();
311
+ r.near = OCCLUSION_NEAR;
312
+ r.far = OCCLUSION_MAX_DISTANCE;
313
+ r.firstHitOnly = true;
314
+ return r;
295
315
  }, []);
296
316
  var hitBufferRef = useRef([]);
297
317
  var smoothedVisibilityRef = useRef(1);
298
318
  var smoothedCentroidXRef = useRef(0);
299
319
  var smoothedCentroidYRef = useRef(0);
300
320
  var frameCounterRef = useRef(0);
301
- var occluderListRef = useRef([]);
302
- var occluderListAgeRef = useRef(Number.MAX_SAFE_INTEGER);
321
+ var occludersRef = useRef([]);
322
+ var occludersBuiltForRef = useRef(null);
303
323
  var flareR = Math.max(10, FLARE_FORWARD_DISTANCE * distanceScale);
304
324
  // Chromatic per-ghost tint composed with the overall flare colour.
305
325
  var elementTints = useMemo(function () {
@@ -334,7 +354,14 @@ function SunLensflare(_ref) {
334
354
  var frame = frameCounterRef.current++;
335
355
  var shouldSample = inFront && frame % OCCLUSION_SAMPLE_INTERVAL === 0;
336
356
  if (shouldSample) {
337
- var occluders = getOccluders(scene, g, occluderListRef, occluderListAgeRef);
357
+ if (occludersBuiltForRef.current !== occludersRevision) {
358
+ occludersBuiltForRef.current = occludersRevision;
359
+ // Build queued BVHs first: a `boundsTree` is what marks decor collidable, and the
360
+ // article's trees keep the taps below off the O(triangles) raycast path.
361
+ flushPendingBVH();
362
+ occludersRef.current = collectCollisionTargets(scene).meshes;
363
+ }
364
+ var occluders = occludersRef.current;
338
365
  var totalWeight = 0;
339
366
  var visibleWeight = 0;
340
367
  var weightedVisibleX = 0;
@@ -346,15 +373,17 @@ function SunLensflare(_ref) {
346
373
  oy = _OCCLUSION_SAMPLE_OFF[1],
347
374
  w = _OCCLUSION_SAMPLE_OFF[2];
348
375
  sampleDirScratch.copy(sunDirNormalized).addScaledVector(rightScratch, ox * OCCLUSION_SAMPLE_RADIUS).addScaledVector(upScratch, oy * OCCLUSION_SAMPLE_RADIUS).normalize();
349
- raycaster.near = OCCLUSION_NEAR;
350
- raycaster.far = OCCLUSION_MAX_DISTANCE;
351
376
  raycaster.set(cameraWorldPos, sampleDirScratch);
352
377
  var hits = hitBufferRef.current;
353
- hits.length = 0;
354
- // `recursive: false` — `occluders` is already a flat list of leaf
355
- // meshes filtered to skip InstancedMesh / SkinnedMesh / opt-outs.
356
- raycaster.intersectObjects(occluders, false, hits);
357
- var visible = hits.length > 0 ? 0 : 1;
378
+ var blocked = false;
379
+ // Any hit answers the tap — stop at the first blocking mesh instead of collecting
380
+ // (and sorting) every intersection along the whole ray.
381
+ for (var m = 0; m < occluders.length && !blocked; m++) {
382
+ hits.length = 0;
383
+ raycaster.intersectObject(occluders[m], false, hits);
384
+ blocked = hits.length > 0;
385
+ }
386
+ var visible = blocked ? 0 : 1;
358
387
  totalWeight += w;
359
388
  visibleWeight += visible * w;
360
389
  weightedVisibleX += ox * visible * w;
@@ -366,13 +395,14 @@ function SunLensflare(_ref) {
366
395
  var targetCentroidX = visibleWeight > 1e-4 ? weightedVisibleX / visibleWeight : 0;
367
396
  var targetCentroidY = visibleWeight > 1e-4 ? weightedVisibleY / visibleWeight : 0;
368
397
  var prevVis = smoothedVisibilityRef.current;
369
- smoothedVisibilityRef.current = Math.min(1, Math.max(0, prevVis + (targetVisibility - prevVis) * OCCLUSION_VISIBILITY_LERP));
370
- smoothedCentroidXRef.current += (targetCentroidX - smoothedCentroidXRef.current) * OCCLUSION_VISIBILITY_LERP;
371
- smoothedCentroidYRef.current += (targetCentroidY - smoothedCentroidYRef.current) * OCCLUSION_VISIBILITY_LERP;
398
+ var visibilityLerp = targetVisibility < prevVis ? OCCLUSION_HIDE_LERP : OCCLUSION_REVEAL_LERP;
399
+ smoothedVisibilityRef.current = Math.min(1, Math.max(0, prevVis + (targetVisibility - prevVis) * visibilityLerp));
400
+ smoothedCentroidXRef.current += (targetCentroidX - smoothedCentroidXRef.current) * OCCLUSION_CENTROID_LERP;
401
+ smoothedCentroidYRef.current += (targetCentroidY - smoothedCentroidYRef.current) * OCCLUSION_CENTROID_LERP;
372
402
  } else if (!inFront) {
373
403
  // Sun behind camera: decay toward zero so we don't hold stale visibility
374
404
  // when the user spins around.
375
- smoothedVisibilityRef.current *= 1 - OCCLUSION_VISIBILITY_LERP;
405
+ smoothedVisibilityRef.current *= 1 - OCCLUSION_HIDE_LERP;
376
406
  }
377
407
 
378
408
  // Shift anchor toward the visible side of the sun disc so left/right occlusion
@@ -450,34 +480,4 @@ function viewZInFront(viewZ) {
450
480
  return viewZ < 0;
451
481
  }
452
482
 
453
- /**
454
- * Collect a flat list of meshes worth raycasting against for sun occlusion.
455
- * The result is cached for {@link OCCLUDER_LIST_REBUILD_INTERVAL} frames so a
456
- * static scene only walks the graph occasionally. Excludes:
457
- * - the lensflare's own root (avoids self-hits)
458
- * - invisible objects
459
- * - `userData.lensflare === 'no-occlusion'` opt-outs
460
- * - `InstancedMesh` (scattered trees) — per-instance raycasts are O(N)
461
- * and these are decorative, not real occluders for the sun disc
462
- * - `SkinnedMesh` — skin-bound raycasts force skeleton matrix work
463
- */
464
- function getOccluders(scene, flareRoot, listRef, ageRef) {
465
- ageRef.current += 1;
466
- if (ageRef.current < OCCLUDER_LIST_REBUILD_INTERVAL && listRef.current.length > 0) {
467
- return listRef.current;
468
- }
469
- ageRef.current = 0;
470
- var list = listRef.current;
471
- list.length = 0;
472
- scene.traverseVisible(function (object) {
473
- if (flareRoot.getObjectById(object.id) !== undefined) return;
474
- if (object.userData.lensflare === 'no-occlusion') return;
475
- var anyObj = object;
476
- if (anyObj.isInstancedMesh === true) return;
477
- if (anyObj.isSkinnedMesh === true) return;
478
- if (anyObj.isMesh === true) list.push(object);
479
- });
480
- return list;
481
- }
482
-
483
483
  export { SunLensflare };
@@ -1,4 +1,4 @@
1
- import { w as withRenderTarget, s as setCausticReceiversSuspended, a as waterUniforms, d as disableWaterUniforms, u as useRTVStore, F as FRAME_ORDER, c as sunAnglesToDirection, l as lightingSunTowardDirection } from './index-920dd901.js';
1
+ import { w as withRenderTarget, s as setCausticReceiversSuspended, a as waterUniforms, d as disableWaterUniforms, u as useRTVStore, F as FRAME_ORDER, c as sunAnglesToDirection, l as lightingSunTowardDirection } from './index-fa3c5a24.js';
2
2
  import { useRef, useMemo, useEffect, useCallback } from 'react';
3
3
  import { useThree, useFrame } from '@react-three/fiber';
4
4
  import { Vector3, WebGLRenderTarget, HalfFloatType, RGBAFormat, LinearFilter, OrthographicCamera, ShaderMaterial, DoubleSide, MeshStandardMaterial, Box3, NoToneMapping, Frustum, Matrix4, Vector4, Vector2, Plane, Scene, Mesh, PlaneGeometry, ClampToEdgeWrapping, UnsignedByteType, MeshBasicMaterial, Color } from 'three';