@thatopen/components 3.2.6 → 3.3.0

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
@@ -5,7 +5,7 @@ var __publicField = (obj, key, value) => {
5
5
  return value;
6
6
  };
7
7
  import * as FRAGS from "@thatopen/fragments";
8
- import { DataMap as DataMap$1, FragmentsModels } from "@thatopen/fragments";
8
+ import { DataMap as DataMap$1, FragmentsModels, DataSet as DataSet$1 } from "@thatopen/fragments";
9
9
  import * as THREE$1 from "three";
10
10
  import { BufferAttribute, Vector3, Plane, Line3, Vector2, Triangle, Sphere, Matrix4, Box3, BackSide, DoubleSide, FrontSide, Ray, Mesh, Controls, Quaternion, Raycaster, Object3D, MeshBasicMaterial, LineBasicMaterial, CylinderGeometry, BoxGeometry, BufferGeometry, Float32BufferAttribute, OctahedronGeometry, Line, TorusGeometry, SphereGeometry, Euler, PlaneGeometry } from "three";
11
11
  import * as WEBIFC from "web-ifc";
@@ -3078,7 +3078,7 @@ const _ConfigManager = class _ConfigManager extends Component {
3078
3078
  /**
3079
3079
  * The list of all configurations of this app.
3080
3080
  */
3081
- __publicField(this, "list", new DataMap());
3081
+ __publicField(this, "list", new DataMap$1());
3082
3082
  /** {@link Component.enabled} */
3083
3083
  __publicField(this, "enabled", true);
3084
3084
  components.add(_ConfigManager.uuid, this);
@@ -3897,6 +3897,7 @@ __publicField(_BoundingBoxer, "uuid", "d1444724-dba6-4cdd-a0c7-68ee1450d166");
3897
3897
  let BoundingBoxer = _BoundingBoxer;
3898
3898
  class FinderQuery {
3899
3899
  constructor(components, queries) {
3900
+ __publicField(this, "name", "Query");
3900
3901
  __publicField(this, "customData", {});
3901
3902
  __publicField(this, "_components");
3902
3903
  __publicField(this, "_queries", []);
@@ -4021,9 +4022,10 @@ class FinderQuery {
4021
4022
  */
4022
4023
  toJSON() {
4023
4024
  const manager = this._components.get(ItemsFinder);
4024
- const name = manager.list.getKey(this) ?? "Finder Query";
4025
+ const guid = manager.list.getKey(this) ?? UUID.create();
4025
4026
  const result = {
4026
- name,
4027
+ guid,
4028
+ name: this.name,
4027
4029
  customData: this.customData,
4028
4030
  queries: this.queries.map(this.serializeQueryParameters),
4029
4031
  aggregation: this.aggregation,
@@ -4056,6 +4058,7 @@ class FinderQuery {
4056
4058
  * @returns A `FinderQuery` instance.
4057
4059
  */
4058
4060
  fromJSON(data) {
4061
+ this.name = data.name;
4059
4062
  this.customData = data.customData;
4060
4063
  this.aggregation = data.aggregation;
4061
4064
  this.cache = data.cache;
@@ -4087,7 +4090,17 @@ const _ItemsFinder = class _ItemsFinder extends Component {
4087
4090
  * @returns A map of model IDs to sets of item IDs that match the query.
4088
4091
  */
4089
4092
  async getItems(queries, config) {
4090
- const { modelIds } = config ?? {};
4093
+ let modelsToCheck;
4094
+ if (config) {
4095
+ const { modelIds, items } = config;
4096
+ if (items) {
4097
+ const itemsModels = Object.keys(items);
4098
+ if (itemsModels.length > 0)
4099
+ modelsToCheck = itemsModels.map((entry) => new RegExp(`^${entry}$`));
4100
+ } else if (modelIds) {
4101
+ modelsToCheck = modelIds;
4102
+ }
4103
+ }
4091
4104
  const aggregation = (config == null ? void 0 : config.aggregation) ?? "exclusive";
4092
4105
  const fragments = this.components.get(FragmentsManager);
4093
4106
  const results = await Promise.all(
@@ -4095,9 +4108,11 @@ const _ItemsFinder = class _ItemsFinder extends Component {
4095
4108
  const result = {};
4096
4109
  await Promise.all(
4097
4110
  Array.from(fragments.list).map(async ([id, model]) => {
4098
- if (modelIds && !modelIds.some((regex) => regex.test(id)))
4111
+ var _a2;
4112
+ if (modelsToCheck && !modelsToCheck.some((regex) => regex.test(id)))
4099
4113
  return;
4100
- const items = await model.getItemsByQuery(query);
4114
+ const localIds = (_a2 = config == null ? void 0 : config.items) == null ? void 0 : _a2[id];
4115
+ const items = await model.getItemsByQuery(query, { localIds: localIds ? [...localIds] : void 0 });
4101
4116
  result[id] = new Set(items);
4102
4117
  })
4103
4118
  );
@@ -4156,9 +4171,8 @@ const _ItemsFinder = class _ItemsFinder extends Component {
4156
4171
  if (!data)
4157
4172
  return instances;
4158
4173
  for (const value of data) {
4159
- const { name, customData, queries, aggregation, cache } = value;
4160
- const finderQuery = this.create(name, []);
4161
- finderQuery.fromJSON({ customData, queries, aggregation, cache });
4174
+ const finderQuery = this.create(value.guid, []);
4175
+ finderQuery.fromJSON(value);
4162
4176
  instances.push(finderQuery);
4163
4177
  }
4164
4178
  return instances;
@@ -4547,6 +4561,448 @@ const _Classifier = class _Classifier extends Component {
4547
4561
  */
4548
4562
  __publicField(_Classifier, "uuid", "e25a7f3c-46c4-4a14-9d3d-5115f24ebeb7");
4549
4563
  let Classifier = _Classifier;
4564
+ class FastModelPicker {
4565
+ constructor(components, world) {
4566
+ /** {@link Component.enabled} */
4567
+ __publicField(this, "enabled", true);
4568
+ /** The components instance to which this FastModelPicker belongs. */
4569
+ __publicField(this, "components");
4570
+ /** {@link Disposable.onDisposed} */
4571
+ __publicField(this, "onDisposed", new Event());
4572
+ /** The position of the mouse in the screen. */
4573
+ __publicField(this, "mouse");
4574
+ /**
4575
+ * A reference to the world instance to which this FastModelPicker belongs.
4576
+ * This is used to access the camera and scene.
4577
+ */
4578
+ __publicField(this, "world");
4579
+ /**
4580
+ * Whether debug mode is enabled. When enabled, shows the color-coded canvas.
4581
+ */
4582
+ __publicField(this, "debugMode", false);
4583
+ /**
4584
+ * Map from color (as RGB number) to model ID.
4585
+ * Color is encoded as: (r << 16) | (g << 8) | b
4586
+ */
4587
+ __publicField(this, "colorToModelId", /* @__PURE__ */ new Map());
4588
+ /**
4589
+ * Map from model ID to color.
4590
+ */
4591
+ __publicField(this, "modelIdToColor", /* @__PURE__ */ new Map());
4592
+ /**
4593
+ * Render target for the color-coded scene.
4594
+ */
4595
+ __publicField(this, "renderTarget");
4596
+ /**
4597
+ * Size of the render target (stored separately since getSize doesn't exist).
4598
+ */
4599
+ __publicField(this, "renderTargetSize", new THREE$1.Vector2());
4600
+ /**
4601
+ * Debug canvas element (shown when debugMode is true).
4602
+ */
4603
+ __publicField(this, "debugCanvas");
4604
+ /**
4605
+ * Debug container element.
4606
+ */
4607
+ __publicField(this, "debugContainer");
4608
+ /**
4609
+ * Material used for color-coding models.
4610
+ */
4611
+ __publicField(this, "colorMaterials", /* @__PURE__ */ new Map());
4612
+ /**
4613
+ * Original materials cache (to restore after picking).
4614
+ */
4615
+ __publicField(this, "originalMaterials", /* @__PURE__ */ new Map());
4616
+ __publicField(this, "originalLodColors", /* @__PURE__ */ new Map());
4617
+ /**
4618
+ * Whether colors need to be reassigned (when models change).
4619
+ */
4620
+ __publicField(this, "colorsNeedUpdate", true);
4621
+ const renderer = world.renderer;
4622
+ if (!renderer) {
4623
+ throw new Error("A renderer is needed for the FastModelPicker to work!");
4624
+ }
4625
+ this.world = world;
4626
+ this.mouse = new Mouse(renderer.three.domElement);
4627
+ this.components = components;
4628
+ this.setupRenderTarget();
4629
+ this.setupFragmentListeners();
4630
+ }
4631
+ /**
4632
+ * Sets up listeners for fragment model changes.
4633
+ */
4634
+ setupFragmentListeners() {
4635
+ const fragments = this.components.get(FragmentsManager);
4636
+ fragments.list.onItemSet.add(() => {
4637
+ this.colorsNeedUpdate = true;
4638
+ });
4639
+ fragments.list.onItemDeleted.add(() => {
4640
+ this.colorsNeedUpdate = true;
4641
+ });
4642
+ }
4643
+ /**
4644
+ * Sets up the render target for color-coded picking.
4645
+ */
4646
+ setupRenderTarget() {
4647
+ const renderer = this.world.renderer.three;
4648
+ const size = renderer.getSize(new THREE$1.Vector2());
4649
+ this.renderTargetSize.copy(size);
4650
+ this.renderTarget = new THREE$1.WebGLRenderTarget(size.x, size.y);
4651
+ this.renderTarget.texture.format = THREE$1.RGBAFormat;
4652
+ this.renderTarget.texture.type = THREE$1.UnsignedByteType;
4653
+ if (this.debugMode) {
4654
+ this.setupDebugCanvas();
4655
+ }
4656
+ this.world.renderer.onResize.add((size2) => {
4657
+ this.renderTargetSize.copy(size2);
4658
+ this.renderTarget.setSize(size2.x, size2.y);
4659
+ if (this.debugCanvas) {
4660
+ this.debugCanvas.width = size2.x;
4661
+ this.debugCanvas.height = size2.y;
4662
+ }
4663
+ });
4664
+ }
4665
+ /**
4666
+ * Sets up the debug canvas for visualization.
4667
+ */
4668
+ setupDebugCanvas() {
4669
+ if (this.debugCanvas)
4670
+ return;
4671
+ const size = this.world.renderer.three.getSize(new THREE$1.Vector2());
4672
+ this.debugContainer = document.createElement("div");
4673
+ this.debugContainer.style.position = "fixed";
4674
+ this.debugContainer.style.top = "10px";
4675
+ this.debugContainer.style.right = "10px";
4676
+ this.debugContainer.style.width = "300px";
4677
+ this.debugContainer.style.height = "300px";
4678
+ this.debugContainer.style.border = "2px solid #fff";
4679
+ this.debugContainer.style.backgroundColor = "#000";
4680
+ this.debugContainer.style.zIndex = "10000";
4681
+ this.debugContainer.style.pointerEvents = "none";
4682
+ this.debugCanvas = document.createElement("canvas");
4683
+ this.debugCanvas.width = size.x;
4684
+ this.debugCanvas.height = size.y;
4685
+ this.debugCanvas.style.width = "100%";
4686
+ this.debugCanvas.style.height = "100%";
4687
+ this.debugCanvas.style.imageRendering = "pixelated";
4688
+ this.debugContainer.appendChild(this.debugCanvas);
4689
+ document.body.appendChild(this.debugContainer);
4690
+ }
4691
+ /**
4692
+ * Generates a deterministic color for a model based on its ID.
4693
+ * This ensures the same model always gets the same color.
4694
+ */
4695
+ generateColorForModel(modelId) {
4696
+ let hash = 0;
4697
+ for (let i = 0; i < modelId.length; i++) {
4698
+ hash = (hash << 5) - hash + modelId.charCodeAt(i);
4699
+ hash &= hash;
4700
+ }
4701
+ let colorId = Math.abs(hash) % 16777215;
4702
+ if (colorId === 0) {
4703
+ colorId = 1;
4704
+ }
4705
+ const r = colorId >> 16 & 255 || 1;
4706
+ const g = colorId >> 8 & 255 || 1;
4707
+ const b = colorId & 255 || 1;
4708
+ return new THREE$1.Color(r / 255, g / 255, b / 255);
4709
+ }
4710
+ /**
4711
+ * Converts a color to a numeric ID.
4712
+ */
4713
+ colorToId(color) {
4714
+ const r = Math.round(color.r * 255);
4715
+ const g = Math.round(color.g * 255);
4716
+ const b = Math.round(color.b * 255);
4717
+ return r << 16 | g << 8 | b;
4718
+ }
4719
+ /**
4720
+ * Assigns unique colors to all fragment models.
4721
+ * Colors are deterministic based on model ID, so the same model always gets the same color.
4722
+ */
4723
+ assignColors() {
4724
+ const fragments = this.components.get(FragmentsManager);
4725
+ if (!fragments.initialized) {
4726
+ return;
4727
+ }
4728
+ if (!this.colorsNeedUpdate) {
4729
+ const currentModelIds = new Set(fragments.list.keys());
4730
+ const cachedModelIds = new Set(this.modelIdToColor.keys());
4731
+ if (currentModelIds.size !== cachedModelIds.size || [...currentModelIds].some((id) => !cachedModelIds.has(id))) {
4732
+ this.colorsNeedUpdate = true;
4733
+ }
4734
+ }
4735
+ if (!this.colorsNeedUpdate) {
4736
+ return;
4737
+ }
4738
+ this.colorToModelId.clear();
4739
+ this.modelIdToColor.clear();
4740
+ for (const material of this.colorMaterials.values()) {
4741
+ material.dispose();
4742
+ }
4743
+ this.colorMaterials.clear();
4744
+ for (const [modelId] of fragments.list) {
4745
+ const color = this.generateColorForModel(modelId);
4746
+ const colorId = this.colorToId(color);
4747
+ this.colorToModelId.set(colorId, modelId);
4748
+ this.modelIdToColor.set(modelId, color);
4749
+ const material = new THREE$1.MeshBasicMaterial({
4750
+ color,
4751
+ depthTest: true,
4752
+ depthWrite: true
4753
+ });
4754
+ this.colorMaterials.set(modelId, material);
4755
+ }
4756
+ this.colorsNeedUpdate = false;
4757
+ }
4758
+ /**
4759
+ * Applies color materials to fragment models.
4760
+ */
4761
+ applyColorMaterials() {
4762
+ const fragments = this.components.get(FragmentsManager);
4763
+ if (!fragments.initialized) {
4764
+ return;
4765
+ }
4766
+ for (const [modelId, model] of fragments.list) {
4767
+ const modelMaterial = this.colorMaterials.get(modelId);
4768
+ if (!modelMaterial)
4769
+ continue;
4770
+ model.object.traverse((child) => {
4771
+ if (child instanceof THREE$1.Mesh) {
4772
+ if ("isLODGeometry" in child.geometry) {
4773
+ const originalLodMaterial = child.material[0];
4774
+ const lodColor = originalLodMaterial.uniforms.lodColor;
4775
+ if (!this.originalLodColors.has(lodColor)) {
4776
+ this.originalLodColors.set(lodColor, lodColor.value);
4777
+ }
4778
+ lodColor.value = modelMaterial.color;
4779
+ return;
4780
+ }
4781
+ if (!this.originalMaterials.has(child)) {
4782
+ this.originalMaterials.set(child, child.material);
4783
+ }
4784
+ child.material = modelMaterial;
4785
+ }
4786
+ });
4787
+ }
4788
+ }
4789
+ /**
4790
+ * Restores original materials to fragment models.
4791
+ */
4792
+ restoreOriginalMaterials() {
4793
+ for (const [object, material] of this.originalMaterials) {
4794
+ object.material = material;
4795
+ }
4796
+ for (const [lodColor, color] of this.originalLodColors) {
4797
+ lodColor.value = color;
4798
+ }
4799
+ this.originalMaterials.clear();
4800
+ }
4801
+ /**
4802
+ * Renders the scene with color-coded models.
4803
+ */
4804
+ renderColorCoded() {
4805
+ if (!this.renderTarget || !this.world.renderer) {
4806
+ throw new Error("Render target not initialized!");
4807
+ }
4808
+ const renderer = this.world.renderer.three;
4809
+ const scene = this.world.scene.three;
4810
+ const camera = this.world.camera.three;
4811
+ const currentRenderTarget = renderer.getRenderTarget();
4812
+ const currentAutoClear = renderer.autoClear;
4813
+ const currentClearColor = new THREE$1.Color();
4814
+ const currentClearAlpha = renderer.getClearAlpha();
4815
+ renderer.getClearColor(currentClearColor);
4816
+ this.applyColorMaterials();
4817
+ renderer.setRenderTarget(this.renderTarget);
4818
+ renderer.autoClear = true;
4819
+ renderer.setClearColor(0, 1);
4820
+ renderer.clear(true, true, false);
4821
+ renderer.render(scene, camera);
4822
+ renderer.setRenderTarget(currentRenderTarget);
4823
+ renderer.autoClear = currentAutoClear;
4824
+ renderer.setClearColor(currentClearColor, currentClearAlpha);
4825
+ this.restoreOriginalMaterials();
4826
+ if (this.debugMode && this.debugCanvas) {
4827
+ this.updateDebugCanvas();
4828
+ }
4829
+ }
4830
+ /**
4831
+ * Updates the debug canvas with the color-coded render.
4832
+ */
4833
+ updateDebugCanvas() {
4834
+ if (!this.debugCanvas || !this.renderTarget || !this.world.renderer)
4835
+ return;
4836
+ const renderer = this.world.renderer.three;
4837
+ const size = this.renderTargetSize;
4838
+ const pixels = new Uint8Array(size.x * size.y * 4);
4839
+ renderer.readRenderTargetPixels(
4840
+ this.renderTarget,
4841
+ 0,
4842
+ 0,
4843
+ size.x,
4844
+ size.y,
4845
+ pixels
4846
+ );
4847
+ const ctx = this.debugCanvas.getContext("2d");
4848
+ if (!ctx)
4849
+ return;
4850
+ const imageData = ctx.createImageData(size.x, size.y);
4851
+ const rowSize = size.x * 4;
4852
+ for (let y = 0; y < size.y; y++) {
4853
+ const srcRow = y;
4854
+ const dstRow = size.y - 1 - y;
4855
+ const srcOffset = srcRow * rowSize;
4856
+ const dstOffset = dstRow * rowSize;
4857
+ imageData.data.set(
4858
+ pixels.subarray(srcOffset, srcOffset + rowSize),
4859
+ dstOffset
4860
+ );
4861
+ }
4862
+ ctx.putImageData(imageData, 0, 0);
4863
+ }
4864
+ /**
4865
+ * Gets the model ID at the given screen position.
4866
+ *
4867
+ * @param position - Optional screen position. If not provided, uses current mouse position.
4868
+ * @returns The model ID at the position, or null if no model is found.
4869
+ */
4870
+ async getModelAt(position) {
4871
+ if (!this.renderTarget || !this.world.renderer) {
4872
+ throw new Error("Render target not initialized!");
4873
+ }
4874
+ const fragments = this.components.get(FragmentsManager);
4875
+ if (!fragments.initialized || fragments.list.size === 0) {
4876
+ return null;
4877
+ }
4878
+ this.assignColors();
4879
+ this.renderColorCoded();
4880
+ const pos = position || this.mouse.position;
4881
+ const size = this.renderTargetSize;
4882
+ const x = Math.floor((pos.x + 1) * 0.5 * size.x);
4883
+ const y = Math.floor((pos.y + 1) * 0.5 * (size.y - 1));
4884
+ const clampedX = Math.max(0, Math.min(size.x - 1, x));
4885
+ const clampedY = Math.max(0, Math.min(size.y - 1, y));
4886
+ const renderer = this.world.renderer.three;
4887
+ const pixels = new Uint8Array(4);
4888
+ renderer.readRenderTargetPixels(
4889
+ this.renderTarget,
4890
+ clampedX,
4891
+ clampedY,
4892
+ 1,
4893
+ 1,
4894
+ pixels
4895
+ );
4896
+ const r = pixels[0];
4897
+ const g = pixels[1];
4898
+ const b = pixels[2];
4899
+ const colorId = r << 16 | g << 8 | b;
4900
+ const modelId = this.colorToModelId.get(colorId);
4901
+ return modelId || null;
4902
+ }
4903
+ /**
4904
+ * Enables or disables debug mode.
4905
+ * When enabled, shows a canvas with the color-coded render.
4906
+ */
4907
+ setDebugMode(enabled) {
4908
+ this.debugMode = enabled;
4909
+ if (enabled) {
4910
+ this.setupDebugCanvas();
4911
+ } else {
4912
+ this.removeDebugCanvas();
4913
+ }
4914
+ }
4915
+ /**
4916
+ * Removes the debug canvas.
4917
+ */
4918
+ removeDebugCanvas() {
4919
+ if (this.debugContainer) {
4920
+ this.debugContainer.remove();
4921
+ this.debugContainer = void 0;
4922
+ this.debugCanvas = void 0;
4923
+ }
4924
+ }
4925
+ /** {@link Disposable.dispose} */
4926
+ dispose() {
4927
+ this.mouse.dispose();
4928
+ this.removeDebugCanvas();
4929
+ for (const material of this.colorMaterials.values()) {
4930
+ material.dispose();
4931
+ }
4932
+ this.colorMaterials.clear();
4933
+ if (this.renderTarget) {
4934
+ this.renderTarget.dispose();
4935
+ }
4936
+ this.colorToModelId.clear();
4937
+ this.modelIdToColor.clear();
4938
+ this.originalMaterials.clear();
4939
+ this.originalLodColors.clear();
4940
+ this.onDisposed.trigger();
4941
+ this.onDisposed.reset();
4942
+ }
4943
+ }
4944
+ const _FastModelPickers = class _FastModelPickers extends Component {
4945
+ constructor(components) {
4946
+ super(components);
4947
+ /** {@link Component.enabled} */
4948
+ __publicField(this, "enabled", true);
4949
+ /**
4950
+ * A Map that stores FastModelPicker instances for each world.
4951
+ * The key is the world's UUID, and the value is the corresponding FastModelPicker instance.
4952
+ */
4953
+ __publicField(this, "list", /* @__PURE__ */ new Map());
4954
+ /** {@link Disposable.onDisposed} */
4955
+ __publicField(this, "onDisposed", new Event());
4956
+ components.add(_FastModelPickers.uuid, this);
4957
+ }
4958
+ /**
4959
+ * Retrieves a FastModelPicker instance for the given world.
4960
+ * If a FastModelPicker instance already exists for the world, it will be returned.
4961
+ * Otherwise, a new FastModelPicker instance will be created and added to the list.
4962
+ *
4963
+ * @param world - The world for which to retrieve or create a FastModelPicker instance.
4964
+ * @returns The FastModelPicker instance for the given world.
4965
+ */
4966
+ get(world) {
4967
+ if (this.list.has(world.uuid)) {
4968
+ return this.list.get(world.uuid);
4969
+ }
4970
+ const picker = new FastModelPicker(this.components, world);
4971
+ this.list.set(world.uuid, picker);
4972
+ world.onDisposed.add(() => {
4973
+ this.delete(world);
4974
+ });
4975
+ return picker;
4976
+ }
4977
+ /**
4978
+ * Deletes the FastModelPicker instance associated with the given world.
4979
+ * If a FastModelPicker instance exists for the given world, it will be disposed and removed from the list.
4980
+ *
4981
+ * @param world - The world for which to delete the FastModelPicker instance.
4982
+ * @returns {void}
4983
+ */
4984
+ delete(world) {
4985
+ const picker = this.list.get(world.uuid);
4986
+ if (picker) {
4987
+ picker.dispose();
4988
+ }
4989
+ this.list.delete(world.uuid);
4990
+ }
4991
+ /** {@link Disposable.dispose} */
4992
+ dispose() {
4993
+ for (const [_id, picker] of this.list) {
4994
+ picker.dispose();
4995
+ }
4996
+ this.list.clear();
4997
+ this.onDisposed.trigger();
4998
+ }
4999
+ };
5000
+ /**
5001
+ * A unique identifier for the component.
5002
+ * This UUID is used to register the component within the Components system.
5003
+ */
5004
+ __publicField(_FastModelPickers, "uuid", "4a82430c-7ff2-49ea-9401-60807502dad6");
5005
+ let FastModelPickers = _FastModelPickers;
4550
5006
  class SimpleRaycaster {
4551
5007
  constructor(components, world) {
4552
5008
  /** {@link Component.enabled} */
@@ -4567,6 +5023,13 @@ class SimpleRaycaster {
4567
5023
  * This is used to access the camera and meshes.
4568
5024
  */
4569
5025
  __publicField(this, "world");
5026
+ /**
5027
+ * Whether to use fast model picking to optimize raycasting.
5028
+ * When enabled, the raycaster will first use FastModelPicker to identify
5029
+ * which model is under the mouse, then only raycast that specific model.
5030
+ * This can significantly improve performance when there are many models.
5031
+ */
5032
+ __publicField(this, "useFastModelPicking", false);
4570
5033
  const renderer = world.renderer;
4571
5034
  if (!renderer) {
4572
5035
  throw new Error("A renderer is needed for the raycaster to work!");
@@ -4613,12 +5076,46 @@ class SimpleRaycaster {
4613
5076
  const mouse = this.mouse.rawPosition;
4614
5077
  let fragResult = null;
4615
5078
  if (fragments.initialized) {
4616
- fragResult = await fragments.raycast({
4617
- camera,
4618
- dom,
4619
- mouse,
4620
- snappingClasses
4621
- });
5079
+ if (this.useFastModelPicking) {
5080
+ const fastPickers = this.components.get(FastModelPickers);
5081
+ const fastPicker = fastPickers.get(this.world);
5082
+ const modelId = await fastPicker.getModelAt(position);
5083
+ if (modelId) {
5084
+ const model = fragments.list.get(modelId);
5085
+ if (model) {
5086
+ if (snappingClasses && snappingClasses.length > 0) {
5087
+ const snappingRaycast = await model.raycastWithSnapping({
5088
+ camera,
5089
+ dom,
5090
+ mouse,
5091
+ snappingClasses
5092
+ });
5093
+ if (snappingRaycast && snappingRaycast.length > 0) {
5094
+ fragResult = snappingRaycast[0];
5095
+ } else {
5096
+ fragResult = await model.raycast({
5097
+ camera,
5098
+ dom,
5099
+ mouse
5100
+ });
5101
+ }
5102
+ } else {
5103
+ fragResult = await model.raycast({
5104
+ camera,
5105
+ dom,
5106
+ mouse
5107
+ });
5108
+ }
5109
+ }
5110
+ }
5111
+ } else {
5112
+ fragResult = await fragments.raycast({
5113
+ camera,
5114
+ dom,
5115
+ mouse,
5116
+ snappingClasses
5117
+ });
5118
+ }
4622
5119
  if (items.length === 0) {
4623
5120
  return fragResult;
4624
5121
  }
@@ -7644,7 +8141,7 @@ const _Worlds = class _Worlds extends Component {
7644
8141
  * A collection of worlds managed by this component.
7645
8142
  * The key is the unique identifier (UUID) of the world, and the value is the World instance.
7646
8143
  */
7647
- __publicField(this, "list", new DataMap());
8144
+ __publicField(this, "list", new DataMap$1());
7648
8145
  /** {@link Component.enabled} */
7649
8146
  __publicField(this, "enabled", true);
7650
8147
  components.add(_Worlds.uuid, this);
@@ -11516,10 +12013,11 @@ const _Components = class _Components {
11516
12013
  * @throws Will throw an error if a component with the same UUID already exists.
11517
12014
  */
11518
12015
  get(Component2) {
12016
+ var _a2;
11519
12017
  const uuid = Component2.uuid;
11520
12018
  if (!this.list.has(uuid)) {
11521
12019
  const toolInstance = new Component2(this);
11522
- if (toolInstance.isDisposeable()) {
12020
+ if ((_a2 = toolInstance.isDisposeable) == null ? void 0 : _a2.call(toolInstance)) {
11523
12021
  toolInstance.onDisposed.add(() => this.list.delete(uuid));
11524
12022
  }
11525
12023
  if (!this.list.has(uuid)) {
@@ -11969,6 +12467,14 @@ class OrthoPerspectiveCamera extends SimpleCamera {
11969
12467
  this.disableUserInput();
11970
12468
  }
11971
12469
  }
12470
+ /**
12471
+ * Adds a custom {@link NavigationMode} to the camera that can be used using the {@link OrthoPerspectiveCamera.set} method.
12472
+ *
12473
+ * @param mode - The custom {@link NavigationMode} to add.
12474
+ */
12475
+ addCustomNavigationMode(mode) {
12476
+ this._navigationModes.set(mode.id, mode);
12477
+ }
11972
12478
  disableUserInput() {
11973
12479
  this._userInputButtons.left = this.controls.mouseButtons.left;
11974
12480
  this._userInputButtons.right = this.controls.mouseButtons.right;
@@ -14751,13 +15257,13 @@ const _Topic = class _Topic {
14751
15257
  __publicField(this, "creationDate", /* @__PURE__ */ new Date());
14752
15258
  __publicField(this, "creationAuthor", "");
14753
15259
  // Store viewpoint guids instead of the actual Viewpoint to prevent a possible memory leak
14754
- __publicField(this, "viewpoints", new DataSet());
15260
+ __publicField(this, "viewpoints", new DataSet$1());
14755
15261
  // Store topic guids instead of the actual Topic to prevent a possible memory leak
14756
- __publicField(this, "relatedTopics", new DataSet());
15262
+ __publicField(this, "relatedTopics", new DataSet$1());
14757
15263
  // There is no problem to store the comment it-self as it is not referenced anywhere else
14758
- __publicField(this, "comments", new DataMap());
15264
+ __publicField(this, "comments", new DataMap$1());
14759
15265
  // Store document reference guids instead of the actual thing to prevent a possible memory leak
14760
- __publicField(this, "documentReferences", new DataSet());
15266
+ __publicField(this, "documentReferences", new DataSet$1());
14761
15267
  __publicField(this, "customData", {});
14762
15268
  __publicField(this, "description");
14763
15269
  __publicField(this, "serverAssignedId");
@@ -15367,8 +15873,8 @@ const _BCFTopics = class _BCFTopics extends Component {
15367
15873
  "BCF Topics",
15368
15874
  _BCFTopics.uuid
15369
15875
  ));
15370
- __publicField(this, "list", new DataMap());
15371
- __publicField(this, "documents", new DataMap());
15876
+ __publicField(this, "list", new DataMap$1());
15877
+ __publicField(this, "documents", new DataMap$1());
15372
15878
  __publicField(this, "onSetup", new Event());
15373
15879
  __publicField(this, "isSetup", false);
15374
15880
  __publicField(this, "onBCFImported", new Event());
@@ -17302,6 +17808,15 @@ class SimplePlane {
17302
17808
  get helper() {
17303
17809
  return this._helper;
17304
17810
  }
17811
+ /**
17812
+ * Getter for the transform controls of the clipping plane.
17813
+ * The controls allow interactive manipulation (translation, rotation, etc.) of the clipping plane.
17814
+ *
17815
+ * @returns {TransformControls} The transform controls of the clipping plane.
17816
+ */
17817
+ get controls() {
17818
+ return this._controls;
17819
+ }
17305
17820
  /**
17306
17821
  * Sets the clipping plane's normal and origin from the given normal and point.
17307
17822
  * This method resets the clipping plane's state, updates the normal and origin,
@@ -17566,6 +18081,7 @@ const _Clipper = class _Clipper extends Component {
17566
18081
  __publicField(this, "_size", 5);
17567
18082
  __publicField(this, "_enabled", false);
17568
18083
  __publicField(this, "_visible", true);
18084
+ __publicField(this, "onStateChanged", new Event());
17569
18085
  __publicField(this, "_onStartDragging", () => {
17570
18086
  this.onBeforeDrag.trigger();
17571
18087
  });
@@ -17582,6 +18098,7 @@ const _Clipper = class _Clipper extends Component {
17582
18098
  /** {@link Component.enabled} */
17583
18099
  set enabled(state) {
17584
18100
  this._enabled = state;
18101
+ this.onStateChanged.trigger(["enabled"]);
17585
18102
  }
17586
18103
  /** {@link Hideable.visible } */
17587
18104
  get visible() {
@@ -17593,6 +18110,7 @@ const _Clipper = class _Clipper extends Component {
17593
18110
  for (const [_, plane] of this.list) {
17594
18111
  plane.visible = state;
17595
18112
  }
18113
+ this.onStateChanged.trigger(["visibility"]);
17596
18114
  }
17597
18115
  /** The material of the clipping plane representation. */
17598
18116
  get material() {
@@ -17604,6 +18122,7 @@ const _Clipper = class _Clipper extends Component {
17604
18122
  for (const [_, plane] of this.list) {
17605
18123
  plane.planeMaterial = material;
17606
18124
  }
18125
+ this.onStateChanged.trigger(["material"]);
17607
18126
  }
17608
18127
  /** The size of the geometric representation of the clippings planes. */
17609
18128
  get size() {
@@ -17615,6 +18134,7 @@ const _Clipper = class _Clipper extends Component {
17615
18134
  for (const [_, plane] of this.list) {
17616
18135
  plane.size = size;
17617
18136
  }
18137
+ this.onStateChanged.trigger(["size"]);
17618
18138
  }
17619
18139
  setEvents() {
17620
18140
  this.list.onBeforeDelete.add(({ value: plane }) => {
@@ -17848,6 +18368,7 @@ class Viewpoint {
17848
18368
  camera_view_point: { x: 0, y: 0, z: 0 },
17849
18369
  camera_up_vector: { x: 0, y: 1, z: 0 }
17850
18370
  });
18371
+ __publicField(this, "customData", {});
17851
18372
  /**
17852
18373
  * A list of components GUIDs to hide when defaultVisibility = true or to show when defaultVisibility = false
17853
18374
  */
@@ -20992,7 +21513,7 @@ const _IDSSpecifications = class _IDSSpecifications extends Component {
20992
21513
  super(components);
20993
21514
  __publicField(this, "enabled", true);
20994
21515
  __publicField(this, "IDSInfo");
20995
- __publicField(this, "list", new DataMap());
21516
+ __publicField(this, "list", new DataMap$1());
20996
21517
  components.add(_IDSSpecifications.uuid, this);
20997
21518
  }
20998
21519
  /**
@@ -21519,11 +22040,6 @@ const _MeasurementUtils = class _MeasurementUtils extends Component {
21519
22040
  throw new Error("Precision must be an integer between 0 and 5.");
21520
22041
  }
21521
22042
  let factor = unitFactors[fromUnit] / unitFactors[toUnit];
21522
- if (fromUnit.endsWith("2") && toUnit.endsWith("2")) {
21523
- factor **= 2;
21524
- } else if (fromUnit.endsWith("3") && toUnit.endsWith("3")) {
21525
- factor **= 3;
21526
- }
21527
22043
  const convertedValue = value * factor;
21528
22044
  const roundingFactor = 10 ** precision;
21529
22045
  return Math.round(convertedValue * roundingFactor) / roundingFactor;
@@ -21558,6 +22074,8 @@ export {
21558
22074
  Disposer,
21559
22075
  Event,
21560
22076
  EventManager,
22077
+ FastModelPicker,
22078
+ FastModelPickers,
21561
22079
  FinderQuery,
21562
22080
  FirstPersonMode,
21563
22081
  FragmentsManager,