Skip to main content

Hoverer

Source

Copying and pasting? We've got you covered! You can find the full source code of this tutorial here.

πŸ“„ Hovering Items​


Hovering is a key interaction in 3D applications, offering visual feedback as users explore a scene. When hovering over an object, its appearance changesβ€”often through color or shadingβ€”highlighting it and enhancing usability. In this tutorial, you'll learn how to implement hovering effectively.

Why Hovering Matters?

Hovering isn't just aesthetic; it communicates interactivity and focus, making your app feel dynamic and responsive.

πŸ–– Importing our Libraries​

First things first, let's install all necessary dependencies to make this example work:

import * as THREE from "three";
import Stats from "stats.js";
import * as OBC from "@thatopen/components";
import * as BUI from "@thatopen/ui";
// You have to import * as OBF from "@thatopen/components-front"
import * as OBF from "../..";

🌎 Setting up a Simple Scene​

To get started, let's set up a basic ThreeJS scene. This will serve as the foundation for our application and allow us to visualize the 3D models effectively:

const components = new OBC.Components();

const worlds = components.get(OBC.Worlds);
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBF.PostproductionRenderer
>();

world.scene = new OBC.SimpleScene(components);
world.scene.setup();
world.scene.three.background = null;

const container = document.getElementById("container")!;
world.renderer = new OBF.PostproductionRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
await world.camera.controls.setLookAt(68, 23, -8.5, 21.5, -5.5, 23);

components.init();

πŸ› οΈ Setting Up Fragments​

Now, let's configure the FragmentsManager. This will allow us to load models effortlessly and start manipulating them with ease:

const workerUrl =
"https://thatopen.github.io/engine_fragment/resources/worker.mjs";
const fragments = components.get(OBC.FragmentsManager);
fragments.init(workerUrl);

world.camera.controls.addEventListener("rest", () =>
fragments.core.update(true),
);

world.onCameraChanged.add((camera) => {
for (const [, model] of fragments.list) {
model.useCamera(camera.three);
}
fragments.core.update(true);
});

fragments.list.onItemSet.add(({ value: model }) => {
model.useCamera(world.camera.three);
world.scene.three.add(model.object);
fragments.core.update(true);
});

πŸ“‚ Loading Fragments Models​

With the core setup complete, it's time to load a Fragments model into our scene. Fragments are optimized for fast loading and rendering, making them ideal for large-scale 3D models.

Where can I find Fragment files?

You can use the sample Fragment files available in our repository for testing. If you have an IFC model you'd like to convert to Fragments, check out the IfcImporter tutorial for detailed instructions.

const fragPaths = ["https://thatopen.github.io/engine_components/resources/frags/school_arq.frag"];
await Promise.all(
fragPaths.map(async (path) => {
const modelId = path.split("/").pop()?.split(".").shift();
if (!modelId) return null;
const file = await fetch(path);
const buffer = await file.arrayBuffer();
return fragments.core.load(buffer, { modelId });
}),
);

✨ Using The Hoverer Component​

Using the component is extremely simple. The only thing you need to do is get the instance and configure it:

const hoverer = components.get(OBF.Hoverer);
hoverer.world = world;
hoverer.enabled = true;
hoverer.material = new THREE.MeshBasicMaterial({
color: 0x6528d7,
transparent: true, // transparent must be true to allow the animation
opacity: 0.5, // this will act as the maximum possible opacity when animating
depthTest: false, // recommended to avoid z-fighting
});

We will use the @thatopen/ui library to add some simple and cool UI elements to our app. First, we need to call the init method of the BUI.Manager class to initialize the library:

BUI.Manager.init();

Now we will add some UI to play around with the actions in this tutorial. For more information about the UI library, you can check the specific documentation for it!

const panel = BUI.Component.create<BUI.PanelSection>(() => {
const onChange = ({ target }: { target: BUI.ColorInput }) => {
if (
!(
"color" in hoverer.material &&
hoverer.material.color instanceof THREE.Color
)
) {
return;
}

hoverer.material.color.set(target.color);
};

return BUI.html`
<bim-panel active label="Hoverer Tutorial" class="options-menu">
<bim-panel-section label="Controls">
<bim-color-input color="#${((hoverer.material as any).color as THREE.Color).getHexString()}" label="Color" @input=${onChange}></bim-color-input>
</bim-panel-section>
</bim-panel>
`;
});

document.body.append(panel);

And we will make some logic that adds a button to the screen when the user is visiting our app from their phone, allowing to show or hide the menu. Otherwise, the menu would make the app unusable.

const button = BUI.Component.create<BUI.PanelSection>(() => {
return BUI.html`
<bim-button class="phone-menu-toggler" icon="solar:settings-bold"
@click="${() => {
if (panel.classList.contains("options-menu-visible")) {
panel.classList.remove("options-menu-visible");
} else {
panel.classList.add("options-menu-visible");
}
}}">
</bim-button>
`;
});

document.body.append(button);

⏱️ Measuring the performance (optional)​

We'll use the Stats.js to measure the performance of our app. We will add it to the top left corner of the viewport. This way, we'll make sure that the memory consumption and the FPS of our app are under control.

const stats = new Stats();
stats.showPanel(2);
document.body.append(stats.dom);
stats.dom.style.left = "0px";
stats.dom.style.zIndex = "unset";
world.renderer.onBeforeUpdate.add(() => stats.begin());
world.renderer.onAfterUpdate.add(() => stats.end());

πŸŽ‰ Wrap up​

That's it! Now you're able to implement hovering functionality in your 3D applications. Congratulations! Keep exploring more tutorials in the documentation to enhance your skills further.