diff --git a/examples/FragmentHighlighter/index.html b/examples/FragmentHighlighter/index.html
new file mode 100644
index 000000000..7785ad015
--- /dev/null
+++ b/examples/FragmentHighlighter/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+ Fragment Highlighter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/assets/fragmentHighlighter.js b/examples/assets/fragmentHighlighter.js
new file mode 100644
index 000000000..2a4f0902b
--- /dev/null
+++ b/examples/assets/fragmentHighlighter.js
@@ -0,0 +1 @@
+import{l as b,A as y,C,M}from"./unzipit.module-DQmiVUKU.js";import{S as E}from"./stats.min-GTpOrGrX.js";import{g as B}from"./lil-gui.module.min-Bc0DeA9g.js";import"./N8AO-CNivsaIi.js";import{P as O}from"./index-BqklWmzc.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./serializer-DZyNoHx-.js";import"./stream-serializer-BO8jmA13.js";const s=document.getElementById("container"),e=new(void 0),c=new(void 0)(e);c.setup();e.scene=c;const o=new O(e,s);e.renderer=o;const r=new(void 0)(e);e.camera=r;e.raycaster=new(void 0)(e);e.init();o.postproduction.enabled=!0;const m=e.scene.get();r.controls.setLookAt(10,10,10,0,0,0);const l=new b;l.position.set(5,10,3);l.intensity=.5;m.add(l);const u=new y;u.intensity=.5;m.add(u);const S=new(void 0)(e,new C(6710886)),D=S.get(),I=o.postproduction.customEffects;I.excludedMeshes.push(D);const L=new(void 0)(e),t=new(void 0)(e),v=await fetch("../../../resources/small.frag"),A=await v.arrayBuffer(),k=new Uint8Array(A);L.load(k);t.updateHighlight();o.postproduction.customEffects.outlineEnabled=!0;t.outlineEnabled=!0;const h=new M({color:"#BCF124",depthTest:!1,opacity:.8,transparent:!0});t.add("default",[h]);t.outlineMaterial.color.set(15794042);let a;const f={value:!0};async function x(){const d=await t.highlight("default",f.value);if(d){a={};for(const p of d.fragments){const w=p.id;a[w]=[d.id]}}}s.addEventListener("click",()=>x());function F(){a!==void 0&&t.highlightByID("default",a)}const i=new E;i.showPanel(2);document.body.append(i.dom);i.dom.style.left="0px";o.onBeforeUpdate.add(()=>i.begin());o.onAfterUpdate.add(()=>i.end());const g={highlight:"Click",highlightOnID:()=>F()},n=new B;n.add(g,"highlight");n.add(g,"highlightOnID").name("Select last selection");n.add(f,"value").name("Single selection");n.add(t,"zoomToSelection").name("Zoom to selection");n.add(t,"fillEnabled").name("Fill enabled");n.add(t,"outlineEnabled").name("Outline enabled");n.addColor(h,"color").name("Fill color");n.addColor(t.outlineMaterial,"color").name("Outline color");n.add(t.outlineMaterial,"opacity").name("Outline width").min(.3).max(1).step(.05);
diff --git a/temp/fragments/FragmentHighlighter/example.html b/packages/components-front/src/fragments/FragmentHighlighter/example.html
similarity index 100%
rename from temp/fragments/FragmentHighlighter/example.html
rename to packages/components-front/src/fragments/FragmentHighlighter/example.html
diff --git a/temp/fragments/FragmentHighlighter/example.ts b/packages/components-front/src/fragments/FragmentHighlighter/example.ts
similarity index 100%
rename from temp/fragments/FragmentHighlighter/example.ts
rename to packages/components-front/src/fragments/FragmentHighlighter/example.ts
diff --git a/temp/fragments/FragmentHighlighter/index.ts b/packages/components-front/src/fragments/FragmentHighlighter/index.ts
similarity index 69%
rename from temp/fragments/FragmentHighlighter/index.ts
rename to packages/components-front/src/fragments/FragmentHighlighter/index.ts
index abf29b51f..e7836c7a9 100644
--- a/temp/fragments/FragmentHighlighter/index.ts
+++ b/packages/components-front/src/fragments/FragmentHighlighter/index.ts
@@ -1,26 +1,16 @@
import * as THREE from "three";
-import { Fragment, FragmentMesh } from "bim-fragment";
-import {
- Disposable,
- Updateable,
- Event,
- FragmentIdMap,
- Configurable,
-} from "../../base-types";
-import { Component } from "../../base-types/component";
-import { FragmentManager } from "../FragmentManager";
-import { FragmentBoundingBox } from "../FragmentBoundingBox";
-import { Components } from "../../core/Components";
-import { SimpleCamera } from "../../core/SimpleCamera";
-import { ToolComponent } from "../../core/ToolsComponent";
-import { PostproductionRenderer } from "../../navigation/PostproductionRenderer";
+import * as FRAGS from "bim-fragment";
+import * as OBC from "@thatopen/components";
+import { Fragment, FragmentIdMap, FragmentMesh } from "bim-fragment";
+import { World } from "@thatopen/components";
+import { PostproductionRenderer } from "../../navigation";
// TODO: Clean up and document
interface HighlightEvents {
[highlighterName: string]: {
- onHighlight: Event;
- onClear: Event;
+ onHighlight: OBC.Event;
+ onClear: OBC.Event;
};
}
@@ -35,26 +25,25 @@ export interface FragmentHighlighterConfig {
hoverMaterial: THREE.Material;
autoHighlightOnClick: boolean;
cullHighlightMeshes: boolean;
+ world: OBC.World | null;
}
export class FragmentHighlighter
- extends Component
- implements Disposable, Configurable
+ extends OBC.Component
+ implements OBC.Disposable, OBC.Configurable
{
static readonly uuid = "cb8a76f2-654a-4b50-80c6-66fd83cafd77" as const;
- /** {@link Disposable.onDisposed} */
- readonly onDisposed = new Event();
+ readonly onDisposed = new OBC.Event();
- /** {@link Updateable.onBeforeUpdate} */
- readonly onBeforeUpdate = new Event();
+ readonly onBeforeUpdate = new OBC.Event();
- /** {@link Updateable.onAfterUpdate} */
- readonly onAfterUpdate = new Event();
+ readonly onAfterUpdate = new OBC.Event();
+
+ readonly onSetup = new OBC.Event();
needsUpdate = false;
- /** {@link Configurable.isSetup} */
isSetup = false;
enabled = true;
@@ -66,7 +55,7 @@ export class FragmentHighlighter
zoomToSelection = false;
selection: {
- [selectionID: string]: FragmentIdMap;
+ [selectionID: string]: FRAGS.FragmentIdMap;
} = {};
excludeOutline = new Set();
@@ -107,6 +96,7 @@ export class FragmentHighlighter
}),
autoHighlightOnClick: true,
cullHighlightMeshes: true,
+ world: null,
};
private _mouseState = {
@@ -114,29 +104,10 @@ export class FragmentHighlighter
moved: false,
};
- get outlineEnabled() {
- return this._outlineEnabled;
- }
-
- set outlineEnabled(value: boolean) {
- this._outlineEnabled = value;
- if (!value) {
- delete this._postproduction.customEffects.outlinedMeshes.fragments;
- }
- }
-
- private get _postproduction() {
- if (!(this.components.renderer instanceof PostproductionRenderer)) {
- throw new Error("Postproduction renderer is needed for outlines!");
- }
- const renderer = this.components.renderer as PostproductionRenderer;
- return renderer.postproduction;
- }
-
- constructor(components: Components) {
+ constructor(components: OBC.Components) {
super(components);
- this.components.tools.add(FragmentHighlighter.uuid, this);
- const fragmentManager = components.tools.get(FragmentManager);
+ this.components.add(FragmentHighlighter.uuid, this);
+ const fragmentManager = components.get(OBC.FragmentManager);
fragmentManager.onFragmentsDisposed.add(this.onFragmentsDisposed);
}
@@ -147,8 +118,16 @@ export class FragmentHighlighter
this.disposeOutlinedMeshes(data.fragmentIDs);
};
- get(): HighlightMaterials {
- return this.highlightMats;
+ getOutlineEnabled() {
+ return this._outlineEnabled;
+ }
+
+ setOutlineEnabled(value: boolean, world: OBC.World) {
+ this._outlineEnabled = value;
+ if (!value) {
+ const postproduction = this.getPostproduction(world);
+ delete postproduction.customEffects.outlinedMeshes.fragments;
+ }
}
getHoveredSelection() {
@@ -186,14 +165,14 @@ export class FragmentHighlighter
this.events[name].onHighlight.reset();
}
this.onSetup.reset();
- const fragmentManager = this.components.tools.get(FragmentManager);
+ const fragmentManager = this.components.get(OBC.FragmentManager);
fragmentManager.onFragmentsDisposed.remove(this.onFragmentsDisposed);
this.events = {};
- await this.onDisposed.trigger(FragmentHighlighter.uuid);
+ this.onDisposed.trigger(FragmentHighlighter.uuid);
this.onDisposed.reset();
}
- async add(name: string, material?: THREE.Material[]) {
+ add(name: string, material?: THREE.Material[]) {
if (this.highlightMats[name]) {
throw new Error("A highlight with this name already exists.");
}
@@ -201,23 +180,22 @@ export class FragmentHighlighter
this.highlightMats[name] = material;
this.selection[name] = {};
this.events[name] = {
- onHighlight: new Event(),
- onClear: new Event(),
+ onHighlight: new OBC.Event(),
+ onClear: new OBC.Event(),
};
- await this.updateHighlight();
+ this.updateHighlight();
}
- async updateHighlight() {
+ updateHighlight() {
if (!this.fillEnabled) {
return;
}
- await this.onBeforeUpdate.trigger(this);
- const fragments = this.components.tools.get(FragmentManager);
- for (const fragmentID in fragments.list) {
- const fragment = fragments.list[fragmentID];
+ this.onBeforeUpdate.trigger(this);
+ const fragments = this.components.get(OBC.FragmentManager);
+ for (const [id, fragment] of fragments.list) {
this.addHighlightToFragment(fragment);
- const outlinedMesh = this._outlinedMeshes[fragmentID];
+ const outlinedMesh = this._outlinedMeshes[id];
if (outlinedMesh) {
fragment.mesh.updateMatrixWorld(true);
outlinedMesh.position.set(0, 0, 0);
@@ -226,24 +204,32 @@ export class FragmentHighlighter
outlinedMesh.applyMatrix4(fragment.mesh.matrixWorld);
}
}
- await this.onAfterUpdate.trigger(this);
+ this.onAfterUpdate.trigger(this);
}
async highlight(
name: string,
removePrevious = true,
- zoomToSelection = this.zoomToSelection
+ zoomToSelection = this.zoomToSelection,
) {
if (!this.enabled) return null;
+ if (!this.config.world) {
+ throw new Error("No world found in config!");
+ }
+
+ const world = this.config.world;
this.checkSelection(name);
- const fragments = this.components.tools.get(FragmentManager);
+ const fragments = this.components.get(OBC.FragmentManager);
const fragList: Fragment[] = [];
const meshes = fragments.meshes;
- const result = this.components.raycaster.castRay(meshes);
+
+ const casters = this.components.get(OBC.Raycasters);
+ const caster = casters.get(world);
+ const result = caster.castRay(meshes);
if (!result || !result.face) {
- await this.clear(name);
+ this.clear(world, name);
return null;
}
@@ -256,7 +242,7 @@ export class FragmentHighlighter
}
if (removePrevious) {
- await this.clear(name);
+ this.clear(world, name);
}
if (!this.selection[name][mesh.fragment.id]) {
@@ -271,7 +257,7 @@ export class FragmentHighlighter
}
this.selection[name][mesh.fragment.id].add(itemID);
- await this.regenerate(name, mesh.fragment.id);
+ await this.regenerate(name, mesh.fragment.id, world);
const group = mesh.fragment.group;
if (group) {
@@ -289,7 +275,10 @@ export class FragmentHighlighter
}
if (fragID === mesh.fragment.id) continue;
- const fragment = fragments.list[fragID];
+ const fragment = fragments.list.get(fragID);
+ if (!fragment) {
+ throw new Error("Fragment not found!");
+ }
fragList.push(fragment);
if (!this.selection[name][fragID]) {
@@ -297,14 +286,14 @@ export class FragmentHighlighter
}
this.selection[name][fragID].add(itemID);
- await this.regenerate(name, fragID);
+ await this.regenerate(name, fragID, world);
}
}
- await this.events[name].onHighlight.trigger(this.selection[name]);
+ this.events[name].onHighlight.trigger(this.selection[name]);
if (zoomToSelection) {
- await this.zoomSelection(name);
+ await this.zoomSelection(name, world);
}
return { id: itemID, fragments: fragList };
@@ -314,12 +303,20 @@ export class FragmentHighlighter
name: string,
ids: FragmentIdMap,
removePrevious = true,
- zoomToSelection = this.zoomToSelection
+ zoomToSelection = this.zoomToSelection,
) {
if (!this.enabled) return;
+
+ if (!this.config.world) {
+ throw new Error("No world found in config!");
+ }
+
+ const world = this.config.world;
+
if (removePrevious) {
- await this.clear(name);
+ this.clear(world, name);
}
+
const styles = this.selection[name];
for (const fragID in ids) {
if (!styles[fragID]) {
@@ -330,62 +327,65 @@ export class FragmentHighlighter
styles[fragID].add(id);
}
- await this.regenerate(name, fragID);
+ await this.regenerate(name, fragID, world);
}
- await this.events[name].onHighlight.trigger(this.selection[name]);
+ this.events[name].onHighlight.trigger(this.selection[name]);
if (zoomToSelection) {
- await this.zoomSelection(name);
+ await this.zoomSelection(name, world);
}
}
/**
* Clears any selection previously made by calling {@link highlight}.
*/
- async clear(name?: string) {
- await this.clearFills(name);
+ clear(world: World, name?: string) {
+ this.clearFills(name);
if (!name || !this.excludeOutline.has(name)) {
- await this.clearOutlines();
+ this.clearOutlines(world);
}
}
- readonly onSetup = new Event();
-
- async setup(config?: Partial) {
+ setup(config?: Partial) {
if (config?.selectionMaterial) {
this.config.selectionMaterial.dispose();
}
if (config?.hoverMaterial) {
this.config.hoverMaterial.dispose();
}
+
this.config = { ...this.config, ...config };
this.outlineMaterial.color.set(0xf0ff7a);
this.excludeOutline.add(this.config.hoverName);
- await this.add(this.config.selectName, [this.config.selectionMaterial]);
- await this.add(this.config.hoverName, [this.config.hoverMaterial]);
+ this.add(this.config.selectName, [this.config.selectionMaterial]);
+ this.add(this.config.hoverName, [this.config.hoverMaterial]);
this.setupEvents(true);
this.enabled = true;
this.isSetup = true;
- await this.onSetup.trigger(this);
+ this.onSetup.trigger(this);
}
- private async regenerate(name: string, fragID: string) {
+ private async regenerate(name: string, fragID: string, world: OBC.World) {
if (this.fillEnabled) {
await this.updateFragmentFill(name, fragID);
}
if (this._outlineEnabled) {
- await this.updateFragmentOutline(name, fragID);
+ await this.updateFragmentOutline(name, fragID, world);
}
}
- private async zoomSelection(name: string) {
+ private async zoomSelection(name: string, world: OBC.World) {
if (!this.fillEnabled && !this._outlineEnabled) {
return;
}
- const bbox = this.components.tools.get(FragmentBoundingBox);
- const fragments = this.components.tools.get(FragmentManager);
+ if (!world.camera.hasCameraControls()) {
+ return;
+ }
+
+ const bbox = this.components.get(OBC.FragmentBoundingBox);
+ const fragments = this.components.get(OBC.FragmentManager);
bbox.reset();
const selected = this.selection[name];
@@ -393,7 +393,8 @@ export class FragmentHighlighter
return;
}
for (const fragID in selected) {
- const fragment = fragments.list[fragID];
+ const fragment = fragments.list.get(fragID);
+ if (!fragment) continue;
if (this.fillEnabled) {
const highlight = fragment.fragments[name];
if (highlight) {
@@ -416,16 +417,17 @@ export class FragmentHighlighter
if (isInf || isMInf || isZero) {
return;
}
+
sphere.radius *= this.zoomFactor;
- const camera = this.components.camera as SimpleCamera;
+ const camera = world.camera;
await camera.controls.fitToSphere(sphere, true);
}
- private async clearStyle(name: string) {
- const fragments = this.components.tools.get(FragmentManager);
+ private clearStyle(name: string) {
+ const fragments = this.components.get(OBC.FragmentManager);
for (const fragID in this.selection[name]) {
- const fragment = fragments.list[fragID];
+ const fragment = fragments.list.get(fragID);
if (!fragment) continue;
const selection = fragment.fragments[name];
if (selection) {
@@ -433,15 +435,15 @@ export class FragmentHighlighter
}
}
- await this.events[name].onClear.trigger(null);
+ this.events[name].onClear.trigger(null);
this.selection[name] = {};
}
private async updateFragmentFill(name: string, fragmentID: string) {
- const fragments = this.components.tools.get(FragmentManager);
+ const fragments = this.components.get(OBC.FragmentManager);
const ids = this.selection[name][fragmentID];
- const fragment = fragments.list[fragmentID];
+ const fragment = fragments.list.get(fragmentID);
if (!fragment) return;
const selection = fragment.fragments[name];
if (!selection) return;
@@ -472,15 +474,16 @@ export class FragmentHighlighter
}
}
- private async clearFills(name: string | undefined) {
+ private clearFills(name: string | undefined) {
const names = name ? [name] : Object.keys(this.selection);
for (const name of names) {
- await this.clearStyle(name);
+ this.clearStyle(name);
}
}
- private async clearOutlines() {
- const effects = this._postproduction.customEffects;
+ private clearOutlines(world: OBC.World) {
+ const postproduction = this.getPostproduction(world);
+ const effects = postproduction.customEffects;
const fragmentsOutline = effects.outlinedMeshes.fragments;
if (fragmentsOutline) {
fragmentsOutline.meshes.clear();
@@ -491,8 +494,12 @@ export class FragmentHighlighter
}
}
- private async updateFragmentOutline(name: string, fragmentID: string) {
- const fragments = this.components.tools.get(FragmentManager);
+ private async updateFragmentOutline(
+ name: string,
+ fragmentID: string,
+ world: OBC.World,
+ ) {
+ const fragments = this.components.get(OBC.FragmentManager);
if (!this.selection[name][fragmentID]) {
return;
@@ -503,11 +510,12 @@ export class FragmentHighlighter
}
const ids = this.selection[name][fragmentID];
- const fragment = fragments.list[fragmentID];
+ const fragment = fragments.list.get(fragmentID);
if (!fragment) return;
const geometry = fragment.mesh.geometry;
- const customEffects = this._postproduction.customEffects;
+ const postproduciton = this.getPostproduction(world);
+ const customEffects = postproduciton.customEffects;
if (!customEffects.outlinedMeshes.fragments) {
customEffects.outlinedMeshes.fragments = {
@@ -527,7 +535,7 @@ export class FragmentHighlighter
const newMesh = new THREE.InstancedMesh(
newGeometry,
this._invisibleMaterial,
- fragment.capacity
+ fragment.capacity,
);
newMesh.frustumCulled = false;
newMesh.renderOrder = 999;
@@ -535,7 +543,7 @@ export class FragmentHighlighter
newMesh.applyMatrix4(fragment.mesh.matrixWorld);
this._outlinedMeshes[fragmentID] = newMesh;
- const scene = this.components.scene.get();
+ const scene = world.scene.three;
scene.add(newMesh);
}
@@ -558,7 +566,15 @@ export class FragmentHighlighter
}
private setupEvents(active: boolean) {
- const container = this.components.renderer.get().domElement;
+ if (!this.config.world) {
+ return;
+ }
+
+ if (!this.config.world.renderer) {
+ throw new Error("The given world doesn't have a renderer!");
+ }
+
+ const container = this.config.world.renderer.three.domElement;
if (active === this._eventsActive) {
return;
@@ -583,8 +599,15 @@ export class FragmentHighlighter
};
private onMouseUp = async (event: MouseEvent) => {
+ const world = this.config.world;
+ if (!world) {
+ throw new Error("No world found!");
+ }
+ if (!world.renderer) {
+ throw new Error("This world doesn't have a renderer!");
+ }
if (!this.enabled) return;
- if (event.target !== this.components.renderer.get().domElement) return;
+ if (event.target !== world.renderer.three.domElement) return;
this._mouseState.down = false;
if (this._mouseState.moved || event.button !== 0) {
this._mouseState.moved = false;
@@ -600,13 +623,22 @@ export class FragmentHighlighter
private onMouseMove = async () => {
if (!this.enabled) return;
if (this._mouseState.moved) {
- await this.clearFills(this.config.hoverName);
+ this.clearFills(this.config.hoverName);
return;
}
this._mouseState.moved = this._mouseState.down;
await this.highlight(this.config.hoverName, true, false);
};
-}
-ToolComponent.libraryUUIDs.add(FragmentHighlighter.uuid);
+ private getPostproduction(world: OBC.World) {
+ if (!world.renderer) {
+ throw new Error("The given world doesn't have a renderer!");
+ }
+ if (!(world.renderer instanceof PostproductionRenderer)) {
+ throw new Error("Postproduction renderer is needed for outlines!");
+ }
+ const renderer = world.renderer as PostproductionRenderer;
+ return renderer.postproduction;
+ }
+}