Skip to main content

Worlds

Source

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

🌎 Creating our 3D world


In this tutorial you'll learn how to create a simple scene using @thatopen/components.

Hello world!

A world represents a 3D environment in your application. It consists of a scene, a camera and (optionally) a renderer. You can create multiple worlds and show them in multiple viewports at the same time.

In this tutorial, we will import:

  • Three.js to get some 3D entities for our app.
  • @thatopen/components to set up the barebone of our app.
  • @thatopen/ui to add some simple and cool UI menus.
  • Stats.js (optional) to measure the performance of our app.
import * as THREE from "three";
import * as BUI from "@thatopen/ui";
import Stats from "stats.js";
import * as OBC from "@thatopen/components";

🖼️ Getting the container


Next, we need to tell the library where do we want to render the 3D scene. We have added an DIV element to this HTML page that occupies the whole width and height of the viewport. Let's fetch it by its ID:

const container = document.getElementById("container")!;

🚀 Creating a components instance


Now we will create a new instance of the Components class. This class is the main entry point of the library. It will be used to register and manage all the components in your application.

Don't forget to dispose it when you are done!

Once you are done with your application, you need to dispose the Components instance to free up the memory. This is a requirement of Three.js, which can't dispose the memory of 3D related elements automatically.

const components = new OBC.Components();

🌎 Setting up the world


Now we are ready to create our first world. We will use the Worlds component to manage all the worlds in your application. Instead of instancing it, we can get it from the Components instance. All components are singleton, so this is always a better way to get them.

const worlds = components.get(OBC.Worlds);

We can create a new world by calling the create method of the Worlds component. It's a generic method, so we can specify the type of the scene, the camera and the renderer we want to use.

const world = worlds.create<
OBC.SimpleScene,
OBC.SimpleCamera,
OBC.SimpleRenderer
>();

Now we can set the scene, the camera and the renderer of the world, and call the init method to start the rendering process.

world.scene = new OBC.SimpleScene(components);
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.SimpleCamera(components);

components.init();

We could add some lights, but the SimpleScene class can do that easier for us using its setup method:

world.scene.setup();

We'll make the background of the scene transparent so that it looks good in our docs page, but you don't have to do that in your app!

world.scene.three.background = null;

💄 Adding things to our scene


Now we are ready to start adding some 3D entities to our scene. We will add a simple cube:

const material = new THREE.MeshLambertMaterial({ color: "#6528D7" });
const geometry = new THREE.BoxGeometry();
const cube = new THREE.Mesh(geometry, material);
world.scene.three.add(cube);

Finally, we will make the camera look at the cube:

world.camera.controls.setLookAt(3, 3, 3, 0, 0, 0);

⏱️ 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());

🧩 Adding some UI


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 create a new panel with some inputs to change the background color of the scene and the intensity of the directional and ambient lights. For more information about the UI library, you can check the specific documentation for it!

const panel = BUI.Component.create<BUI.PanelSection>(() => {
return BUI.html`
<bim-panel label="Worlds Tutorial" class="options-menu">
<bim-panel-section collapsed label="Controls">

<bim-color-input
label="Background Color" color="#202932"
@input="${({ target }: { target: BUI.ColorInput }) => {
world.scene.config.backgroundColor = new THREE.Color(target.color);
}}">
</bim-color-input>

<bim-number-input
slider step="0.1" label="Directional lights intensity" value="1.5" min="0.1" max="10"
@change="${({ target }: { target: BUI.NumberInput }) => {
world.scene.config.directionalLight.intensity = target.value;
}}">
</bim-number-input>

<bim-number-input
slider step="0.1" label="Ambient light intensity" value="1" min="0.1" max="5"
@change="${({ target }: { target: BUI.NumberInput }) => {
world.scene.config.ambientLight.intensity = target.value;
}}">
</bim-number-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);

🎉 Wrap up


That's it! You have created your first 3D world and added some UI elements to it. You can now play with the inputs to see how the scene changes.