@thatopen/components 3.4.6 → 3.4.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.
package/dist/index.mjs CHANGED
@@ -7,9 +7,9 @@ var __publicField = (obj, key, value) => {
7
7
  import * as FRAGS from "@thatopen/fragments";
8
8
  import { DataMap as DataMap$1, FragmentsModels, DataSet as DataSet$1 } from "@thatopen/fragments";
9
9
  import * as THREE from "three";
10
- import { Box3, Matrix4, BufferAttribute, Vector3, Vector2, Plane, Line3, Triangle, REVISION, BackSide, DoubleSide, FrontSide, Ray, Mesh, Line, LineSegments, LineLoop, Points, BatchedMesh, Sphere, OrthographicCamera, WebGLRenderTarget, Scene, Color, ShaderMaterial, GLSL3, NoBlending, Vector4, MathUtils, BufferGeometry } from "three";
10
+ import { Box3, Matrix4, BufferAttribute, Vector3, Line3, Plane, Vector2, Triangle, BackSide, DoubleSide, REVISION, Ray, FrontSide, Mesh, LineSegments, LineLoop, Line, Points, Sphere, BatchedMesh, OrthographicCamera, WebGLRenderTarget, Scene, Color, ShaderMaterial, Vector4, NoBlending, GLSL3, MathUtils, BufferGeometry } from "three";
11
11
  import * as THREEWEBGPU from "three/webgpu";
12
- import { instancedArray, Fn, mat4, Loop, int, vec3, vec4, float, cross, If, Continue, max, min, normalize, select, abs, mix, uint, atomicAdd, instanceIndex } from "three/tsl";
12
+ import { instancedArray, Fn, instanceIndex, mat4, Loop, int, vec3, vec4, float, cross, If, Continue, max, min, normalize, select, abs, mix, uint, atomicAdd } from "three/tsl";
13
13
  import * as WEBIFC from "web-ifc";
14
14
  import CameraControls from "camera-controls";
15
15
  import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
@@ -177,6 +177,15 @@ class BaseRenderer extends BaseWorldItem {
177
177
  * The list of [clipping planes](https://threejs.org/docs/#api/en/renderers/WebGLRenderer.clippingPlanes) used by this instance of the renderer.
178
178
  */
179
179
  __publicField(this, "clippingPlanes", []);
180
+ /**
181
+ * The default `isLocal` value used by {@link setPlane} when the caller
182
+ * doesn't pass one. When `true`, planes added without an explicit
183
+ * `isLocal` stay out of the global `three.clippingPlanes` list and are
184
+ * clipped per-material instead. The {@link Clipper} sets this to mirror
185
+ * its own `localClippingPlanes` flag, so consumers like `Views` that add
186
+ * their own planes automatically inherit the active clipping mode.
187
+ */
188
+ __publicField(this, "localClippingPlanes", false);
180
189
  }
181
190
  /**
182
191
  * Updates the clipping planes and triggers the `onClippingPlanesUpdated` event.
@@ -202,7 +211,7 @@ class BaseRenderer extends BaseWorldItem {
202
211
  * The `three.clippingPlanes` property is then updated to reflect the current state of the `clippingPlanes` array,
203
212
  * excluding any planes marked as local.
204
213
  */
205
- setPlane(active, plane, isLocal) {
214
+ setPlane(active, plane, isLocal = this.localClippingPlanes) {
206
215
  plane.isLocal = isLocal;
207
216
  const index = this.clippingPlanes.indexOf(plane);
208
217
  if (active && index === -1) {
@@ -213,6 +222,7 @@ class BaseRenderer extends BaseWorldItem {
213
222
  this.three.clippingPlanes = this.clippingPlanes.filter(
214
223
  (plane2) => !plane2.isLocal
215
224
  );
225
+ this.updateClippingPlanes();
216
226
  }
217
227
  }
218
228
  const _Disposer = class _Disposer extends Component {
@@ -9467,9 +9477,11 @@ class VisibilityCuller {
9467
9477
  const tilesX = Math.ceil(pixelWidth / maxTextureSize);
9468
9478
  const tilesY = Math.ceil(pixelHeight / maxTextureSize);
9469
9479
  target.setSize(Math.ceil(pixelWidth / tilesX), Math.ceil(pixelHeight / tilesY));
9480
+ const yMargin = Math.max(size.y, 1) * 0.01;
9470
9481
  camera.rotation.x = -Math.PI / 2;
9471
- camera.far = box.max.y - box.min.y + camera.near;
9472
- camera.position.y = box.max.y + camera.near;
9482
+ camera.near = 0;
9483
+ camera.far = size.y + 2 * yMargin;
9484
+ camera.position.y = box.max.y + yMargin;
9473
9485
  const idScene = new Scene();
9474
9486
  const originalMaterials = /* @__PURE__ */ new Map();
9475
9487
  const originalParents = /* @__PURE__ */ new Map();
@@ -9503,7 +9515,7 @@ class VisibilityCuller {
9503
9515
  const tileMaxZ = tileMinZ + stepZ;
9504
9516
  const tileCenterX = (tileMinX + tileMaxX) / 2;
9505
9517
  const tileCenterZ = (tileMinZ + tileMaxZ) / 2;
9506
- camera.position.set(tileCenterX, box.max.y, tileCenterZ);
9518
+ camera.position.set(tileCenterX, box.max.y + yMargin, tileCenterZ);
9507
9519
  const halfWidth = stepX / 2;
9508
9520
  const halfHeight = stepZ / 2;
9509
9521
  camera.left = -halfWidth;
@@ -9552,6 +9564,11 @@ class IDMaterial extends ShaderMaterial {
9552
9564
  super({
9553
9565
  glslVersion: GLSL3,
9554
9566
  blending: NoBlending,
9567
+ // A flat item (a slab) seen straight down shows only one face; if its
9568
+ // winding puts the back face toward the camera it wouldn't rasterize and
9569
+ // the item would be culled. The pass only cares about coverage, so render
9570
+ // both sides.
9571
+ side: DoubleSide,
9555
9572
  uniforms: {
9556
9573
  objectId: { value: new Vector4() }
9557
9574
  },
@@ -12542,6 +12559,19 @@ const _FastModelPicker = class _FastModelPicker {
12542
12559
  * restored after.
12543
12560
  */
12544
12561
  __publicField(this, "_hiddenLods", []);
12562
+ /**
12563
+ * The meshes each model owns, i.e. excluding those belonging to a
12564
+ * model nested inside it. A delta model's object is parented under
12565
+ * its parent model's object (see the fragments EditHelper), while
12566
+ * both are listed as separate models, so subtree membership alone
12567
+ * does not determine ownership.
12568
+ *
12569
+ * Populated in {@link applyIdMaterial}, consumed by
12570
+ * {@link renderPickPass} to isolate one model per render without
12571
+ * relying on root visibility. A nested model can't be isolated by
12572
+ * its root, since hiding the parent's root culls the child too.
12573
+ */
12574
+ __publicField(this, "_ownMeshes", /* @__PURE__ */ new Map());
12545
12575
  if (!world.renderer) {
12546
12576
  throw new Error("A renderer is needed for the FastModelPicker to work!");
12547
12577
  }
@@ -12782,14 +12812,14 @@ const _FastModelPicker = class _FastModelPicker {
12782
12812
  applyIdMaterial() {
12783
12813
  const fragments = this.components.get(FragmentsManager);
12784
12814
  const byteToModel = /* @__PURE__ */ new Map();
12815
+ const modelRoots = this.getModelRoots();
12816
+ this._ownMeshes.clear();
12785
12817
  let nextByte = 1;
12786
12818
  for (const [modelId, model] of fragments.list) {
12787
12819
  if (nextByte > _FastModelPicker.MAX_MODELS)
12788
12820
  break;
12789
- const byte = nextByte;
12790
- nextByte += 1;
12791
- let any = false;
12792
- model.object.traverse((child) => {
12821
+ const owned = [];
12822
+ this.traverseOwn(model.object, modelRoots, (child) => {
12793
12823
  if (!(child instanceof THREE.Mesh))
12794
12824
  return;
12795
12825
  const geom = child.geometry;
@@ -12806,13 +12836,44 @@ const _FastModelPicker = class _FastModelPicker {
12806
12836
  const original = child.material;
12807
12837
  const len = Array.isArray(original) ? original.length : 1;
12808
12838
  child.material = new Array(len).fill(this._idMaterial);
12809
- any = true;
12839
+ owned.push(child);
12810
12840
  });
12811
- if (any)
12812
- byteToModel.set(byte, modelId);
12841
+ if (!owned.length)
12842
+ continue;
12843
+ this._ownMeshes.set(modelId, owned);
12844
+ byteToModel.set(nextByte, modelId);
12845
+ nextByte += 1;
12813
12846
  }
12814
12847
  return byteToModel;
12815
12848
  }
12849
+ /**
12850
+ * The object roots of every loaded model.
12851
+ */
12852
+ getModelRoots() {
12853
+ const fragments = this.components.get(FragmentsManager);
12854
+ const roots = /* @__PURE__ */ new Set();
12855
+ for (const [, model] of fragments.list)
12856
+ roots.add(model.object);
12857
+ return roots;
12858
+ }
12859
+ /**
12860
+ * Like `Object3D.traverse`, but prunes the subtree of any nested model
12861
+ * root, so each mesh is visited exactly once, by the model that owns
12862
+ * it. A delta model's object hangs under its parent model's object
12863
+ * while being a model in its own right; a plain `traverse` from the
12864
+ * parent would walk into it.
12865
+ */
12866
+ traverseOwn(root, modelRoots, callback) {
12867
+ const stack = [root];
12868
+ while (stack.length) {
12869
+ const node = stack.pop();
12870
+ if (node !== root && modelRoots.has(node))
12871
+ continue;
12872
+ callback(node);
12873
+ for (const child of node.children)
12874
+ stack.push(child);
12875
+ }
12876
+ }
12816
12877
  /**
12817
12878
  * Renders the world scene to the id target one model at a time,
12818
12879
  * flipping the `modelByte` uniform between renders. The depth buffer
@@ -12903,21 +12964,47 @@ const _FastModelPicker = class _FastModelPicker {
12903
12964
  nonBimVisibility.set(child, child.visible);
12904
12965
  child.visible = false;
12905
12966
  });
12906
- const bimVisibilityBefore = /* @__PURE__ */ new Map();
12967
+ const wasOnScreen = /* @__PURE__ */ new Map();
12968
+ for (const [modelId, obj] of objectsByModel) {
12969
+ let visible = true;
12970
+ let node = obj;
12971
+ while (node) {
12972
+ if (!node.visible) {
12973
+ visible = false;
12974
+ break;
12975
+ }
12976
+ node = node.parent;
12977
+ }
12978
+ wasOnScreen.set(modelId, visible);
12979
+ }
12980
+ const rootVisibilityBefore = /* @__PURE__ */ new Map();
12907
12981
  for (const obj of objectsByModel.values()) {
12908
- bimVisibilityBefore.set(obj, obj.visible);
12909
- obj.visible = false;
12982
+ rootVisibilityBefore.set(obj, obj.visible);
12983
+ obj.visible = true;
12984
+ }
12985
+ const meshVisibilityBefore = /* @__PURE__ */ new Map();
12986
+ for (const meshes of this._ownMeshes.values()) {
12987
+ for (const mesh of meshes) {
12988
+ meshVisibilityBefore.set(mesh, mesh.visible);
12989
+ mesh.visible = false;
12990
+ }
12910
12991
  }
12911
12992
  for (const [byte, modelId] of byteToModel) {
12912
- const obj = objectsByModel.get(modelId);
12913
- if (!obj)
12993
+ if (!wasOnScreen.get(modelId))
12914
12994
  continue;
12915
- obj.visible = true;
12995
+ const meshes = this._ownMeshes.get(modelId);
12996
+ if (!meshes)
12997
+ continue;
12998
+ for (const mesh of meshes)
12999
+ mesh.visible = meshVisibilityBefore.get(mesh);
12916
13000
  material.uniforms.modelByte.value = byte;
12917
13001
  renderer.render(scene, camera);
12918
- obj.visible = false;
13002
+ for (const mesh of meshes)
13003
+ mesh.visible = false;
12919
13004
  }
12920
- for (const [obj, was] of bimVisibilityBefore)
13005
+ for (const [mesh, was] of meshVisibilityBefore)
13006
+ mesh.visible = was;
13007
+ for (const [obj, was] of rootVisibilityBefore)
12921
13008
  obj.visible = was;
12922
13009
  for (const [obj, was] of nonBimVisibility)
12923
13010
  obj.visible = was;
@@ -12958,8 +13045,9 @@ const _FastModelPicker = class _FastModelPicker {
12958
13045
  const prevClearAlpha = renderer.getClearAlpha();
12959
13046
  const matSwap = /* @__PURE__ */ new Map();
12960
13047
  const hiddenLines = [];
13048
+ const modelRoots = this.getModelRoots();
12961
13049
  for (const [, model] of fragments.list) {
12962
- model.object.traverse((child) => {
13050
+ this.traverseOwn(model.object, modelRoots, (child) => {
12963
13051
  if (!(child instanceof THREE.Mesh))
12964
13052
  return;
12965
13053
  const geom = child.geometry;
@@ -13029,6 +13117,7 @@ const _FastModelPicker = class _FastModelPicker {
13029
13117
  mesh.material = mat;
13030
13118
  }
13031
13119
  this._originalMaterials.clear();
13120
+ this._ownMeshes.clear();
13032
13121
  for (const obj of this._hiddenLods)
13033
13122
  obj.visible = true;
13034
13123
  this._hiddenLods.length = 0;
@@ -13405,12 +13494,32 @@ class SnapResolver {
13405
13494
  */
13406
13495
  __publicField(this, "maxCacheSize", 1e3);
13407
13496
  __publicField(this, "components");
13497
+ __publicField(this, "_precision", "half");
13408
13498
  // Map insertion order is recency order (LRU). On every read we
13409
13499
  // delete + re-set the entry to bump it to most-recent; on every
13410
13500
  // write we drop oldest entries until size <= maxCacheSize.
13411
13501
  __publicField(this, "cache", /* @__PURE__ */ new Map());
13412
13502
  this.components = components;
13413
13503
  }
13504
+ /**
13505
+ * Numeric precision for cached face/edge/vertex buffers.
13506
+ *
13507
+ * `"half"` uses Float32Array for lower memory footprint.
13508
+ * `"full"` uses Float64Array for higher numeric precision.
13509
+ * Use `"full"` for workflows that require higher geometric fidelity.
13510
+ *
13511
+ * Changing this value clears the snap cache so subsequent requests
13512
+ * are rebuilt with the new precision.
13513
+ */
13514
+ get precision() {
13515
+ return this._precision;
13516
+ }
13517
+ set precision(value) {
13518
+ if (value === this._precision)
13519
+ return;
13520
+ this._precision = value;
13521
+ this.clear();
13522
+ }
13414
13523
  /** {@link Disposable.dispose} */
13415
13524
  dispose() {
13416
13525
  this.cache.clear();
@@ -13550,8 +13659,8 @@ class SnapResolver {
13550
13659
  }
13551
13660
  if (polygons.length === 0)
13552
13661
  return null;
13553
- const edges = deriveUniqueEdges(polygons);
13554
- const vertices = deriveUniqueVertices(polygons);
13662
+ const edges = deriveUniqueEdges(polygons, this._precision);
13663
+ const vertices = deriveUniqueVertices(polygons, this._precision);
13555
13664
  return { faces: polygons, edges, vertices };
13556
13665
  }
13557
13666
  collectPolygonsFromShell(shell, worldMatrix, out) {
@@ -13562,7 +13671,8 @@ class SnapResolver {
13562
13671
  const collect = (indices) => {
13563
13672
  if (indices.length < 3)
13564
13673
  return;
13565
- const flat = new Float32Array(indices.length * 3);
13674
+ const Ctor = this._precision === "full" ? Float64Array : Float32Array;
13675
+ const flat = new Ctor(indices.length * 3);
13566
13676
  for (let i = 0; i < indices.length; i++) {
13567
13677
  const p = points[indices[i]];
13568
13678
  tmp.set(p[0], p[1], p[2]).applyMatrix4(worldMatrix);
@@ -13671,19 +13781,33 @@ class SnapResolver {
13671
13781
  return null;
13672
13782
  let best = null;
13673
13783
  let bestAbs = Infinity;
13784
+ let bestContains = false;
13674
13785
  for (const f of faces) {
13675
- const signed = f.normal.x * point.x + f.normal.y * point.y + f.normal.z * point.z - f.d;
13786
+ const signed = this.signedDistance(f, point);
13676
13787
  const abs2 = Math.abs(signed);
13677
- if (abs2 <= this.maxDistance && abs2 < bestAbs) {
13788
+ if (abs2 > this.maxDistance)
13789
+ continue;
13790
+ const contains = this.containsPoint(f, this.projectOntoPlane(point, f, signed));
13791
+ if (bestContains && !contains)
13792
+ continue;
13793
+ if (contains && !bestContains) {
13794
+ bestContains = true;
13795
+ bestAbs = abs2;
13796
+ best = f;
13797
+ continue;
13798
+ }
13799
+ if (abs2 < bestAbs) {
13678
13800
  bestAbs = abs2;
13679
13801
  best = f;
13680
13802
  }
13681
13803
  }
13682
13804
  if (!best)
13683
13805
  return null;
13684
- const projected = point.clone().sub(best.normal.clone().multiplyScalar(
13685
- best.normal.x * point.x + best.normal.y * point.y + best.normal.z * point.z - best.d
13686
- ));
13806
+ const projected = this.projectOntoPlane(
13807
+ point,
13808
+ best,
13809
+ this.signedDistance(best, point)
13810
+ );
13687
13811
  return {
13688
13812
  result: {
13689
13813
  point: projected,
@@ -13694,6 +13818,50 @@ class SnapResolver {
13694
13818
  }
13695
13819
  };
13696
13820
  }
13821
+ signedDistance(face, point) {
13822
+ return face.normal.x * point.x + face.normal.y * point.y + face.normal.z * point.z - face.d;
13823
+ }
13824
+ projectOntoPlane(point, face, signed) {
13825
+ return point.clone().sub(face.normal.clone().multiplyScalar(signed));
13826
+ }
13827
+ /**
13828
+ * Even-odd point-in-polygon test, run on the polygon projected onto its
13829
+ * dominant plane. `point` is assumed to already lie on the face's plane.
13830
+ */
13831
+ containsPoint(face, point) {
13832
+ const { points, normal } = face;
13833
+ const count = points.length / 3;
13834
+ if (count < 3)
13835
+ return false;
13836
+ const ax = Math.abs(normal.x);
13837
+ const ay = Math.abs(normal.y);
13838
+ const az = Math.abs(normal.z);
13839
+ let a = 0;
13840
+ let b = 1;
13841
+ if (ax >= ay && ax >= az) {
13842
+ a = 1;
13843
+ b = 2;
13844
+ } else if (ay >= az) {
13845
+ a = 0;
13846
+ b = 2;
13847
+ }
13848
+ const px = point.getComponent(a);
13849
+ const py = point.getComponent(b);
13850
+ let inside = false;
13851
+ for (let i = 0, j = count - 1; i < count; j = i, i += 1) {
13852
+ const xi = points[i * 3 + a];
13853
+ const yi = points[i * 3 + b];
13854
+ const xj = points[j * 3 + a];
13855
+ const yj = points[j * 3 + b];
13856
+ const straddles = yi > py !== yj > py;
13857
+ if (!straddles)
13858
+ continue;
13859
+ const x = (xj - xi) * (py - yi) / (yj - yi) + xi;
13860
+ if (px < x)
13861
+ inside = !inside;
13862
+ }
13863
+ return inside;
13864
+ }
13697
13865
  }
13698
13866
  const _tmpMatrix = new THREE.Matrix4();
13699
13867
  const _tmpX = new THREE.Vector3();
@@ -13761,7 +13929,7 @@ function fanTriangulation(vertexCount) {
13761
13929
  }
13762
13930
  return out;
13763
13931
  }
13764
- function deriveUniqueEdges(faces) {
13932
+ function deriveUniqueEdges(faces, precision) {
13765
13933
  const seen = /* @__PURE__ */ new Set();
13766
13934
  const collected = [];
13767
13935
  for (const f of faces) {
@@ -13778,9 +13946,10 @@ function deriveUniqueEdges(faces) {
13778
13946
  collected.push(ax, ay, az, bx, by, bz);
13779
13947
  }
13780
13948
  }
13781
- return new Float32Array(collected);
13949
+ const Ctor = precision === "full" ? Float64Array : Float32Array;
13950
+ return new Ctor(collected);
13782
13951
  }
13783
- function deriveUniqueVertices(faces) {
13952
+ function deriveUniqueVertices(faces, precision) {
13784
13953
  const seen = /* @__PURE__ */ new Set();
13785
13954
  const collected = [];
13786
13955
  for (const f of faces) {
@@ -13795,7 +13964,8 @@ function deriveUniqueVertices(faces) {
13795
13964
  collected.push(x, y, z);
13796
13965
  }
13797
13966
  }
13798
- return new Float32Array(collected);
13967
+ const Ctor = precision === "full" ? Float64Array : Float32Array;
13968
+ return new Ctor(collected);
13799
13969
  }
13800
13970
  function quantKey(x, y, z) {
13801
13971
  return `${Math.round(x * VERTEX_QUANT)},${Math.round(y * VERTEX_QUANT)},${Math.round(z * VERTEX_QUANT)}`;
@@ -14091,6 +14261,9 @@ class SimpleWorld extends Base {
14091
14261
  */
14092
14262
  __publicField(this, "enabled", true);
14093
14263
  __publicField(this, "_dynamicAnchor", false);
14264
+ // Bumped on every anchor press so a slow geometry pick that resolves after a
14265
+ // newer press is ignored (see onPointerDown).
14266
+ __publicField(this, "_anchorRequestId", 0);
14094
14267
  /**
14095
14268
  * A unique identifier for the world. Is not meant to be changed at any moment.
14096
14269
  */
@@ -14102,21 +14275,36 @@ class SimpleWorld extends Base {
14102
14275
  __publicField(this, "_scene");
14103
14276
  __publicField(this, "_camera");
14104
14277
  __publicField(this, "_renderer", null);
14105
- __publicField(this, "onPointerDown", async (event) => {
14278
+ __publicField(this, "onPointerDown", (event) => {
14106
14279
  if (!this.camera.hasCameraControls()) {
14107
14280
  throw new Error(
14108
14281
  "World: can't set dynamic anchor if the camera doesn't have controls."
14109
14282
  );
14110
14283
  }
14284
+ if (event.button !== 0)
14285
+ return;
14286
+ const position = this.getPointerPosition(event);
14287
+ if (!position)
14288
+ return;
14111
14289
  const caster = this.components.get(Raycasters).get(this);
14112
- const result = await caster.castRay();
14113
- if (result && result.point && event.button === 0) {
14290
+ const planePoint = this.getPlaneAnchor(caster, position);
14291
+ if (planePoint) {
14114
14292
  this.camera.controls.setOrbitPoint(
14115
- result.point.x,
14116
- result.point.y,
14117
- result.point.z
14293
+ planePoint.x,
14294
+ planePoint.y,
14295
+ planePoint.z
14118
14296
  );
14119
14297
  }
14298
+ const requestId = ++this._anchorRequestId;
14299
+ caster.castRay({ position }).then((result) => {
14300
+ const isStale = requestId !== this._anchorRequestId;
14301
+ if (isStale || this.isDisposing || !this._dynamicAnchor)
14302
+ return;
14303
+ if (!(result == null ? void 0 : result.point) || !this.camera.hasCameraControls())
14304
+ return;
14305
+ const { x, y, z } = result.point;
14306
+ this.camera.controls.setOrbitPoint(x, y, z);
14307
+ });
14120
14308
  });
14121
14309
  __publicField(this, "_defaultCamera");
14122
14310
  }
@@ -14135,10 +14323,46 @@ class SimpleWorld extends Base {
14135
14323
  } else {
14136
14324
  container.removeEventListener("pointerdown", this.onPointerDown);
14137
14325
  }
14326
+ this._dynamicAnchor = value;
14138
14327
  }
14139
14328
  get dynamicAnchor() {
14140
14329
  return this._dynamicAnchor;
14141
14330
  }
14331
+ /**
14332
+ * The pointer position in normalized device coordinates, derived from the
14333
+ * event rather than the raycaster's mouse: that one only tracks pointermove
14334
+ * and touchstart, so on a bare pointerdown it would still report the center of
14335
+ * the viewport.
14336
+ */
14337
+ getPointerPosition(event) {
14338
+ var _a;
14339
+ const dom = (_a = this.renderer) == null ? void 0 : _a.three.domElement;
14340
+ if (!dom)
14341
+ return null;
14342
+ const bounds = dom.getBoundingClientRect();
14343
+ return new THREE.Vector2(
14344
+ (event.clientX - bounds.left) / (bounds.right - bounds.left) * 2 - 1,
14345
+ -((event.clientY - bounds.top) / (bounds.bottom - bounds.top)) * 2 + 1
14346
+ );
14347
+ }
14348
+ /**
14349
+ * The point where the pointer ray crosses the plane that contains the current
14350
+ * orbit target and faces the camera. Used as the anchor when the pointer
14351
+ * doesn't land on any geometry.
14352
+ */
14353
+ getPlaneAnchor(caster, position) {
14354
+ if (!this.camera.hasCameraControls())
14355
+ return null;
14356
+ const camera = this.camera.three;
14357
+ caster.three.setFromCamera(position, camera);
14358
+ const target = this.camera.controls.getTarget(new THREE.Vector3());
14359
+ const normal = camera.getWorldDirection(new THREE.Vector3());
14360
+ const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(
14361
+ normal,
14362
+ target
14363
+ );
14364
+ return caster.three.ray.intersectPlane(plane, new THREE.Vector3());
14365
+ }
14142
14366
  get defaultCamera() {
14143
14367
  if (!this._defaultCamera) {
14144
14368
  throw new Error("World: there is no default camera defined.");
@@ -14241,8 +14465,16 @@ class SimpleWorld extends Base {
14241
14465
  }
14242
14466
  /** {@link Disposable.dispose} */
14243
14467
  dispose(disposeResources = true) {
14468
+ var _a;
14469
+ if (this.isDisposing)
14470
+ return;
14244
14471
  this.enabled = false;
14245
14472
  this.isDisposing = true;
14473
+ const container = (_a = this.renderer) == null ? void 0 : _a.three.domElement.parentElement;
14474
+ if (container) {
14475
+ container.removeEventListener("pointerdown", this.onPointerDown);
14476
+ }
14477
+ this._dynamicAnchor = false;
14246
14478
  this.scene.onWorldChanged.trigger({ world: this, action: "removed" });
14247
14479
  this.camera.onWorldChanged.trigger({ world: this, action: "removed" });
14248
14480
  if (this.renderer) {
@@ -15489,16 +15721,23 @@ const _Components = class _Components {
15489
15721
  dispose() {
15490
15722
  this.enabled = false;
15491
15723
  let fragments;
15724
+ let worlds;
15492
15725
  for (const [_id, component] of this.list) {
15493
15726
  component.enabled = false;
15494
15727
  if (_id === FragmentsManager.uuid) {
15495
15728
  fragments = component;
15496
15729
  continue;
15497
15730
  }
15731
+ if (_id === Worlds.uuid) {
15732
+ worlds = component;
15733
+ continue;
15734
+ }
15498
15735
  if (component.isDisposeable()) {
15499
15736
  component.dispose();
15500
15737
  }
15501
15738
  }
15739
+ if (worlds == null ? void 0 : worlds.isDisposeable())
15740
+ worlds.dispose();
15502
15741
  fragments == null ? void 0 : fragments.dispose();
15503
15742
  this._timer.dispose();
15504
15743
  this.onDisposed.trigger();
@@ -15975,7 +16214,7 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js
15975
16214
  JSZip uses the library pako released under the MIT license :
15976
16215
  https://github.com/nodeca/pako/blob/main/LICENSE
15977
16216
  */
15978
- (function(module, exports) {
16217
+ (function(module, exports$1) {
15979
16218
  !function(e) {
15980
16219
  module.exports = e();
15981
16220
  }(function() {
@@ -19033,33 +19272,31 @@ const _Topic = class _Topic {
19033
19272
  serialize() {
19034
19273
  var _a, _b;
19035
19274
  const version = this._managerVersion;
19275
+ const isV3 = version === "3";
19036
19276
  const topic = {
19037
19277
  $Guid: this.guid,
19038
19278
  $TopicType: this.type,
19039
19279
  $TopicStatus: this.status,
19040
19280
  $ServerAssignedId: this.serverAssignedId,
19041
19281
  Title: this.title,
19042
- CreationAuthor: this.creationAuthor,
19043
- CreationDate: this.creationDate.toISOString(),
19044
19282
  Priority: this.priority,
19045
19283
  Index: version === "2.1" ? this.index : void 0,
19284
+ Labels: isV3 ? { Label: this.createLabelTags() } : this.createLabelTags(),
19285
+ CreationDate: this.creationDate.toISOString(),
19286
+ CreationAuthor: this.creationAuthor,
19046
19287
  ModifiedDate: (_a = this.modifiedDate) == null ? void 0 : _a.toISOString(),
19047
19288
  ModifiedAuthor: this.modifiedAuthor,
19048
19289
  DueDate: (_b = this.dueDate) == null ? void 0 : _b.toISOString(),
19049
19290
  AssignedTo: this.assignedTo,
19050
- Description: this.description,
19051
19291
  Stage: this.stage,
19052
- DocumentReferences: version === "3" ? { DocumentReference: this.createDocumentReferencesTag(version) } : void 0,
19053
- RelatedTopics: version === "3" ? { RelatedTopic: this.createRelatedTopicTags() } : void 0,
19054
- RelatedTopic: version === "2.1" ? this.createRelatedTopicTags() : void 0,
19055
- Labels: version === "3" ? { Label: this.createLabelTags() } : void 0,
19056
- Viewpoints: version === "3" ? { ViewPoint: this.createViewpointTags() } : void 0,
19057
- Comments: version === "3" ? { Comment: this.createCommentTags() } : void 0
19292
+ Description: this.description,
19293
+ DocumentReferences: isV3 ? { DocumentReference: this.createDocumentReferencesTag(version) } : void 0,
19294
+ DocumentReference: isV3 ? void 0 : this.createDocumentReferencesTag(version),
19295
+ RelatedTopics: isV3 ? { RelatedTopic: this.createRelatedTopicTags() } : void 0,
19296
+ RelatedTopic: isV3 ? void 0 : this.createRelatedTopicTags(),
19297
+ Comments: isV3 ? { Comment: this.createCommentTags() } : void 0,
19298
+ Viewpoints: isV3 ? { ViewPoint: this.createViewpointTags() } : void 0
19058
19299
  };
19059
- if (version === "2.1") {
19060
- topic.Labels = this.createLabelTags();
19061
- topic.DocumentReference = this.createDocumentReferencesTag(version);
19062
- }
19063
19300
  const markup = {
19064
19301
  Markup: { Topic: topic }
19065
19302
  };
@@ -19486,10 +19723,13 @@ const _BCFTopics = class _BCFTopics extends Component {
19486
19723
  */
19487
19724
  async export(topics = this.list.values()) {
19488
19725
  const zip = new JSZip();
19726
+ const isV3 = this.config.version === "3";
19727
+ const versionId = isV3 ? "3.0" : this.config.version;
19728
+ const schemaRelease = isV3 ? "release_3_0" : "release_2_1";
19489
19729
  zip.file(
19490
19730
  "bcf.version",
19491
19731
  `<?xml version="1.0" encoding="UTF-8"?>
19492
- <Version VersionId="${this.config.version}" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_3_0/Schemas/version.xsd"
19732
+ <Version VersionId="${versionId}" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/buildingSMART/BCF-XML/${schemaRelease}/Schemas/version.xsd"
19493
19733
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
19494
19734
  </Version>`
19495
19735
  );
@@ -19524,7 +19764,7 @@ const _BCFTopics = class _BCFTopics extends Component {
19524
19764
  );
19525
19765
  }
19526
19766
  }
19527
- zip.file("bcf.extensions", this.serializeExtensions());
19767
+ zip.file("extensions.xml", this.serializeExtensions());
19528
19768
  const viewpoints = this.components.get(Viewpoints);
19529
19769
  for (const topic of topics) {
19530
19770
  const topicFolder = zip.folder(topic.guid);
@@ -19596,7 +19836,7 @@ ${users}
19596
19836
  Comment: CommentText,
19597
19837
  Viewpoint: Viewpoint2
19598
19838
  } = markupComment;
19599
- if (!(Guid && CommentDate && Author && Comment))
19839
+ if (!(Guid && CommentDate && Author && (Comment || Viewpoint2)))
19600
19840
  return null;
19601
19841
  const comment = new Comment(this.components, CommentText ?? "");
19602
19842
  comment.guid = Guid;
@@ -19693,14 +19933,16 @@ ${users}
19693
19933
  const versionFile = files.find((file) => file.name.endsWith(".version"));
19694
19934
  if (versionFile) {
19695
19935
  const versionXML = await versionFile.async("string");
19696
- const bcfVersion = _BCFTopics.xmlParser.parse(versionXML).Version.VersionId;
19697
- version = String(bcfVersion);
19936
+ const bcfVersion = String(
19937
+ _BCFTopics.xmlParser.parse(versionXML).Version.VersionId
19938
+ );
19939
+ version = bcfVersion === "3.0" ? "3" : bcfVersion;
19698
19940
  }
19699
19941
  if (!(version && (version === "2.1" || version === "3"))) {
19700
19942
  throw new Error(`BCFTopics: ${version} is not supported.`);
19701
19943
  }
19702
19944
  const extensionsFile = files.find(
19703
- (file) => file.name.endsWith(".extensions")
19945
+ (file) => file.name.endsWith("extensions.xml") || file.name.endsWith(".extensions")
19704
19946
  );
19705
19947
  if (updateExtensionsOnImport && extensionsFile) {
19706
19948
  const extensionsXML = await extensionsFile.async("string");
@@ -20243,7 +20485,7 @@ class SimplePlane {
20243
20485
  dispose() {
20244
20486
  var _a;
20245
20487
  this._enabled = false;
20246
- if ((_a = this.world.camera) == null ? void 0 : _a.controls) {
20488
+ if (!this.world.isDisposing && ((_a = this.world.camera) == null ? void 0 : _a.controls)) {
20247
20489
  this.world.camera.controls.removeEventListener(
20248
20490
  "update",
20249
20491
  this.updateScale
@@ -20484,8 +20726,13 @@ const _Clipper = class _Clipper extends Component {
20484
20726
  * Default `false` to preserve existing behavior. Set this once
20485
20727
  * before creating planes; toggling at runtime won't reclassify
20486
20728
  * planes already on the renderer.
20729
+ *
20730
+ * Setting this also mirrors the value onto every world's
20731
+ * `renderer.localClippingPlanes`, so planes added by other
20732
+ * components (e.g. the {@link Views} cut planes) default to the
20733
+ * same mode without each call site having to pass `isLocal`.
20487
20734
  */
20488
- __publicField(this, "localClippingPlanes", false);
20735
+ __publicField(this, "_localClippingPlanes", false);
20489
20736
  /**
20490
20737
  * The type of clipping plane to be created.
20491
20738
  * Default is {@link SimplePlane}.
@@ -20521,6 +20768,17 @@ const _Clipper = class _Clipper extends Component {
20521
20768
  this.components.add(_Clipper.uuid, this);
20522
20769
  this.setEvents();
20523
20770
  }
20771
+ get localClippingPlanes() {
20772
+ return this._localClippingPlanes;
20773
+ }
20774
+ set localClippingPlanes(value) {
20775
+ this._localClippingPlanes = value;
20776
+ const worlds = this.components.get(Worlds);
20777
+ for (const [, world] of worlds.list) {
20778
+ if (world.renderer)
20779
+ world.renderer.localClippingPlanes = value;
20780
+ }
20781
+ }
20524
20782
  /** {@link Component.enabled} */
20525
20783
  get enabled() {
20526
20784
  return this._enabled;
@@ -23448,19 +23706,24 @@ class IDSProperty extends IDSFacet {
23448
23706
  );
23449
23707
  }
23450
23708
  getTypePsets(item) {
23451
- if (!Array.isArray(item.IsTypedBy))
23452
- return [];
23453
- const [type] = item.IsTypedBy;
23454
- if (!(type && Array.isArray(type.HasPropertySets))) {
23455
- return [];
23709
+ const definitions = [
23710
+ ...Array.isArray(item.IsTypedBy) ? item.IsTypedBy : [],
23711
+ ...Array.isArray(item.IsDefinedBy) ? item.IsDefinedBy : []
23712
+ ];
23713
+ const psets = [];
23714
+ for (const definition of definitions) {
23715
+ if (Array.isArray(definition == null ? void 0 : definition.HasPropertySets)) {
23716
+ psets.push(...definition.HasPropertySets);
23717
+ }
23456
23718
  }
23457
- return type.HasPropertySets;
23719
+ return psets;
23458
23720
  }
23459
23721
  async getPsets(item) {
23460
23722
  const typeSets = this.getTypePsets(item);
23461
23723
  if (!Array.isArray(item.IsDefinedBy))
23462
23724
  return typeSets;
23463
23725
  const sets = [];
23726
+ const mergedTypeSets = /* @__PURE__ */ new Set();
23464
23727
  for (const definition of item.IsDefinedBy) {
23465
23728
  if (!("value" in definition.Name))
23466
23729
  continue;
@@ -23473,22 +23736,29 @@ class IDSProperty extends IDSFacet {
23473
23736
  return false;
23474
23737
  return set.Name.value === definitionName;
23475
23738
  });
23476
- if (typeSet && Array.isArray(typeSet.HasProperties) && Array.isArray(definition.HasProperties)) {
23477
- for (const prop of typeSet.HasProperties) {
23478
- if (!("value" in prop.Name))
23479
- continue;
23480
- const name = prop.Name.value;
23481
- const existingProp = definition.HasProperties.find((p) => {
23482
- if (!("value" in p.Name))
23483
- return false;
23484
- return p.Name.value === name;
23485
- });
23486
- if (!existingProp)
23487
- definition.HasProperties.push(prop);
23739
+ if (typeSet) {
23740
+ mergedTypeSets.add(typeSet);
23741
+ if (Array.isArray(typeSet.HasProperties) && Array.isArray(definition.HasProperties)) {
23742
+ for (const prop of typeSet.HasProperties) {
23743
+ if (!("value" in prop.Name))
23744
+ continue;
23745
+ const name = prop.Name.value;
23746
+ const existingProp = definition.HasProperties.find((p) => {
23747
+ if (!("value" in p.Name))
23748
+ return false;
23749
+ return p.Name.value === name;
23750
+ });
23751
+ if (!existingProp)
23752
+ definition.HasProperties.push(prop);
23753
+ }
23488
23754
  }
23489
23755
  }
23490
23756
  sets.push(definition);
23491
23757
  }
23758
+ for (const typeSet of typeSets) {
23759
+ if (!mergedTypeSets.has(typeSet))
23760
+ sets.push(typeSet);
23761
+ }
23492
23762
  return sets;
23493
23763
  }
23494
23764
  // IFCPROPERTYBOUNDEDVALUE are not supported yet
@@ -23510,7 +23780,7 @@ class IDSProperty extends IDSFacet {
23510
23780
  return false;
23511
23781
  }
23512
23782
  const facetValue = structuredClone(this.value);
23513
- if (valueAttr.type === "IFCLABEL" && facetValue.type === "simple") {
23783
+ if (typeof valueAttr.value === "string" && facetValue.type === "simple") {
23514
23784
  facetValue.parameter = String(facetValue.parameter);
23515
23785
  }
23516
23786
  const result = this.evalRequirement(
@@ -23566,12 +23836,38 @@ class IDSProperty extends IDSFacet {
23566
23836
  class IDSMaterial extends IDSFacet {
23567
23837
  constructor() {
23568
23838
  super(...arguments);
23839
+ // Every type IfcRelAssociatesMaterial.RelatingMaterial (IfcMaterialSelect)
23840
+ // can point at. Missing any of them silently drops every element that
23841
+ // associates its material through it: an element can reference a layer set
23842
+ // directly instead of through a layer set usage, and both spellings are
23843
+ // valid IFC.
23569
23844
  __publicField(this, "_ifcMaterialEntities", [
23570
23845
  /^IFCMATERIALLAYERSETUSAGE$/,
23846
+ /^IFCMATERIALLAYERSET$/,
23847
+ /^IFCMATERIALPROFILESETUSAGE$/,
23848
+ /^IFCMATERIALPROFILESET$/,
23571
23849
  /^IFCMATERIALCONSTITUENTSET$/,
23572
23850
  /^IFCMATERIAL$/,
23573
23851
  /^IFCMATERIALLIST$/
23574
23852
  ]);
23853
+ // Attributes that lead from a material set / usage down to its IfcMaterial.
23854
+ __publicField(this, "_materialParentAttributes", [
23855
+ "ForLayerSet",
23856
+ "MaterialLayers",
23857
+ "ForProfileSet",
23858
+ "MaterialProfiles",
23859
+ "Material",
23860
+ "MaterialConstituents",
23861
+ "Materials"
23862
+ ]);
23863
+ __publicField(this, "_materialRelations", {
23864
+ MaterialConstituents: { attributes: true, relations: true },
23865
+ ForLayerSet: { attributes: true, relations: true },
23866
+ MaterialLayers: { attributes: true, relations: true },
23867
+ ForProfileSet: { attributes: true, relations: true },
23868
+ MaterialProfiles: { attributes: true, relations: true },
23869
+ Materials: { attributes: true, relations: false }
23870
+ });
23575
23871
  __publicField(this, "facetType", "Material");
23576
23872
  __publicField(this, "value");
23577
23873
  __publicField(this, "uri");
@@ -23603,10 +23899,7 @@ class IDSMaterial extends IDSFacet {
23603
23899
  const data = await model.getItemsData(localIds, {
23604
23900
  relations: {
23605
23901
  AssociatedTo: { attributes: true, relations: false },
23606
- MaterialConstituents: { attributes: true, relations: true },
23607
- ForLayerSet: { attributes: true, relations: true },
23608
- MaterialLayers: { attributes: true, relations: true },
23609
- Materials: { attributes: true, relations: false }
23902
+ ...this._materialRelations
23610
23903
  }
23611
23904
  });
23612
23905
  for (const item of data) {
@@ -23635,10 +23928,7 @@ class IDSMaterial extends IDSFacet {
23635
23928
  relations: {
23636
23929
  AssociatedTo: { attributes: false, relations: false },
23637
23930
  HasAssociations: { attributes: true, relations: true },
23638
- MaterialConstituents: { attributes: true, relations: true },
23639
- ForLayerSet: { attributes: true, relations: true },
23640
- MaterialLayers: { attributes: true, relations: true },
23641
- Materials: { attributes: true, relations: false }
23931
+ ...this._materialRelations
23642
23932
  }
23643
23933
  });
23644
23934
  for (const item of data) {
@@ -23682,13 +23972,7 @@ class IDSMaterial extends IDSFacet {
23682
23972
  isValid = true;
23683
23973
  } else {
23684
23974
  for (const [name, attribute] of Object.entries(item)) {
23685
- if (![
23686
- "ForLayerSet",
23687
- "MaterialLayers",
23688
- "Material",
23689
- "MaterialConstituents",
23690
- "Materials"
23691
- ].includes(name))
23975
+ if (!this._materialParentAttributes.includes(name))
23692
23976
  continue;
23693
23977
  if (!Array.isArray(attribute))
23694
23978
  continue;
@@ -26695,7 +26979,7 @@ function buildPreviewPositions(kind, points, cursor, style) {
26695
26979
  for (let i = 0; i < points.length - 1; i++) {
26696
26980
  positions.push(
26697
26981
  ...buildDimensionPositions(
26698
- { uuid: "", pointA: points[i], pointB: points[i + 1], offset, style: "" },
26982
+ { pointA: points[i], pointB: points[i + 1], offset },
26699
26983
  style
26700
26984
  )
26701
26985
  );