![cover](https://thatopen.github.io/engine_components/resources/cover.png)
@@ -20,46 +20,53 @@
This library is a collection of BIM tools based on [Three.js](https://github.com/mrdoob/three.js/) and other libraries. It includes pre-made features to easily build browser-based 3D BIM applications, such as postproduction, dimensions, floorplan navigation, DXF export and much more.
+### Packages
+
+This library contains 2 packages:
+
+`@thatopen/components` - The core functionality. Compatible both with browser and Node.js environments.
+
+`@thatopen/components-front` - Features exclusive for browser environments.
+
### Usage
You need to be familiar with [Three.js API](https://github.com/mrdoob/three.js/) to be able to use this library effectively. In the following example, we will create a cube in a 3D scene that can be navigated with the mouse or touch events. You can see the full example [here](https://github.com/ThatOpen/engine_components/blob/main/src/core/SimpleScene/index.html) and the deployed app [here](https://thatopen.github.io/engine_components/src/core/SimpleScene/index.html).
```js
-import * as THREE from "three";
-import * as OBC from "openbim-components";
-
-// Get the
element where the scene will be displayed
+/* eslint import/no-extraneous-dependencies: 0 */
-const container = document.getElementById('container');
+import * as THREE from "three";
+import * as OBC from "../..";
-// Initialize the basic components needed to use this library
+const container = document.getElementById("container")!;
const components = new OBC.Components();
-components.scene = new OBC.SimpleScene(components);
-components._renderer = new OBC.Index(components, container);
-components.camera = new OBC.SimpleCamera(components);
-components.raycaster = new OBC.SimpleRaycaster(components);
-
-components.init();
+const worlds = components.get(OBC.Worlds);
-// Add some elements to the scene
+const world = worlds.create<
+ OBC.SimpleScene,
+ OBC.SimpleCamera,
+ OBC.SimpleRenderer
+>();
-components.scene.setup();
+world.scene = new OBC.SimpleScene(components);
+world.renderer = new OBC.SimpleRenderer(components, container);
+world.camera = new OBC.SimpleCamera(components);
-const scene = components.scene.get();
+components.init();
-const geometry = new THREE.BoxGeometry(3, 3, 3);
-const material = new THREE.MeshStandardMaterial({ color: "red" });
+const material = new THREE.MeshLambertMaterial({ color: "#6528D7" });
+const geometry = new THREE.BoxGeometry();
const cube = new THREE.Mesh(geometry, material);
-cube.position.set(0, 1.5, 0);
-scene.add(cube);
+world.scene.three.add(cube);
-components.meshes.push(cube);
-```
+world.scene.setup();
+world.camera.controls.setLookAt(3, 3, 3, 0, 0, 0);
+```
-[npm]: https://img.shields.io/npm/v/openbim-components
-[npm-url]: https://www.npmjs.com/package/openbim-components
-[npm-downloads]: https://img.shields.io/npm/dw/openbim-components
+[npm]: https://img.shields.io/npm/v/@thatopen/components
+[npm-url]: https://www.npmjs.com/package/@thatopen/components
+[npm-downloads]: https://img.shields.io/npm/dw/@thatopen/components
diff --git a/packages/core/package.json b/packages/core/package.json
index 693036b7b..6b5f73d6a 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,7 +1,7 @@
{
"name": "@thatopen/components",
"description": "Collection of core functionalities to author BIM apps.",
- "version": "2.0.0-alpha.15",
+ "version": "2.0.0-alpha.16",
"author": "That Open Company",
"contributors": [
"Antonio Gonzalez Viegas (https://github.com/agviegas)",
@@ -38,7 +38,8 @@
},
"devDependencies": {
"@thatopen/fragments": "2.0.0-alpha.5",
- "@thatopen/ui": "2.0.0-alpha.5",
+ "@thatopen/ui": "2.0.0-alpha.12",
+ "@thatopen/ui-obc": "2.0.0-alpha.12",
"@types/jest": "27.0.0",
"@types/node": "20.11.30",
"@types/three": "0.160.0",
diff --git a/packages/core/src/core/Clipper/example.ts b/packages/core/src/core/Clipper/example.ts
index 49aa0529e..588653d65 100644
--- a/packages/core/src/core/Clipper/example.ts
+++ b/packages/core/src/core/Clipper/example.ts
@@ -26,9 +26,6 @@ world.camera.controls.setLookAt(10, 10, 10, 0, 0, 0);
world.scene.setup();
-// @ts-ignore
-// const grid = new OBC.SimpleGrid(components);
-
/* MD
### โ๏ธ Clipping Tool
---
@@ -49,7 +46,7 @@ world.scene.setup();
Let's start by adding a Cube, which we can dissect.
We will create a [Cube](https://threejs.org/docs/index.html?q=box#api/en/geometries/BoxGeometry)
with `3x3x3` dimensions and use red color for the material.
- */
+*/
const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
@@ -157,7 +154,7 @@ world.renderer.onAfterUpdate.add(() => stats.end());
// Set up UI
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/core/Cullers/index.ts b/packages/core/src/core/Cullers/index.ts
index 82c38b889..ab2beb80b 100644
--- a/packages/core/src/core/Cullers/index.ts
+++ b/packages/core/src/core/Cullers/index.ts
@@ -9,20 +9,42 @@ export * from "./src";
* that are not visible to the camera.
*/
export class Cullers extends Component implements Disposable {
+
+ /**
+ * A unique identifier for the Cullers component.
+ */
static readonly uuid = "69f2a50d-c266-44fc-b1bd-fa4d34be89e6" as const;
+ /**
+ * Indicates whether the Cullers component is enabled.
+ */
private _enabled = true;
+ /**
+ * A map of MeshCullerRenderer instances, keyed by their world UUIDs.
+ */
list = new Map();
- /** {@link Disposable.onDisposed} */
+ /**
+ * An event that is triggered when the Cullers component is disposed.
+ */
readonly onDisposed = new Event();
- /** {@link Component.enabled} */
+ /**
+ * Gets the enabled state of the Cullers component.
+ *
+ * @returns The current enabled state.
+ */
get enabled() {
return this._enabled;
}
+ /**
+ * Sets the enabled state of the Cullers component.
+ * Also sets the enabled state of all MeshCullerRenderer instances.
+ *
+ * @param value - The new enabled state.
+ */
set enabled(value: boolean) {
this._enabled = value;
for (const [_id, renderer] of this.list) {
@@ -35,14 +57,23 @@ export class Cullers extends Component implements Disposable {
components.add(Cullers.uuid, this);
}
- create(world: World, config?: Partial) {
- if (this.list.has(world.uuid)) {
- return this.list.get(world.uuid) as MeshCullerRenderer;
- }
- const culler = new MeshCullerRenderer(this.components, world, config);
- this.list.set(world.uuid, culler);
- return culler;
+ /**
+ * Creates a new MeshCullerRenderer for the given world.
+ * If a MeshCullerRenderer already exists for the world, it will return the existing one.
+ *
+ * @param world - The world for which to create the MeshCullerRenderer.
+ * @param config - Optional configuration settings for the MeshCullerRenderer.
+ *
+ * @returns The newly created or existing MeshCullerRenderer for the given world.
+ */
+create(world: World, config?: Partial): MeshCullerRenderer {
+ if (this.list.has(world.uuid)) {
+ return this.list.get(world.uuid) as MeshCullerRenderer;
}
+ const culler = new MeshCullerRenderer(this.components, world, config);
+ this.list.set(world.uuid, culler);
+ return culler;
+}
delete(world: World) {
const culler = this.list.get(world.uuid);
diff --git a/packages/core/src/core/MiniMap/example.ts b/packages/core/src/core/MiniMap/example.ts
index 7bb89aab6..48d7235de 100644
--- a/packages/core/src/core/MiniMap/example.ts
+++ b/packages/core/src/core/MiniMap/example.ts
@@ -123,7 +123,7 @@ stats.dom.style.left = "0px";
world.renderer.onBeforeUpdate.add(() => stats.begin());
world.renderer.onAfterUpdate.add(() => stats.end());
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const mapSize = map.getSize();
diff --git a/packages/core/src/core/OrthoPerspectiveCamera/example.ts b/packages/core/src/core/OrthoPerspectiveCamera/example.ts
index ff1523507..7f2888225 100644
--- a/packages/core/src/core/OrthoPerspectiveCamera/example.ts
+++ b/packages/core/src/core/OrthoPerspectiveCamera/example.ts
@@ -1,54 +1,40 @@
-/* eslint import/no-extraneous-dependencies: 0 */
+/* MD
+### ๐น How to handle a fancy camera
+---
-import Stats from "stats.js";
+Sometimes, you need perspective for depth and realism. Other times, you need an orthographic camera to get precise measurements and proportions. Luckily for you, we have a camera that has both of those projections at the same time! It also has some cool functionality for navigation. In this tutorial, you'll learn to use it.
-import * as THREE from "three";
-import * as BUI from "@thatopen/ui";
-import * as OBC from "../..";
-
-/* MD
- ### ๐ฝ๏ธ Managing Multiple Views
- ---
- Perspective view adds depth and realism, which helps in creating visually compelling representations in 3D scenes.๐ค๏ธ
- While, Orthographic view is important for precise measurements and proportions.๐
+:::tip Orthographic and Perspective cameras
- :::tip First, let's set up a simple scene!
+The difference between Orthographic and Perspective cameras is that Orthographic cameras don't see things smaller when they are further away. This has some implications, like the camera being always "outside" of your scene. You can't see the interior of a room with an orthographic camera. The most common use for orthographic cameras are 2D floor plans and sections, but they can also be used to create cool-looking 3D scenes.
- ๐ If you haven't started there, check out [that tutorial first](SimpleScene.mdx)!
+:::
- :::
+In this tutorial, we will import:
- We'll be using an advanced camera component for this tutorial.
- OrthoPerspectiveCamera makes it simple to use Orthographic and Perspective projections.
+- `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.
- ### ๐ฒ Creating a Cube Mesh
- ---
- First, let's create a simple Cube, which will render differently depending on the projection you choose.๐ง
- We will create a [Cube](https://threejs.org/docs/index.html?q=box#api/en/geometries/BoxGeometry)
- with `3x3x3` dimensions and use red color for the material.๐๏ธ
+*/
- */
+import Stats from "stats.js";
+import * as THREE from "three";
+import * as BUI from "@thatopen/ui";
+import * as OBC from "../..";
-const cubeGeometry = new THREE.BoxGeometry();
-const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
-const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
-cube.position.set(0, 0.5, 0);
/* MD
- ### ๐๏ธ Developing an OrthoPerspective Camera
+ ### ๐ Setting up the world AND the camera
---
- We will create OrthoPerspectiveCamera by passing `components` as an argument to it.๐๏ธ
- The OrthoPerspective Camera extends the SimpleCamera by providing you with extra controls.
-
- We will then configure the camera location and update the look at target using `setLookAt()` API.๐
+ We will start by creating a simple scene with a camera and a renderer. If you don't know how to set up a scene, you can check the Worlds tutorial. But there's one difference: we will use the OrthoPerspectiveCamera for initializing the world.
- */
+*/
const container = document.getElementById("container")!;
-
const components = new OBC.Components();
-
const worlds = components.get(OBC.Worlds);
const world = worlds.create<
@@ -67,41 +53,37 @@ world.scene.setup();
world.camera.controls.setLookAt(3, 3, 3, 0, 0, 0);
-world.scene.three.add(cube);
-world.meshes.add(cube);
-
-const grids = components.get(OBC.Grids);
-const grid = grids.create(world);
-
/* MD
-
- :::info Igniting Components!
- ๐ฅ Whenever the components like scene, camera are created, you need to initialize the component library.
- Check out components.init() for more info!๐
+ Easy, right? Believe it or not, this is all you need to use the OrthoPerspectiveCamera. Now, let's see it in action!
- :::
- ### ๐น๏ธ Changing Views and Navigation
+ ### ๐ง Creating a cube
---
- Now, that our camera setup is done, we need to manage the camera projection on demand.
- #### Toggling Orthographic View and Perspective View
+ We will start by creating a simple cube and a grid that will serve as a reference point for our camera.
+
+*/
- Let's create a simple method **`toggleProjection()`** which toggles the Camera View using `camera.toggleProjection`.
- Alternatively, you can also use `camera.setProjection()` and pass `'Orthographic'` or `'Perspective'` to manage the views.๐ก
+const cubeGeometry = new THREE.BoxGeometry();
+const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
+const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
+cube.position.set(0, 0.5, 0);
+
+world.scene.three.add(cube);
+world.meshes.add(cube);
+
+const grids = components.get(OBC.Grids);
+const grid = grids.create(world);
- */
-// @ts-ignore
-function toggleProjection() {
- world.camera.projection.toggle();
-}
/* MD
- You can also subscribe to an event for when the projection changes. For instance, let's change the grid fading mode
- when the projection changes. This will make the grid look good in orthographic mode:
- */
+ ### ๐๏ธ Using camera events
+ ---
+
+ The OrthoPerspectiveCamera has a few events that you can use to manage the your scene. We will use the `camera.projection.onChanged` event to update the grid, so that when using the Orthographic camera, the grid will fade out if the camera zooms away a lot.
+*/
world.camera.projection.onChanged.add(() => {
const projection = world.camera.projection.current;
@@ -109,39 +91,35 @@ world.camera.projection.onChanged.add(() => {
});
/* MD
- #### Managing Navigation Modes
- Along with projection, we can also manage Navigation modes using **OrthoPerspective** camera.
- To update navigation modes, we will use `camera.setNavigationMode('Orbit' | 'FirstPerson' | 'Plan')`
+ ### ๐งฉ Building a camera UI
+ ---
+
+ Now we will use @thatopen/ui to create a simple UI for the OrthoPerspectiveCamera. It will have 4 elements:
- - **Orbit** - Orbit Mode helps us to easily navigate around the 3D Elements.
- - **FirstPerson** - It helps you to visualize scene from your own perspective.
- First Person mode is only available for Perspective Projection.
- - **Plan** - This mode helps you to easily navigate in 2D Projections.
+ #### ๐๏ธ Navigation mode
- */
+ This will control the navigation mode of the OrthoPerspectiveCamera. It will have 3 options:
+
+ - `Orbit`: for 3D orbiting around the scene.
+ - `FirstPerson`: for navigating the scene in with the mouse wheel in first person.
+ - `Plan`: for navigating 2d plans (blocking the orbit).
-// @ts-ignore
-function setNavigationMode(navMode: OBC.NavModeID) {
- world.camera.set(navMode);
-}
+ #### ๐ Projections
-/* MD
- :::info MORE CONTROLS, MORE POWER
+ Like its name implies, the OrthoPerspectiveCamera has 2 projections, and it's really easy to toggle between them. The camera position will remain the same, which is really convenient when you switch between different projections!
- ๐งฎ OrthoPerspective Camera also provides you an option to adjust your camera to fit the 3D elements.
- You can simply use fitModelToFrame(mesh)
- and provide the mesh which you want to fit to your window frame
+ #### โ Toggling user input
- :::
+ Sometimes you might want to remove control from the user. For example, imagine you are animating the camera and you don't want the user to move the camera around. You can use the `setUserInput` method to toggle this.
- **Congratulations** ๐ on completing this tutorial!
- Now you can add Advance Camera System to your web-app in minutes using
- **OrthoPerspectiveCamera** โ๐ฝ๏ธ
- Let's keep it up and check out another tutorial! ๐
+ #### ๐ Focusing objects
- */
+ The OrthoPerspectiveCamera has a `fit` method that will fit the camera to a list of meshes. This is really useful when you want to bring attention to a specific part of the scene, or for allowing your user to navigate the scene by focusing objects.
+
+*/
-BUI.Manager.registerComponents();
+
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
@@ -163,6 +141,7 @@ const panel = BUI.Component.create(() => {
}
world.camera.set(selected);
}}">
+
@@ -206,10 +185,25 @@ const panel = BUI.Component.create(() => {
document.body.append(panel);
-// Set up stats
+/* MD
+ ### โฑ๏ธ Measuring the performance (optional)
+ ---
+
+ We'll use the [Stats.js](https://github.com/mrdoob/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";
world.renderer.onBeforeUpdate.add(() => stats.begin());
world.renderer.onAfterUpdate.add(() => stats.end());
+
+/* MD
+ ### ๐ Wrap up
+ ---
+
+ That's it! We have created an OrthoPerspective camera that can be used to navigate a 3D scene with multiple projections and navigation modes, as well as a neat UI to control it. Great job!
+
+*/
\ No newline at end of file
diff --git a/packages/core/src/core/OrthoPerspectiveCamera/index.ts b/packages/core/src/core/OrthoPerspectiveCamera/index.ts
index 83406cdd3..13a38afc9 100644
--- a/packages/core/src/core/OrthoPerspectiveCamera/index.ts
+++ b/packages/core/src/core/OrthoPerspectiveCamera/index.ts
@@ -176,12 +176,25 @@ export class OrthoPerspectiveCamera extends SimpleCamera {
if (!this.currentWorld || !this.currentWorld.renderer) {
return;
}
- const size = this.currentWorld.renderer.getSize();
- const aspect = size.x / size.y;
- this.threeOrtho.left = (-this._frustumSize * aspect) / 2;
- this.threeOrtho.right = (this._frustumSize * aspect) / 2;
- this.threeOrtho.top = this._frustumSize / 2;
- this.threeOrtho.bottom = -this._frustumSize / 2;
+
+ const lineOfSight = new THREE.Vector3();
+ this.threePersp.getWorldDirection(lineOfSight);
+ const target = new THREE.Vector3();
+ this.controls.getTarget(target);
+ const distance = target.clone().sub(this.threePersp.position);
+
+ const depth = distance.dot(lineOfSight);
+ const dims = this.currentWorld.renderer.getSize();
+ const aspect = dims.x / dims.y;
+ const camera = this.threePersp;
+ const height = depth * 2 * Math.atan((camera.fov * (Math.PI / 180)) / 2);
+ const width = height * aspect;
+
+ this.threeOrtho.zoom = 1;
+ this.threeOrtho.left = width / -2;
+ this.threeOrtho.right = width / 2;
+ this.threeOrtho.top = height / 2;
+ this.threeOrtho.bottom = height / -2;
this.threeOrtho.updateProjectionMatrix();
}
}
diff --git a/packages/core/src/core/Raycasters/example.ts b/packages/core/src/core/Raycasters/example.ts
index cb830270a..b50af5e5a 100644
--- a/packages/core/src/core/Raycasters/example.ts
+++ b/packages/core/src/core/Raycasters/example.ts
@@ -1,5 +1,34 @@
+/* MD
+### ๐ Picking things with the mouse
+---
+
+In this tutorial you'll learn how to use the Raycaster to pick objects in the scene with the mouse.
+
+:::tip What's ray casting?
+
+Ray casting is the process of casting a ray from a point in space to another point in space. We will cast a ray from the mouse position to the 3D world and check if there is an object in its way. That way, when you hover or click on an object, we can know which one it is and do something with it.
+
+:::
+
+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.
+- `Stats.js` (optional) to measure the performance of our app.
+
+*/
+
import * as THREE from "three";
-import * as OBC from "../..";
+import Stats from "stats.js";
+import * as OBC from "@thatopen/components";
+
+/* MD
+ ### ๐ Setting up a simple scene
+ ---
+
+ We will start by creating a simple scene with a camera and a renderer. If you don't know how to set up a scene, you can check the Worlds tutorial.
+
+*/
const container = document.getElementById("container")!;
@@ -22,25 +51,13 @@ world.camera.controls.setLookAt(10, 10, 10, 0, 0, 0);
world.scene.setup();
-// @ts-ignore
-// const grid = new OBC.SimpleGrid(components);
-
/* MD
- ### ๐ค Touching things
- ___
- In this tutorial, we'll learn to **interact with our scene**. This will work both for a mouse ๐ and for the touch
- screen of a phone ๐ฑ or tablet ๐. This might sound daunting, but it's actually very easy! Let's see **how to do that
- in 5 minutes**.
+ ### ๐ง Adding some cubes to the scene
+ ---
- :::tip First, let's set up a simple scene!
+ Now we will add some cubes to the scene and create some materials. The idea for this app is simple: when you click on a cube, it will change its color to green.
- If you haven't started there, check out [that tutorial first](SimpleScene.mdx)!
-
- :::
-
- Next, we will add some objects to pick. We need 5 meshes that can share the same cube geometry
- (as all the geometry of the cubes is the same).
- */
+*/
const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const greenMaterial = new THREE.MeshStandardMaterial({ color: "#BCF124" });
@@ -53,20 +70,12 @@ const cube3 = new THREE.Mesh(boxGeometry, cubeMaterial);
world.scene.three.add(cube1, cube2, cube3);
const cubes = [cube1, cube2, cube3];
-/* MD
- Let's give the cubes a different position so that we can see all of them in the scene:
- */
-
cube2.position.x = 5;
cube3.position.x = -5;
/* MD
- ### ๐ Spinning up the cubes
- ___
- To spice things up a little, let's create an animation loop that rotates the cubes each frame. You can do this super
- easily by creating a function and adding it to the `beforeUpdate` event of the **RendererComponent** inside
- **Components** using the `on()` method:
- */
+ To make this more interesting, we will add a rotation to the cubes. We can do it by subscribing to the `onBeforeUpdate` event of the world, which fires 60 times per second.
+*/
const oneDegree = Math.PI / 180;
@@ -82,33 +91,24 @@ function rotateCubes() {
world.renderer.onBeforeUpdate.add(rotateCubes);
/* MD
+ ### โก Setting up the raycaster
+ ---
- :::info Turning off animations
+ Next, we will set up the raycaster. We will use the `Raycasters` component. Instead of instantiating it, we will get it from the `components` object, which is usually safer, as all components are meant to be singletons.
+*/
- You can turn off animations simply by using the `off()` method.
- [This works for any event](../api/classes/components.Event)
- in the library! โ
-
- :::
-
- ### โก Casting rays around
- ___
-
- Finally, we will use the raycaster. This is very easy using the
- **[raycaster component](../api/classes/components.SimpleRaycaster)**, which solves this for all screen sizes.
- We will create an event that fires every time that the user moves the mouse. ๐
-
- :::warning How does it really work?
-
- We will simply reset the material of the previous selection (if it exists), and then apply the green material to the
- found object (if any). This might sound like a lot, but it's actually very little code. ๐
+const casters = components.get(OBC.Raycasters);
- :::
- */
+/* MD
+ Each raycaster is bound to a specific world. In this case, we will get the raycaster from the `world` we are using for our scene:
+*/
-const casters = components.get(OBC.Raycasters);
const caster = casters.get(world);
+/* MD
+ Finally, we will subscribe to the mousemove event of the window. We will use the `castRay` method of the raycaster to find out if the mouse is over a cube. If it is, we will change its color to green. Otherwise, we will change its color to the original color.
+*/
+
let previousSelection: THREE.Mesh | null = null;
window.onmousemove = () => {
@@ -124,7 +124,24 @@ window.onmousemove = () => {
};
/* MD
- Great job! ๐ Now you know how to pick geometry in your 3D scene using a
- **[raycaster component](../api/classes/components.SimpleRaycaster)** component! ๐ช You are now one step closer
- to build your own BIM software. Let's keep it up and check out another tutorials!
- */
+ ### โฑ๏ธ Measuring the performance (optional)
+ ---
+
+ We'll use the [Stats.js](https://github.com/mrdoob/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";
+world.renderer.onBeforeUpdate.add(() => stats.begin());
+world.renderer.onAfterUpdate.add(() => stats.end());
+
+/* MD
+ ### ๐ Wrap up
+ ---
+
+ That's it! We have created a simple app that uses the Raycaster to pick objects in the scene with the mouse. Easy, right? Now you can allow your users to interact with your 3D world.
+
+*/
\ No newline at end of file
diff --git a/packages/core/src/core/Worlds/example.ts b/packages/core/src/core/Worlds/example.ts
index 5f8c72520..3fcc53fb8 100644
--- a/packages/core/src/core/Worlds/example.ts
+++ b/packages/core/src/core/Worlds/example.ts
@@ -1,37 +1,130 @@
-/* eslint import/no-extraneous-dependencies: 0 */
+/* MD
+
+### ๐ Creating our 3D world
+---
+
+In this tutorial you'll learn how to create a simple scene using `@thatopen/components`.
+
+:::tip 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 * as OBC from "../..";
+import * as OBC from "@thatopen/components";
+import Stats from "stats.js";
+
+/* MD
+ ### ๐ผ๏ธ 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")!;
+/* MD
+ ### ๐ 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.
+
+ :::tip 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();
+/* MD
+ ### ๐ 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);
+/* MD
+
+ 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
>();
+/* MD
+
+ 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();
+/* MD
+ ### ๐ 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);
+/* MD
+ We could also add some lights, but the SimpleScene class can do that easier for us using its `setup` method:
+*/
+
world.scene.setup();
+/* MD
+ Finally, we will make the camera look at the cube:
+*/
+
world.camera.controls.setLookAt(3, 3, 3, 0, 0, 0);
-BUI.Manager.registerComponents();
+/* MD
+ ### ๐งฉ 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();
+
+/* MD
+ 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(() => {
return BUI.html`
@@ -73,3 +166,26 @@ const panel = BUI.Component.create(() => {
});
document.body.append(panel);
+
+/* MD
+ ### โฑ๏ธ Measuring the performance (optional)
+ ---
+
+ We'll use the [Stats.js](https://github.com/mrdoob/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";
+world.renderer.onBeforeUpdate.add(() => stats.begin());
+world.renderer.onAfterUpdate.add(() => stats.end());
+
+/* MD
+ ### ๐ 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.
+
+*/
diff --git a/packages/core/src/core/Worlds/index.ts b/packages/core/src/core/Worlds/index.ts
index 86aac8d98..f91a5faeb 100644
--- a/packages/core/src/core/Worlds/index.ts
+++ b/packages/core/src/core/Worlds/index.ts
@@ -14,16 +14,40 @@ import { SimpleWorld } from "./src";
export * from "./src";
export class Worlds extends Component implements Updateable, Disposable {
+ /**
+ * A unique identifier for the component.
+ * This UUID is used to register the component within the Components system.
+ */
static readonly uuid = "fdb61dc4-2ec1-4966-b83d-54ea795fad4a" as const;
+ /**
+ * Event triggered after the update process of all worlds.
+ * Subscribers can listen to this event to perform actions post-update.
+ */
readonly onAfterUpdate = new Event();
+ /**
+ * Event triggered before the update process of all worlds.
+ * Subscribers can listen to this event to perform actions pre-update.
+ */
readonly onBeforeUpdate = new Event();
+ /**
+ * Event triggered when the Worlds component is disposed.
+ * Subscribers can listen to this event to perform cleanup actions.
+ */
readonly onDisposed = new Event();
+ /**
+ * A collection of worlds managed by this component.
+ * The key is the unique identifier (UUID) of the world, and the value is the World instance.
+ */
list = new Map();
+ /**
+ * A flag indicating whether the Worlds component is enabled.
+ * If false, the update process will be skipped.
+ */
enabled = true;
constructor(components: Components) {
@@ -31,7 +55,18 @@ export class Worlds extends Component implements Updateable, Disposable {
components.add(Worlds.uuid, this);
}
- create<
+ /**
+ * Creates a new instance of a SimpleWorld and adds it to the list of worlds.
+ *
+ * @template T - The type of the scene, extending from BaseScene. Defaults to BaseScene.
+ * @template U - The type of the camera, extending from BaseCamera. Defaults to BaseCamera.
+ * @template S - The type of the renderer, extending from BaseRenderer. Defaults to BaseRenderer.
+ *
+ * @returns {SimpleWorld} - The newly created SimpleWorld instance.
+ *
+ * @throws {Error} - Throws an error if a world with the same UUID already exists in the list.
+ */
+create<
T extends BaseScene = BaseScene,
U extends BaseCamera = BaseCamera,
S extends BaseRenderer = BaseRenderer,
diff --git a/packages/core/src/core/Worlds/src/simple-camera.ts b/packages/core/src/core/Worlds/src/simple-camera.ts
index 7c3e76ab4..48790cfce 100644
--- a/packages/core/src/core/Worlds/src/simple-camera.ts
+++ b/packages/core/src/core/Worlds/src/simple-camera.ts
@@ -117,6 +117,7 @@ export class SimpleCamera extends BaseCamera implements Updateable, Disposable {
updateAspect = () => {
if (!this.currentWorld || !this.currentWorld.renderer) return;
if (this.three instanceof THREE.OrthographicCamera) {
+ this.onAspectUpdated.trigger();
return;
}
if (this.currentWorld.renderer?.isResizeable()) {
diff --git a/packages/core/src/fragments/BoundingBoxer/example.ts b/packages/core/src/fragments/BoundingBoxer/example.ts
index 4615c85d2..ef9092918 100644
--- a/packages/core/src/fragments/BoundingBoxer/example.ts
+++ b/packages/core/src/fragments/BoundingBoxer/example.ts
@@ -106,7 +106,7 @@ fragmentBbox.reset();
*/
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/fragments/Classifier/example.ts b/packages/core/src/fragments/Classifier/example.ts
index 5690ec00e..9ed6af791 100644
--- a/packages/core/src/fragments/Classifier/example.ts
+++ b/packages/core/src/fragments/Classifier/example.ts
@@ -46,7 +46,7 @@ classifier.byEntity(model);
classifier.byStorey(model);
classifier.byModel(model.uuid, model);
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const walls = classifier.find({
entities: ["IFCWALLSTANDARDCASE"],
diff --git a/packages/core/src/fragments/Exploder/example.ts b/packages/core/src/fragments/Exploder/example.ts
index a2a5a7498..7782fa7f0 100644
--- a/packages/core/src/fragments/Exploder/example.ts
+++ b/packages/core/src/fragments/Exploder/example.ts
@@ -42,7 +42,7 @@ const classifier = components.get(OBC.Classifier);
classifier.byStorey(model);
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/fragments/Hider/example.ts b/packages/core/src/fragments/Hider/example.ts
index dbb05ce19..4ae24aa12 100644
--- a/packages/core/src/fragments/Hider/example.ts
+++ b/packages/core/src/fragments/Hider/example.ts
@@ -108,7 +108,7 @@ for (const name of classNames) {
the visibility of storeys:
*/
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/fragments/IfcGeometryTiler/example.ts b/packages/core/src/fragments/IfcGeometryTiler/example.ts
index e8ae297e0..3e3a4993e 100644
--- a/packages/core/src/fragments/IfcGeometryTiler/example.ts
+++ b/packages/core/src/fragments/IfcGeometryTiler/example.ts
@@ -243,7 +243,7 @@ async function processFile() {
If everything went as expected, you should now be seeing some files being downloaded from your app ๐คฏ Do not get scary if they're a lot, as big models tend to have many files! All of that is the information the streaming uses in order to display the geometry in the most efficient way possible ๐ช
*/
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/fragments/IfcLoader/example.ts b/packages/core/src/fragments/IfcLoader/example.ts
index 0cc225ad6..91ff83556 100644
--- a/packages/core/src/fragments/IfcLoader/example.ts
+++ b/packages/core/src/fragments/IfcLoader/example.ts
@@ -1,8 +1,8 @@
/* eslint import/no-extraneous-dependencies: 0 */
import * as WEBIFC from "web-ifc";
-ยบ// @ts-ignore
import * as BUI from "@thatopen/ui";
+import Stats from "stats.js";
import * as OBC from "../..";
const container = document.getElementById("container")!;
@@ -208,7 +208,7 @@ stats.dom.style.left = "0px";
world.renderer.onBeforeUpdate.add(() => stats.begin());
world.renderer.onAfterUpdate.add(() => stats.end());
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/fragments/IfcPropertiesTiler/example.ts b/packages/core/src/fragments/IfcPropertiesTiler/example.ts
index 8a7c96264..14527e5b2 100644
--- a/packages/core/src/fragments/IfcPropertiesTiler/example.ts
+++ b/packages/core/src/fragments/IfcPropertiesTiler/example.ts
@@ -162,7 +162,7 @@ async function processFile() {
If everything went as expected, you should now be seeing some files being downloaded from your app ๐คฏ Do not get scary if they're a lot, as big models tend to have many files! All of that is the information the streaming uses in order to display the geometry in the most efficient way possible ๐ช
*/
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/ifc/IfcJsonExporter/example.ts b/packages/core/src/ifc/IfcJsonExporter/example.ts
index b38a06e8e..9048192a8 100644
--- a/packages/core/src/ifc/IfcJsonExporter/example.ts
+++ b/packages/core/src/ifc/IfcJsonExporter/example.ts
@@ -50,7 +50,7 @@ const modelID = webIfc.OpenModel(ifcBuffer);
const exporter = components.get(OBC.IfcJsonExporter);
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/ifc/IfcRelationsIndexer/example.ts b/packages/core/src/ifc/IfcRelationsIndexer/example.ts
index 89deb2cf3..3bcef7485 100644
--- a/packages/core/src/ifc/IfcRelationsIndexer/example.ts
+++ b/packages/core/src/ifc/IfcRelationsIndexer/example.ts
@@ -162,7 +162,7 @@ if (buildingStorey && buildingStorey[0]) {
Congratulations! Now you know how to get an easy way to get the relations of your model. Keep going with more tutorials! ๐ช
*/
-BUI.Manager.registerComponents();
+BUI.Manager.init();
const panel = BUI.Component.create(() => {
return BUI.html`
diff --git a/packages/core/src/measurement/Utils/example.ts b/packages/core/src/measurement/Utils/example.ts
index 06f2965d7..e026dc265 100644
--- a/packages/core/src/measurement/Utils/example.ts
+++ b/packages/core/src/measurement/Utils/example.ts
@@ -37,6 +37,21 @@ const buffer = new Uint8Array(data);
const model = fragments.load(buffer);
world.scene.three.add(model);
+const cullers = components.get(OBC.Cullers);
+const culler = cullers.create(world);
+
+for (const child of model.children) {
+ if (child instanceof THREE.Mesh) {
+ culler.add(child);
+ }
+}
+
+culler.needsUpdate = true;
+
+world.camera.controls.addEventListener("sleep", () => {
+ culler.needsUpdate = true;
+});
+
const measurements = components.get(OBC.MeasurementUtils);
const casters = components.get(OBC.Raycasters);
diff --git a/packages/front/package.json b/packages/front/package.json
index ae9ff5240..bb5ec4fd3 100644
--- a/packages/front/package.json
+++ b/packages/front/package.json
@@ -1,7 +1,7 @@
{
"name": "@thatopen/components-front",
"description": "Collection of frontend tools to author BIM apps.",
- "version": "2.0.0-alpha.15",
+ "version": "2.0.0-alpha.16",
"author": "That Open Company",
"contributors": [
"Antonio Gonzalez Viegas (https://github.com/agviegas)",
@@ -39,15 +39,15 @@
},
"devDependencies": {
"@thatopen/fragments": "2.0.0-alpha.5",
- "@thatopen/ui": "2.0.0-alpha.5",
- "@thatopen/ui-obc": "2.0.0-alpha.5",
+ "@thatopen/ui": "2.0.0-alpha.12",
+ "@thatopen/ui-obc": "2.0.0-alpha.12",
"@types/earcut": "^2.1.4",
"@types/three": "^0.160.0",
"three": "^0.160.1",
"web-ifc": "0.0.53"
},
"dependencies": {
- "@thatopen/components": "2.0.0-alpha.15",
+ "@thatopen/components": "2.0.0-alpha.16",
"camera-controls": "2.7.3",
"dexie": "^4.0.4",
"earcut": "^2.2.4",
diff --git a/packages/front/src/civil/Civil3DNavigator/example.html b/packages/front/src/civil/Civil3DNavigator/example.html
index 6c905cd40..3c3bf6464 100644
--- a/packages/front/src/civil/Civil3DNavigator/example.html
+++ b/packages/front/src/civil/Civil3DNavigator/example.html
@@ -9,7 +9,7 @@
- Civil 3D Navigator
+ Simple 2D Scene
-
-
+
+
+