@neurodyn/react-room-viewer 0.0.22 → 0.0.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.
Files changed (2) hide show
  1. package/dist/index.js +167 -112
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import { Swiper, SwiperSlide } from "swiper/react";
19
19
  import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
20
20
  import { Clone, DragControls, Environment, Html, useGLTF, useProgress, useTexture } from "@react-three/drei";
21
21
  import { Canvas, useFrame, useLoader, useThree } from "@react-three/fiber";
22
- import { ACESFilmicToneMapping, Box3, BufferGeometry, CanvasTexture, ClampToEdgeWrapping, Color, DataTexture, DoubleSide, Float32BufferAttribute, LinearFilter, LoadingManager, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, PerspectiveCamera, Plane, Quaternion, RGBAFormat, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderTarget } from "three";
22
+ import { ACESFilmicToneMapping, Box3, BufferGeometry, CanvasTexture, ClampToEdgeWrapping, Color, DataTexture, DoubleSide, Float32BufferAttribute, LinearFilter, LoadingManager, MathUtils, Matrix4, Mesh, MeshBasicMaterial, PerspectiveCamera, Plane, Quaternion, RGBAFormat, RedFormat, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderTarget } from "three";
23
23
  import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
24
24
  import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
25
25
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
@@ -5409,6 +5409,10 @@ var __webpack_modules__ = {
5409
5409
  color: var(--v-text-dark);
5410
5410
  }
5411
5411
 
