-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: first implementation of color-based highlihgter
- Loading branch information
Showing
4 changed files
with
175 additions
and
611 deletions.
There are no files selected for viewing
232 changes: 40 additions & 192 deletions
232
packages/components-front/src/fragments/FragmentHighlighter/example.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,224 +1,72 @@ | ||
// Set up scene (see SimpleScene tutorial) | ||
|
||
import * as THREE from "three"; | ||
/* eslint import/no-extraneous-dependencies: 0 */ | ||
|
||
import * as WEBIFC from "web-ifc"; | ||
import Stats from "stats.js"; | ||
// @ts-ignore | ||
import * as dat from "three/examples/jsm/libs/lil-gui.module.min"; | ||
import * as OBC from "../.."; | ||
import * as OBC from "@thatopen/components"; | ||
import * as OBCF from "../.."; | ||
|
||
const container = document.getElementById("container")!; | ||
|
||
const components = new OBC.Components(); | ||
|
||
const sceneComponent = new OBC.SimpleScene(components); | ||
sceneComponent.setup(); | ||
components.scene = sceneComponent; | ||
|
||
const rendererComponent = new OBC.PostproductionRenderer(components, container); | ||
components.renderer = rendererComponent; | ||
const worlds = components.get(OBC.Worlds); | ||
|
||
const cameraComponent = new OBC.SimpleCamera(components); | ||
components.camera = cameraComponent; | ||
const world = worlds.create< | ||
OBC.SimpleScene, | ||
OBC.SimpleCamera, | ||
OBC.SimpleRenderer | ||
>(); | ||
|
||
components.raycaster = new OBC.SimpleRaycaster(components); | ||
world.scene = new OBC.SimpleScene(components); | ||
world.renderer = new OBC.SimpleRenderer(components, container); | ||
world.camera = new OBC.SimpleCamera(components); | ||
|
||
components.init(); | ||
|
||
rendererComponent.postproduction.enabled = true; | ||
|
||
const scene = components.scene.get(); | ||
|
||
cameraComponent.controls.setLookAt(10, 10, 10, 0, 0, 0); | ||
|
||
const directionalLight = new THREE.DirectionalLight(); | ||
directionalLight.position.set(5, 10, 3); | ||
directionalLight.intensity = 0.5; | ||
scene.add(directionalLight); | ||
|
||
const ambientLight = new THREE.AmbientLight(); | ||
ambientLight.intensity = 0.5; | ||
scene.add(ambientLight); | ||
|
||
const grid = new OBC.SimpleGrid(components, new THREE.Color(0x666666)); | ||
const gridMesh = grid.get(); | ||
const effects = rendererComponent.postproduction.customEffects; | ||
effects.excludedMeshes.push(gridMesh); | ||
|
||
/* MD | ||
### 🤏 Nudging Fragments | ||
--- | ||
Until now, you could add IFC files to your BIM App and render Fragments in your scene. | ||
In this lesson, we will show you how to select Fragment elements. | ||
This may appear to be an immense task but believe us when we say it will take less than 5 minutes to complete.🚀 | ||
:::tip First, let's set up a simple scene! | ||
👀 If you haven't started there, check out [that tutorial first](SimpleScene.mdx)! | ||
::: | ||
#### Managing Fragments | ||
--- | ||
To deal with Fragments we will use `FragmentManager`, it will help us to render **Fragments** easily.💪 | ||
*/ | ||
|
||
const fragments = new OBC.FragmentManager(components); | ||
|
||
/* MD | ||
:::tip 🏔️ Showing Fragments in the Scene | ||
Fragment Manager has its own tutorial, check out [that tutorial here](FragmentManager.mdx)! | ||
::: | ||
### 📌 Selecting Fragments | ||
--- | ||
We will start by using [Fragment Highlighter](../api/classes/components.FragmentHighlighter), | ||
which needs reference of `component` and `fragments` to be provided to it.🧮 | ||
Doing so, we will get an instance of **Fragment Highlighter** which will be used for our selection mechanism. | ||
*/ | ||
|
||
const highlighter = new OBC.FragmentHighlighter(components); | ||
|
||
const file = await fetch("../../../resources/small.frag"); | ||
const dataBlob = await file.arrayBuffer(); | ||
const buffer = new Uint8Array(dataBlob); | ||
fragments.load(buffer); | ||
highlighter.updateHighlight(); | ||
|
||
rendererComponent.postproduction.customEffects.outlineEnabled = true; | ||
highlighter.outlineEnabled = true; | ||
|
||
/* MD | ||
### 🎨 Changing Highlight Appearance | ||
--- | ||
The beauty of **components** is it understands `three.js`, | ||
this will help us to instantly change the Highlight color for the Fragments selection. | ||
We will use **red** color and create a `MeshBasicMaterial`, | ||
which we will pass to the highlighter component using `highlighter.add()`. | ||
To remember the highlighter material created, we will pass first variable as the Highlighter Name.😎 | ||
*/ | ||
|
||
const highlightMaterial = new THREE.MeshBasicMaterial({ | ||
color: "#BCF124", | ||
depthTest: false, | ||
opacity: 0.8, | ||
transparent: true, | ||
}); | ||
|
||
highlighter.add("default", [highlightMaterial]); | ||
highlighter.outlineMaterial.color.set(0xf0ff7a); | ||
|
||
/* MD | ||
### 🛎️ Getting Selection Events | ||
--- | ||
Now that we have our base setup ready, we will now implement the logic needed for selection of Fragment. | ||
Let's start by declaring a variable - `lastSelection` which will hold the **`fragment.id`** of the Fragment that was selected. | ||
Also, we define a variable which will convey Highlighter to only perform single selection and not multiple selection.🎯 | ||
*/ | ||
|
||
let lastSelection: Record<string, any>; | ||
|
||
const singleSelection = { | ||
value: true, | ||
}; | ||
|
||
/* MD | ||
#### Performing Highlighting On Click | ||
Now comes the exciting part: we will add an event listener to the **container** that will detect click events.🖱️ | ||
When a click is detected, the function `highlightOnClick()` is called, | ||
which checks internally to see if the Fragment was present and returns the ID of the Fragment that was clicked on.📭 | ||
To highlight the selection based upon the material you had created, you must pass the `Highlighter Name`. | ||
*/ | ||
|
||
async function highlightOnClick() { | ||
const result = await highlighter.highlight("default", singleSelection.value); | ||
if (result) { | ||
lastSelection = {}; | ||
for (const fragment of result.fragments) { | ||
const fragmentID = fragment.id; | ||
lastSelection[fragmentID] = [result.id]; | ||
} | ||
} | ||
} | ||
world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10); | ||
|
||
container.addEventListener("click", () => highlightOnClick()); | ||
world.scene.setup(); | ||
|
||
/* MD | ||
const grids = components.get(OBC.Grids); | ||
grids.create(world); | ||
|
||
What if you need to highlight the item using Fragment ID? | ||
// const fragments = components.get(OBC.FragmentManager); | ||
const fragmentIfcLoader = components.get(OBC.FragmentIfcLoader); | ||
|
||
The same instance of Highlighter can be used to choose elements based on **Fragment ID**.🧩 | ||
await fragmentIfcLoader.setup(); | ||
|
||
You can use `highlighter.highlightByID()` and provide an array of Fragment IDs to have the work done for you! | ||
const excludedCats = [ | ||
WEBIFC.IFCTENDONANCHOR, | ||
WEBIFC.IFCREINFORCINGBAR, | ||
WEBIFC.IFCREINFORCINGELEMENT, | ||
]; | ||
|
||
*/ | ||
function highlightOnID() { | ||
if (lastSelection !== undefined) { | ||
highlighter.highlightByID("default", lastSelection); | ||
} | ||
for (const cat of excludedCats) { | ||
fragmentIfcLoader.settings.excludedCategories.add(cat); | ||
} | ||
|
||
/* MD | ||
fragmentIfcLoader.settings.webIfc.COORDINATE_TO_ORIGIN = true; | ||
fragmentIfcLoader.settings.webIfc.OPTIMIZE_PROFILES = true; | ||
|
||
**Congratulations** 🎉 on completing this tutorial! Now you can **Highlight** and perform **Selection** on any Fragment Model using | ||
**[Fragment Highlighter Component](../api/classes/components.FragmentHighlighter)** 🎨🖌️ | ||
Let's keep it up and check out another tutorial! 🎓 | ||
const file = await fetch("../../../../../resources/small.ifc"); | ||
const data = await file.arrayBuffer(); | ||
const buffer = new Uint8Array(data); | ||
const model = await fragmentIfcLoader.load(buffer); | ||
model.name = "example"; | ||
world.scene.three.add(model); | ||
|
||
const highlighter = components.get(OBCF.FragmentHighlighter); | ||
highlighter.setup({world}); | ||
|
||
*/ | ||
// Set up stats | ||
|
||
const stats = new Stats(); | ||
stats.showPanel(2); | ||
document.body.append(stats.dom); | ||
stats.dom.style.left = "0px"; | ||
rendererComponent.onBeforeUpdate.add(() => stats.begin()); | ||
rendererComponent.onAfterUpdate.add(() => stats.end()); | ||
|
||
// Set up dat.gui menu | ||
|
||
const commands = { | ||
highlight: "Click", | ||
highlightOnID: () => highlightOnID(), | ||
}; | ||
|
||
const gui = new dat.GUI(); | ||
|
||
gui.add(commands, "highlight"); | ||
gui.add(commands, "highlightOnID").name("Select last selection"); | ||
gui.add(singleSelection, "value").name("Single selection"); | ||
gui.add(highlighter, "zoomToSelection").name("Zoom to selection"); | ||
gui.add(highlighter, "fillEnabled").name("Fill enabled"); | ||
gui.add(highlighter, "outlineEnabled").name("Outline enabled"); | ||
gui.addColor(highlightMaterial, "color").name("Fill color"); | ||
gui.addColor(highlighter.outlineMaterial, "color").name("Outline color"); | ||
gui | ||
.add(highlighter.outlineMaterial, "opacity") | ||
.name("Outline width") | ||
.min(0.3) | ||
.max(1) | ||
.step(0.05); | ||
world.renderer.onBeforeUpdate.add(() => stats.begin()); | ||
world.renderer.onAfterUpdate.add(() => stats.end()); |
Oops, something went wrong.