5412
+ .data-\\[variant\\=destructive\\]\\:\\[\\&_svg\\:not\\(\\[class\\*\\=text-\\]\\)\\]\\:text-inherit[data-variant="destructive"] svg:not([class*="text-"]) {
5413
+ color: inherit;
5414
+ }
5415
+
5412
5416
  .\\[\\&_tr\\]\\:border-b tr {
5413
5417
  border-bottom-style: var(--tw-border-style);
5414
5418
  border-bottom-width: 1px;
@@ -7795,7 +7799,7 @@ const RESOURCE_TYPE_PROFILES = {
7795
7799
  chrome: 'immersive-room',
7796
7800
  presentation: 'immersive-panels',
7797
7801
  manipulation: 'panel-group',
7798
- catalogLabel: 'product-name',
7802
+ productNameSource: 'collection',
7799
7803
  catalogTitle: 'wall-panel',
7800
7804
  introduction: 'focus-resource',
7801
7805
  identity: 'modelled-resource',
@@ -7815,7 +7819,7 @@ const RESOURCE_TYPE_PROFILES = {
7815
7819
  chrome: 'framed-scene',
7816
7820
  presentation: 'framed-default',
7817
7821
  manipulation: 'single-resource',
7818
- catalogLabel: 'material-name',
7822
+ productNameSource: 'material',
7819
7823
  catalogTitle: 'title',
7820
7824
  introduction: null,
7821
7825
  identity: 'textured-resource',
@@ -7832,7 +7836,7 @@ const RESOURCE_TYPE_PROFILES = {
7832
7836
  chrome: 'immersive-room',
7833
7837
  presentation: 'immersive-default',
7834
7838
  manipulation: 'drag-with-rotation-handle',
7835
- catalogLabel: 'material-name',
7839
+ productNameSource: 'collection',
7836
7840
  catalogTitle: 'rugs',
7837
7841
  introduction: null,
7838
7842
  identity: 'modelled-resource',
@@ -7849,7 +7853,7 @@ const RESOURCE_TYPE_PROFILES = {
7849
7853
  chrome: 'immersive-room',
7850
7854
  presentation: 'immersive-default',
7851
7855
  manipulation: 'rotate-on-floor',
7852
- catalogLabel: 'material-name',
7856
+ productNameSource: 'collection',
7853
7857
  catalogTitle: 'flooring',
7854
7858
  introduction: null,
7855
7859
  identity: 'modelled-resource',
@@ -8807,13 +8811,7 @@ function getListingKey(object) {
8807
8811
  return object.kit?.uuid ?? object.uuid ?? object.tinuuid ?? '';
8808
8812
  }
8809
8813
  function getSizedDesignKey(object) {
8810
- const meta = decodeRugSizeMeta(object.description);
8811
- const name = object.name?.trim();
8812
- const sizeSuffix = meta ? ` — ${meta.size}` : null;
8813
- if (name && sizeSuffix && name.endsWith(sizeSuffix)) {
8814
- const designName = name.slice(0, -sizeSuffix.length).trim();
8815
- if (designName) return `rug-name:${designName}`;
8816
- }
8814
+ if (object.kit?.uuid) return object.kit.uuid;
8817
8815
  return `object:${getListingKey(object)}`;
8818
8816
  }
8819
8817
  const PRODUCT_COMPOSITION_ANSWERS = {
@@ -8835,16 +8833,13 @@ const PRODUCT_COMPOSITION_ANSWERS = {
8835
8833
  };
8836
8834
  const MATERIAL_ROLE_ANSWERS = {
8837
8835
  'product-identity': {
8838
- catalogsEveryMaterial: true,
8839
- namesProduct: true
8836
+ catalogsEveryMaterial: true
8840
8837
  },
8841
8838
  finish: {
8842
- catalogsEveryMaterial: false,
8843
- namesProduct: false
8839
+ catalogsEveryMaterial: false
8844
8840
  },
8845
8841
  placeholder: {
8846
- catalogsEveryMaterial: false,
8847
- namesProduct: false
8842
+ catalogsEveryMaterial: false
8848
8843
  }
8849
8844
  };
8850
8845
  function resolveProductCompositionAnswers(type) {
@@ -8869,8 +8864,7 @@ function resolveResourceIdentityAnswers(type) {
8869
8864
  return RESOURCE_IDENTITY_ANSWERS[resolveResourceProfile(type).identity];
8870
8865
  }
8871
8866
  function getResourceProductName(type, object, material, fallback = '') {
8872
- const { namesProduct } = resolveMaterialRoleAnswers(type);
8873
- return namesProduct ? material?.name ?? object?.name ?? object?.kit?.name ?? fallback : object?.kit?.name ?? object?.name ?? material?.name ?? fallback;
8867
+ return 'material' === resolveResourceProfile(type).productNameSource ? material?.name ?? object?.name ?? object?.kit?.name ?? fallback : object?.kit?.name?.trim() || object?.name?.trim() || material?.name?.trim() || fallback;
8874
8868
  }
8875
8869
  function getMaterialId(material) {
8876
8870
  return material.hid ?? '';
@@ -8982,13 +8976,14 @@ function useRoomViewerMaterialCatalog({ includeEmbeddedObject = false } = {}) {
8982
8976
  const { requiresMaterialModel } = resolveResourceIdentityAnswers(type);
8983
8977
  const { getCollapseKey } = resolveProductCompositionAnswers(type);
8984
8978
  const { catalogsEveryMaterial } = resolveMaterialRoleAnswers(type);
8985
- const { catalogLabel, composition } = resolveResourceProfile(type);
8979
+ const { composition, productNameSource } = resolveResourceProfile(type);
8986
8980
  const fetchedObjects = (0, __rspack_external_react.useMemo)(()=>data?.pages.flatMap((page)=>page.objs ?? []) ?? [], [
8987
8981
  data
8988
8982
  ]);
8989
- const unresolvedObjects = (0, __rspack_external_react.useMemo)(()=>'color-design' === composition ? fetchedObjects.filter((object)=>object.tinuuid && !object.kit?.uuid && object.materials?.[0]?.native?.glbUrl) : [], [
8990
- composition,
8991
- fetchedObjects
8983
+ const unresolvedObjects = (0, __rspack_external_react.useMemo)(()=>'material' === productNameSource ? [] : fetchedObjects.filter((object)=>object.tinuuid && (!object.kit?.uuid || !object.kit.name?.trim()) && (!requiresMaterialModel || object.materials?.[0]?.native?.glbUrl)), [
8984
+ fetchedObjects,
8985
+ productNameSource,
8986
+ requiresMaterialModel
8992
8987
  ]);
8993
8988
  const collectionQueries = useQueries({
8994
8989
  queries: unresolvedObjects.map((object)=>({
@@ -9015,7 +9010,10 @@ function useRoomViewerMaterialCatalog({ includeEmbeddedObject = false } = {}) {
9015
9010
  return [
9016
9011
  {
9017
9012
  ...object,
9018
- kit: query?.data?.kit ?? object.kit
9013
+ kit: query?.data?.kit ? {
9014
+ ...object.kit,
9015
+ ...query.data.kit
9016
+ } : object.kit
9019
9017
  }
9020
9018
  ];
9021
9019
  });
@@ -9043,17 +9041,16 @@ function useRoomViewerMaterialCatalog({ includeEmbeddedObject = false } = {}) {
9043
9041
  return catalogMaterials?.map((material)=>{
9044
9042
  const materialId = getMaterialId(material);
9045
9043
  if (!materialId || !object.tinuuid) return null;
9044
+ const productName = getResourceProductName(type, object, material, name);
9046
9045
  return {
9047
9046
  materialId,
9048
- name: ('material-name' === catalogLabel ? material.name : object.kit?.name ?? object.name) ?? material.name ?? '',
9049
- productName: getResourceProductName(type, object, material, name),
9047
+ productName,
9050
9048
  material,
9051
9049
  tinuuid: object.tinuuid
9052
9050
  };
9053
9051
  }).filter((item)=>null !== item) ?? [];
9054
9052
  }), [
9055
9053
  catalogsEveryMaterial,
9056
- catalogLabel,
9057
9054
  composition,
9058
9055
  objects,
9059
9056
  type,
@@ -14676,7 +14673,7 @@ function dropdown_menu_DropdownMenuItem({ className, inset, variant = 'default',
14676
14673
  "data-slot": "dropdown-menu-item",
14677
14674
  "data-inset": inset,
14678
14675
  "data-variant": variant,
14679
- className: utils_cn('relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden hover:bg-background-base focus:bg-background-base data-[highlighted]:bg-background-base data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:hover:bg-destructive/10 data-[highlighted][data-variant=destructive]:bg-destructive/10 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*=text-])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0', inset && 'pl-8', className),
14676
+ className: utils_cn('relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden hover:bg-background-base focus:bg-background-base data-[highlighted]:bg-background-base data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:hover:bg-destructive/10 data-[highlighted][data-variant=destructive]:bg-destructive/10 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*=text-])]:text-muted-foreground data-[variant=destructive]:[&_svg:not([class*=text-])]:text-inherit [&_svg]:pointer-events-none [&_svg]:shrink-0', inset && 'pl-8', className),
14680
14677
  ...props
14681
14678
  });
14682
14679
  }
@@ -15345,6 +15342,106 @@ function useFlooringRotation(enabled, isPointerOnSurface, roomId) {
15345
15342
  setRotation
15346
15343
  ]);
15347
15344
  }
15345
+ const MASK_EDGE_START = 0.4;
15346
+ const MASK_EDGE_END = 0.7;
15347
+ const MASK_DISCARD_ALPHA = 0.001;
15348
+ const UPSCALE = 2;
15349
+ const BLUR_SIGMA = 3;
15350
+ const BLUR_RADIUS = 9;
15351
+ const plane_visibility_mask_kernel = new Float32Array(2 * BLUR_RADIUS + 1);
15352
+ let kernelSum = 0;
15353
+ for(let offset = -BLUR_RADIUS; offset <= BLUR_RADIUS; offset++){
15354
+ const weight = Math.exp(-offset * offset / (2 * BLUR_SIGMA * BLUR_SIGMA));
15355
+ plane_visibility_mask_kernel[offset + BLUR_RADIUS] = weight;
15356
+ kernelSum += weight;
15357
+ }
15358
+ for(let index = 0; index < plane_visibility_mask_kernel.length; index++)plane_visibility_mask_kernel[index] /= kernelSum;
15359
+ function plane_visibility_mask_clamp(value1, max) {
15360
+ return Math.max(0, Math.min(max, value1));
15361
+ }
15362
+ function createPlaneVisibilityMask(map, planeId) {
15363
+ const width = map.width * UPSCALE;
15364
+ const height = map.height * UPSCALE;
15365
+ const upscaled = new Float32Array(width * height);
15366
+ const horizontal = new Float32Array(width * height);
15367
+ const data = new Uint8Array(width * height);
15368
+ let minX = map.width;
15369
+ let minY = map.height;
15370
+ let maxX = -1;
15371
+ let maxY = -1;
15372
+ for(let y = 0; y < map.height; y++)for(let x = 0; x < map.width; x++)if (map.data[y * map.width + x] === planeId) {
15373
+ minX = Math.min(minX, x);
15374
+ minY = Math.min(minY, y);
15375
+ maxX = Math.max(maxX, x);
15376
+ maxY = Math.max(maxY, y);
15377
+ }
15378
+ const margin = BLUR_RADIUS + UPSCALE;
15379
+ const startX = maxX < 0 ? width : Math.max(0, minX * UPSCALE - margin);
15380
+ const startY = maxY < 0 ? height : Math.max(0, minY * UPSCALE - margin);
15381
+ const endX = maxX < 0 ? -1 : Math.min(width - 1, (maxX + 1) * UPSCALE + margin);
15382
+ const endY = maxY < 0 ? -1 : Math.min(height - 1, (maxY + 1) * UPSCALE + margin);
15383
+ for(let y = startY; y <= endY; y++){
15384
+ const sourceY = (y + 0.5) / UPSCALE - 0.5;
15385
+ const sourceY0 = Math.floor(sourceY);
15386
+ const y0 = plane_visibility_mask_clamp(sourceY0, map.height - 1);
15387
+ const y1 = plane_visibility_mask_clamp(sourceY0 + 1, map.height - 1);
15388
+ const fy = sourceY - sourceY0;
15389
+ for(let x = startX; x <= endX; x++){
15390
+ const sourceX = (x + 0.5) / UPSCALE - 0.5;
15391
+ const sourceX0 = Math.floor(sourceX);
15392
+ const x0 = plane_visibility_mask_clamp(sourceX0, map.width - 1);
15393
+ const x1 = plane_visibility_mask_clamp(sourceX0 + 1, map.width - 1);
15394
+ const fx = sourceX - sourceX0;
15395
+ const top = (map.data[y0 * map.width + x0] === planeId ? 1 - fx : 0) + (map.data[y0 * map.width + x1] === planeId ? fx : 0);
15396
+ const bottom = (map.data[y1 * map.width + x0] === planeId ? 1 - fx : 0) + (map.data[y1 * map.width + x1] === planeId ? fx : 0);
15397
+ upscaled[y * width + x] = top * (1 - fy) + bottom * fy;
15398
+ }
15399
+ }
15400
+ for(let y = startY; y <= endY; y++)for(let x = startX; x <= endX; x++){
15401
+ let value1 = 0;
15402
+ for(let offset = -BLUR_RADIUS; offset <= BLUR_RADIUS; offset++)value1 += upscaled[y * width + plane_visibility_mask_clamp(x + offset, width - 1)] * plane_visibility_mask_kernel[offset + BLUR_RADIUS];
15403
+ horizontal[y * width + x] = value1;
15404
+ }
15405
+ for(let y = startY; y <= endY; y++)for(let x = startX; x <= endX; x++){
15406
+ let value1 = 0;
15407
+ for(let offset = -BLUR_RADIUS; offset <= BLUR_RADIUS; offset++)value1 += horizontal[plane_visibility_mask_clamp(y + offset, height - 1) * width + x] * plane_visibility_mask_kernel[offset + BLUR_RADIUS];
15408
+ data[y * width + x] = Math.round(255 * value1);
15409
+ }
15410
+ const texture = new DataTexture(data, width, height, RedFormat);
15411
+ texture.minFilter = LinearFilter;
15412
+ texture.magFilter = LinearFilter;
15413
+ texture.wrapS = ClampToEdgeWrapping;
15414
+ texture.wrapT = ClampToEdgeWrapping;
15415
+ texture.generateMipmaps = false;
15416
+ texture.unpackAlignment = 1;
15417
+ texture.needsUpdate = true;
15418
+ return {
15419
+ data,
15420
+ width,
15421
+ height,
15422
+ texture
15423
+ };
15424
+ }
15425
+ function samplePlaneVisibilityMask(mask, u, v) {
15426
+ if (u < 0 || u > 1 || v < 0 || v > 1) return 0;
15427
+ const x = u * mask.width - 0.5;
15428
+ const y = v * mask.height - 0.5;
15429
+ const sourceX0 = Math.floor(x);
15430
+ const sourceY0 = Math.floor(y);
15431
+ const x0 = plane_visibility_mask_clamp(sourceX0, mask.width - 1);
15432
+ const y0 = plane_visibility_mask_clamp(sourceY0, mask.height - 1);
15433
+ const x1 = plane_visibility_mask_clamp(sourceX0 + 1, mask.width - 1);
15434
+ const y1 = plane_visibility_mask_clamp(sourceY0 + 1, mask.height - 1);
15435
+ const fx = x - Math.floor(x);
15436
+ const fy = y - Math.floor(y);
15437
+ const top = mask.data[y0 * mask.width + x0] * (1 - fx) + mask.data[y0 * mask.width + x1] * fx;
15438
+ const bottom = mask.data[y1 * mask.width + x0] * (1 - fx) + mask.data[y1 * mask.width + x1] * fx;
15439
+ return (top * (1 - fy) + bottom * fy) / 255;
15440
+ }
15441
+ function planeVisibilityAlpha(value1) {
15442
+ const t = plane_visibility_mask_clamp((value1 - MASK_EDGE_START) / (MASK_EDGE_END - MASK_EDGE_START), 1);
15443
+ return t * t * (3 - 2 * t);
15444
+ }
15348
15445
  new Set();
15349
15446
  function warnOnce(key, message) {}
15350
15447
  const RESOURCE_BASE_NORMAL = new Vector3(0, 0, 1);
@@ -15483,11 +15580,12 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15483
15580
  const placementKey = `${roomId}:${[
15484
15581
  ...surfaces
15485
15582
  ].sort().join(',')}`;
15486
- const activePlaneIdUniform = (0, __rspack_external_react.useMemo)(()=>({
15487
- value: 0
15583
+ const activeMaskTextureUniform = (0, __rspack_external_react.useMemo)(()=>({
15584
+ value: null
15488
15585
  }), []);
15489
15586
  const [placementState, dispatchPlacementState] = (0, __rspack_external_react.useReducer)(reduceRoomScenePlacementState, placementKey, createInitialRoomScenePlacementState);
15490
15587
  const activePlaneIdRef = (0, __rspack_external_react.useRef)(null);
15588
+ const planeMasksRef = (0, __rspack_external_react.useRef)(new Map());
15491
15589
  const planeMapRef = (0, __rspack_external_react.useRef)(new Map());
15492
15590
  const pointerDragOffsetRef = (0, __rspack_external_react.useRef)(null);
15493
15591
  const isCurrentPlacementKey = placementState.placementKey === placementKey;
@@ -15520,7 +15618,16 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15520
15618
  placementKey
15521
15619
  });
15522
15620
  decodePlaneIdsDataPng(scene.planeIds.dataPng, scene.planeIds.width, scene.planeIds.height).then((map)=>{
15523
- if (!isCancelled) dispatchPlacementState({
15621
+ if (isCancelled) return;
15622
+ const masks = new Map();
15623
+ try {
15624
+ for (const plane of scene.planes)if (surfaces.includes(plane.kind) && plane.id > 0) masks.set(plane.id, createPlaneVisibilityMask(map, plane.id));
15625
+ } catch (error) {
15626
+ masks.forEach((mask)=>mask.texture.dispose());
15627
+ throw error;
15628
+ }
15629
+ planeMasksRef.current = masks;
15630
+ dispatchPlacementState({
15524
15631
  type: 'set-plane-id-map',
15525
15632
  placementKey,
15526
15633
  planeIdMap: map
@@ -15534,10 +15641,16 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15534
15641
  });
15535
15642
  return ()=>{
15536
15643
  isCancelled = true;
15644
+ planeMasksRef.current.forEach((mask)=>mask.texture.dispose());
15645
+ planeMasksRef.current = new Map();
15646
+ activeMaskTextureUniform.value = null;
15537
15647
  };
15538
15648
  }, [
15649
+ activeMaskTextureUniform,
15539
15650
  placementKey,
15540
- scene.planeIds
15651
+ scene.planeIds,
15652
+ scene.planes,
15653
+ surfaces
15541
15654
  ]);
15542
15655
  (0, __rspack_external_react.useEffect)(()=>{
15543
15656
  dispatchPlacementState({
@@ -15545,17 +15658,17 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15545
15658
  placementKey
15546
15659
  });
15547
15660
  activePlaneIdRef.current = null;
15548
- activePlaneIdUniform.value = 0;
15661
+ activeMaskTextureUniform.value = null;
15549
15662
  pointerDragOffsetRef.current = null;
15550
15663
  }, [
15551
- activePlaneIdUniform,
15664
+ activeMaskTextureUniform,
15552
15665
  placementKey
15553
15666
  ]);
15554
15667
  const updatePoseFromPlane = (0, __rspack_external_react.useCallback)((mesh, point, pointerDragOffset = null)=>{
15555
15668
  const pointerPose = createPoseFromPlane(mesh, point, camera);
15556
15669
  const pose = pointerDragOffset ? applyPointerDragOffset(pointerPose, pointerDragOffset) : pointerPose;
15557
15670
  activePlaneIdRef.current = pose.activePlaneId;
15558
- activePlaneIdUniform.value = pose.activePlaneId;
15671
+ activeMaskTextureUniform.value = planeMasksRef.current.get(pose.activePlaneId)?.texture ?? null;
15559
15672
  dispatchPlacementState({
15560
15673
  type: 'set-resource-pose',
15561
15674
  placementKey,
@@ -15563,7 +15676,7 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15563
15676
  });
15564
15677
  invalidate();
15565
15678
  }, [
15566
- activePlaneIdUniform,
15679
+ activeMaskTextureUniform,
15567
15680
  camera,
15568
15681
  invalidate,
15569
15682
  placementKey
@@ -15594,7 +15707,7 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15594
15707
  });
15595
15708
  if (!pose) return;
15596
15709
  activePlaneIdRef.current = pose.activePlaneId;
15597
- activePlaneIdUniform.value = pose.activePlaneId;
15710
+ activeMaskTextureUniform.value = planeMasksRef.current.get(pose.activePlaneId)?.texture ?? null;
15598
15711
  dispatchPlacementState({
15599
15712
  type: 'set-resource-pose',
15600
15713
  placementKey,
@@ -15602,7 +15715,7 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15602
15715
  });
15603
15716
  invalidate();
15604
15717
  }, [
15605
- activePlaneIdUniform,
15718
+ activeMaskTextureUniform,
15606
15719
  allowedPlanes,
15607
15720
  camera,
15608
15721
  invalidate,
@@ -15636,19 +15749,14 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15636
15749
  pointerDragOffsetRef.current = null;
15637
15750
  }, []);
15638
15751
  const isPointVisible = (0, __rspack_external_react.useCallback)((point)=>{
15639
- if (!planeIdMap) return false;
15752
+ const activePlaneId = activePlaneIdRef.current;
15753
+ const mask = null === activePlaneId ? null : planeMasksRef.current.get(activePlaneId);
15754
+ if (!mask) return false;
15640
15755
  const projected = point.clone().project(camera);
15641
- const x = Math.floor((projected.x + 1) * 0.5 * planeIdMap.width);
15642
- const y = Math.floor((1 - projected.y) * 0.5 * planeIdMap.height);
15643
- for(let dy = -2; dy <= 2; dy++)for(let dx = -2; dx <= 2; dx++){
15644
- const px = Math.max(0, Math.min(planeIdMap.width - 1, x + dx));
15645
- const py = Math.max(0, Math.min(planeIdMap.height - 1, y + dy));
15646
- if (planeIdMap.data[py * planeIdMap.width + px] === activePlaneIdRef.current) return true;
15647
- }
15648
- return false;
15756
+ const value1 = samplePlaneVisibilityMask(mask, (projected.x + 1) * 0.5, (1 - projected.y) * 0.5);
15757
+ return planeVisibilityAlpha(value1) > MASK_DISCARD_ALPHA;
15649
15758
  }, [
15650
- camera,
15651
- planeIdMap
15759
+ camera
15652
15760
  ]);
15653
15761
  const isPointerOnSurface = (0, __rspack_external_react.useCallback)((point)=>{
15654
15762
  const id = planeIdMap ? findPlaneIdAtPointer(point, planeIdMap) : null;
@@ -15659,7 +15767,7 @@ function useRoomScenePlacement({ camera, invalidate, pointer, raycaster, roomId,
15659
15767
  ]);
15660
15768
  return {
15661
15769
  isPointerOnSurface,
15662
- activePlaneIdUniform,
15770
+ activeMaskTextureUniform,
15663
15771
  isPointVisible,
15664
15772
  failure,
15665
15773
  handleDrag,
@@ -15816,16 +15924,10 @@ function createRoomLightingTexture(planeIds, shading, neutral = 128) {
15816
15924
  const CACHE_KEY = 'neurodyn-room-scene-v5';
15817
15925
  const GLSL_DEFINE = 'NEURODYN_ROOM_SCENE_PATCH';
15818
15926
  function patchShader(shader, patch, shading) {
15819
- shader.uniforms.uPlaneIds = {
15820
- value: patch.planeIdsTexture
15821
- };
15927
+ shader.uniforms.uMask = patch.activeMaskTextureUniform;
15822
15928
  shader.uniforms.uRoomLighting = {
15823
15929
  value: patch.lightingTexture
15824
15930
  };
15825
- shader.uniforms.uActiveId = patch.activePlaneIdUniform;
15826
- shader.uniforms.uPlaneIdsSize = {
15827
- value: patch.planeIdsSize
15828
- };
15829
15931
  shader.uniforms.uViewport = {
15830
15932
  value: patch.viewport
15831
15933
  };
@@ -15833,54 +15935,25 @@ function patchShader(shader, patch, shading) {
15833
15935
  const inject = `
15834
15936
  {
15835
15937
  vec2 _neurodynRoomUv = gl_FragCoord.xy / uViewport;
15836
- float _neurodynRoomCoverage = neurodynRoomScenePlaneCoverage(_neurodynRoomUv);
15938
+ float _neurodynRoomMask = texture2D(uMask, vec2(_neurodynRoomUv.x, 1.0 - _neurodynRoomUv.y)).r;
15939
+ float _neurodynRoomCoverage = smoothstep(${MASK_EDGE_START}, ${MASK_EDGE_END}, _neurodynRoomMask);
15837
15940
 
15838
- if (_neurodynRoomCoverage <= 0.001) discard;
15941
+ if (_neurodynRoomCoverage <= ${MASK_DISCARD_ALPHA}) discard;
15839
15942
 
15840
15943
  ${shading ? `
15841
15944
  float _neurodynRoomGain = min(texture2D(uRoomLighting, vec2(_neurodynRoomUv.x, 1.0 - _neurodynRoomUv.y)).r * (255.0 / 128.0), ${patch.maxRoomGain.toFixed(3)});
15842
15945
  outgoingLight = (outgoingLight - ${emissive}) * _neurodynRoomGain + ${emissive};
15843
15946
  ` : ''}
15844
- diffuseColor.a *= smoothstep(0.0, 0.6, _neurodynRoomCoverage);
15947
+ diffuseColor.a *= _neurodynRoomCoverage;
15845
15948
  }
15846
15949
  `;
15847
15950
  const fragmentWithUniforms = shader.fragmentShader.replace('#include <common>', `
15848
15951
  #include <common>
15849
15952
  #ifndef ${GLSL_DEFINE}
15850
15953
  #define ${GLSL_DEFINE}
15851
- uniform sampler2D uPlaneIds;
15954
+ uniform sampler2D uMask;
15852
15955
  uniform sampler2D uRoomLighting;
15853
- uniform float uActiveId;
15854
- uniform vec2 uPlaneIdsSize;
15855
15956
  uniform vec2 uViewport;
15856
-
15857
- float neurodynRoomScenePlaneMatch(vec2 uv) {
15858
- float id = floor(texture2D(uPlaneIds, uv).r * 255.0 + 0.5);
15859
- return 1.0 - step(0.5, abs(id - uActiveId));
15860
- }
15861
-
15862
- // uPlaneIds is a low-res nearest-filtered id map, so its edges are blocky.
15863
- // A single-texel tent filter isn't wide enough to hide that — it just
15864
- // softens each block's corner, which reads as a staircase. Supersample
15865
- // a wider neighbourhood (5x5 taps, 2-texel radius) with Gaussian-like
15866
- // falloff so the boundary is both denser-sampled and properly blurred.
15867
- float neurodynRoomScenePlaneCoverage(vec2 uv) {
15868
- vec2 texel = 1.0 / uPlaneIdsSize;
15869
- float coverage = 0.0;
15870
- float totalWeight = 0.0;
15871
-
15872
- for (int y = -2; y <= 2; y++) {
15873
- for (int x = -2; x <= 2; x++) {
15874
- vec2 offset = vec2(float(x), float(y));
15875
- float weight = 1.0 / (1.0 + dot(offset, offset));
15876
-
15877
- coverage += neurodynRoomScenePlaneMatch(uv + offset * texel) * weight;
15878
- totalWeight += weight;
15879
- }
15880
- }
15881
-
15882
- return coverage / totalWeight;
15883
- }
15884
15957
  #endif
15885
15958
  `);
15886
15959
  if (fragmentWithUniforms.includes('#include <output_fragment>')) {
@@ -17151,10 +17224,6 @@ function Room() {
17151
17224
  });
17152
17225
  useFlooringRotation('rotate-on-floor' === resolveResourceProfile(type).manipulation, scenePlacement.isPointerOnSurface, room.id);
17153
17226
  const viewport = (0, __rspack_external_react.useMemo)(()=>new Vector2(1, 1), []);
17154
- const planeIdsSize = (0, __rspack_external_react.useMemo)(()=>new Vector2(scene.planeIds.width, scene.planeIds.height), [
17155
- scene.planeIds.height,
17156
- scene.planeIds.width
17157
- ]);
17158
17227
  const lightingTexture = (0, __rspack_external_react.useMemo)(()=>createRoomLightingTexture(planeIdsTexture, shadingTexture, scene.shading?.neutral), [
17159
17228
  planeIdsTexture,
17160
17229
  shadingTexture,
@@ -17164,16 +17233,12 @@ function Room() {
17164
17233
  lightingTexture
17165
17234
  ]);
17166
17235
  const materialPatch = (0, __rspack_external_react.useMemo)(()=>({
17167
- planeIdsTexture,
17168
17236
  lightingTexture,
17169
- planeIdsSize,
17170
17237
  viewport,
17171
- activePlaneIdUniform: scenePlacement.activePlaneIdUniform,
17238
+ activeMaskTextureUniform: scenePlacement.activeMaskTextureUniform,
17172
17239
  maxRoomGain: MAX_ROOM_GAIN
17173
17240
  }), [
17174
- scenePlacement.activePlaneIdUniform,
17175
- planeIdsSize,
17176
- planeIdsTexture,
17241
+ scenePlacement.activeMaskTextureUniform,
17177
17242
  lightingTexture,
17178
17243
  viewport
17179
17244
  ]);
@@ -17189,16 +17254,6 @@ function Room() {
17189
17254
  invalidate,
17190
17255
  scene.camera.fovYDeg
17191
17256
  ]);
17192
- (0, __rspack_external_react.useLayoutEffect)(()=>{
17193
- planeIdsTexture.minFilter = NearestFilter;
17194
- planeIdsTexture.magFilter = NearestFilter;
17195
- planeIdsTexture.wrapS = ClampToEdgeWrapping;
17196
- planeIdsTexture.wrapT = ClampToEdgeWrapping;
17197
- planeIdsTexture.generateMipmaps = false;
17198
- planeIdsTexture.needsUpdate = true;
17199
- }, [
17200
- planeIdsTexture
17201
- ]);
17202
17257
  (0, __rspack_external_react.useLayoutEffect)(()=>{
17203
17258
  gl.getDrawingBufferSize(viewport);
17204
17259
  viewport.set(Math.max(1, viewport.x), Math.max(1, viewport.y));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@neurodyn/react-room-viewer",
3
3
  "type": "module",
4
- "version": "0.0.22",
4
+ "version": "0.0.24",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },