diff --git a/build/404.html b/build/404.html index 116549211..eab9e4b54 100644 --- a/build/404.html +++ b/build/404.html @@ -4,13 +4,13 @@ Page Not Found | That Open Docs - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/build/Tutorials/Components/Core/BoundingBoxer/index.html b/build/Tutorials/Components/Core/BoundingBoxer/index.html index 3729e6d90..25466645f 100644 --- a/build/Tutorials/Components/Core/BoundingBoxer/index.html +++ b/build/Tutorials/Components/Core/BoundingBoxer/index.html @@ -4,24 +4,15 @@ BoundingBoxer | That Open Docs - +
-
Skip to main content

BoundingBoxer

Source

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

๐Ÿงณ Gathering BIM Dataโ€‹


Fragment help you to render your BIM files faster than ever.๐Ÿš… Fragment is a group of FragmentMeshes -which are clubbed together to visualize the BIM model. -When working with large BIM models, you may need to quit the navigation to see the whole model.๐Ÿ“Œ -To accomplish this, we must extract Mesh data from the Fragment and use control APIs to display the complete Fragment.

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

For this tutorial, we'll use the FragmentBoundingBox component, which will provide us with the mesh by using the Fragment Model.

๐Ÿงฉ Adding Fragmentsโ€‹


We'll start by adding a Fragment to our scene using FragmentManager. -We'll use a simple fragment for the purposes of this tutorial, but the code is capable of handling big files as well.๐Ÿ—๏ธ

// Set up scene (see SimpleScene tutorial)

// @ts-ignore
import * as dat from "three/examples/jsm/libs/lil-gui.module.min";
import Stats from "stats.js";
import * as BUI from "@thatopen/ui";
import * as OBC from "../..";

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

const components = new OBC.Components();

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

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

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

components.init();

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

๐ŸŽฒ Creation of Bounding Boxesโ€‹


Now that our setup is done, lets see how you can use FragmentBoundingBox(). -You will be amazed to see how easy it is to create bounding box using components.๐Ÿ’ช -We will use OBC.FragmentBoundingBox() and add the Fragment model to it using add(model).

const fragments = new OBC.FragmentsManager(components);

const file = await fetch("https://thatopen.github.io/engine_components/resources/small.frag");
const data = await file.arrayBuffer();
const buffer = new Uint8Array(data);
const model = fragments.load(buffer);
world.scene.three.add(model);

๐Ÿ‘“ Reading the Mesh Dataโ€‹

After adding the model, we can now read the mesh from bounding box using getMesh()

const fragmentBbox = components.get(OBC.BoundingBoxer);
fragmentBbox.add(model);

๐ŸŽฎ Managing Zoom Eventsโ€‹


Now that all the setup is done, we need to trigger the zoom event on a button click.๐Ÿ–ฑ -We will use fitToSphere from camera.controls, -which takes the mesh as a parameter and zooms to it. -Also, we will enable a nice transition effect while zooming to the mesh by setting the last parameter as true

const bbox = fragmentBbox.getMesh();
fragmentBbox.reset();

Congratulations ๐ŸŽ‰ on completing this short yet useful tutorial! -You can now easily zoom to Fragment Mesh using FragmentBoundingBox๐Ÿ˜Ž -Let's keep it up and check out another tutorial! ๐ŸŽ“

BUI.Manager.init();

const panel = BUI.Component.create<BUI.PanelSection>(() => {
return BUI.html`
<bim-panel active label="Bounding Boxes Tutorial"
style="position: fixed; top: 5px; right: 5px">
<bim-panel-section style="padding-top: 10px;">

<bim-button
label="Fit BIM model"
@click="${() => {
world.camera.controls.fitToSphere(bbox, true);
}}">
</bim-button>

</bim-panel-section>
</bim-panel>
`;
});

document.body.append(panel);
- +
Skip to main content

BoundingBoxer

Source

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

๐ŸงŠ Playing with boxesโ€‹


In this tutorial, you'll learn to easily create the bounding boxes of a BIM model. This can be useful for knowing the overall position and dimension of your models, which can be used, for instance, to make the camera fit a whole BIM model in the screen.

Bounding boxes?

Bounding boxes (AABB or Axis-Aligned Bounding Boxes) are the boxes aligned with the X, Y and Z axes of a 3D model that contain one or many objects. They are very common in 3D applications to make fast computations that require to know the whole dimension or position of one or many objects.

In this tutorial, we will import:

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

๐ŸŒŽ 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")!;

const components = new OBC.Components();

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

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

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

components.init();

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

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;

๐Ÿงณ Loading a BIM modelโ€‹


We'll start by adding a BIM model to our scene. That model is already converted to fragments, so it will load much faster than if we loaded the IFC file.

Fragments?

If you are not familiar with fragments, check out the IfcLoader tutorial!

const fragments = components.get(OBC.FragmentsManager);
const file = await fetch(
"https://thatopen.github.io/engine_components/resources/small.frag",
);
const data = await file.arrayBuffer();
const buffer = new Uint8Array(data);
const model = fragments.load(buffer);
world.scene.three.add(model);

๐ŸŽฒ Creation of Bounding Boxesโ€‹


Now that our setup is done, lets see how you can create the bounding boxes of the model. +BIM models are complex, but don't worry: creating the bounding boxes is a piece of cake thanks to the BoundingBoxer.๐Ÿ’ช +We can add models to the computation of the bounding box simply by using the add() method.

const fragmentBbox = components.get(OBC.BoundingBoxer);
fragmentBbox.add(model);

๐Ÿ‘“ Reading the Bounding Box dataโ€‹

After adding the model, we can now read the mesh from bounding box using getMesh() method.

Don't forget to clean up after using it! ๐Ÿงน

It's a good practice to reset the bounding box after using it with the reset() method. Otherwise, if you add more models or meshes to the bounding boxer, the bounding box will compute a bounding box that includes everything (including the previously added models).

const bbox = fragmentBbox.getMesh();
fragmentBbox.reset();

โฑ๏ธ 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 an input to make the camera fit the model to the screen. 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 active label="Bounding Boxes Tutorial" class="options-menu">
<bim-panel-section collapsed label="Controls">

<bim-button
label="Fit BIM model"
@click="${() => {
world.camera.controls.fitToSphere(bbox, true);
}}">
</bim-button>

</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 the bounding box of a BIM model and used it to make the camera fit the model to the screen. This also works with many models!

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Core/Clipper/index.html b/build/Tutorials/Components/Core/Clipper/index.html index f11df8235..edfd52c7d 100644 --- a/build/Tutorials/Components/Core/Clipper/index.html +++ b/build/Tutorials/Components/Core/Clipper/index.html @@ -4,14 +4,14 @@ Clipper | That Open Docs - +
Skip to main content

Clipper

Source

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

โœ‚๏ธ Cutting our scene with planesโ€‹


The Clipping Tool is a powerful feature in 3D modelling that helps you dissect 3D objects. Clipping Tool is useful for inspecting and analyzing objects in detail.๐Ÿ’ช

Clipping?

Clipping is the process of "cutting" a 3D object by creating a plane. That way, we can have a bird view of the inside of a BIM model.

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 Stats from "stats.js";
import * as BUI from "@thatopen/ui";
import * as OBC from "@thatopen/components";

๐ŸŒŽ 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")!;

const components = new OBC.Components();

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

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

components.init();

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

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;

๐ŸŽฒ Creating a Cube Meshโ€‹


Let's start by adding a Cube, which we can dissect. We will create a Cube with 3x3x3 dimensions and use purple color for the material.

const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);
world.scene.three.add(cube);
world.meshes.add(cube);

โšก Initializing the Raycasterโ€‹


We also need to initialize the Raycaster for this world so that the position of the mouse is tracked from the very first moment we use the clipping planes.

const casters = components.get(OBC.Raycasters);
casters.get(world);

โš™๏ธ Adding the Clipperโ€‹


Here comes the interesting part, we will add a Simple Clipper to our scene. You can instantiate it, but it's always better to use the components.get(OBC.Clipper) method to get it. All components are meant to be used as a singleton per components instance, and using this system to get a component makes sure this happens.

const clipper = components.get(OBC.Clipper);
Controllign the plane

Each plane generated by the clipper can be controlled using the built-in 3D arrows. You can control the activation and visibility of each plane using plane.enabled and plane.visible. To control the activation and visibility of all planes, use clipper.enabled and clipper.visible.

clipper.enabled = true;

๐Ÿค Performing Clipping Eventsโ€‹


Now, we want a way to create a clipping plane on demand. You can do it with a Single Click or Double Click of a mouse. For this tutorial, we will use Double Click. This will cast a ray from the mouse position to the scene and check if the ray intersects with any of the 3D objects. If it does, it will create a new clipping plane in the point of intersection.

container.ondblclick = () => clipper.create(world);

We use the Raycaster to determine if the intersection has occurred. The clipper places a plane after detecting the face on which the mouse was clicked. ๐Ÿ˜Ž :::

๐Ÿงน Deleting the Clipping Planesโ€‹


Now that we know how to make multiple clipping planes, we must also know how to delete them when necessary. Clipping planes can be removed using clipper.delete(world) (which will pick the first plane under the mouse using the raycaster in the specified world) or clipper.delete(world, plane) (which will delete a specific clipping plane).

window.onkeydown = (event) => {
if (event.code === "Delete" || event.code === "Backspace") {
clipper.delete(world);
}
};
Delete all the Clipping Planes

โŽ If you want to safely delete all the clipping planes that were created you can simply call clipper.deleteAll().

โฑ๏ธ 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 some UI elements and bind them to some of the controls of the clipper, like activation, visibility, size, color, etc. 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="Clipper Tutorial" class="options-menu">
<bim-panel-section collapsed label="Commands">

<bim-label label="Double click: Create clipping plane"></bim-label>
<bim-label label="Delete key: Delete clipping plane"></bim-label>


</bim-panel-section>
<bim-panel-section collapsed label="Others"">

<bim-checkbox label="Clipper enabled" checked
@change="${({ target }: { target: BUI.Checkbox }) => {
clipper.enabled = target.value;
}}">
</bim-checkbox>

<bim-checkbox label="Clipper visible" checked
@change="${({ target }: { target: BUI.Checkbox }) => {
clipper.visible = target.value;
}}">
</bim-checkbox>

<bim-color-input
label="Planes Color" color="#202932"
@input="${({ target }: { target: BUI.ColorInput }) => {
clipper.material.color.set(target.color);
}}">
</bim-color-input>

<bim-number-input
slider step="0.01" label="Planes opacity" value="0.2" min="0.1" max="1"
@change="${({ target }: { target: BUI.NumberInput }) => {
clipper.material.opacity = target.value;
}}">
</bim-number-input>

<bim-number-input
slider step="0.1" label="Planes size" value="5" min="2" max="10"
@change="${({ target }: { target: BUI.NumberInput }) => {
clipper.size = target.value;
}}">
</bim-number-input>

<bim-button
label="Delete all"
@click="${() => {
clipper.deleteAll();
}}">
</bim-button>

<bim-button
label="Rotate cube"
@click="${() => {
cube.rotation.x = 2 * Math.PI * Math.random();
cube.rotation.y = 2 * Math.PI * Math.random();
cube.rotation.z = 2 * Math.PI * Math.random();
}}">
</bim-button>


</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 clipping planes to cut your 3D models. You can now play with the inputs to see how the planes change and adapt them to the look of your app! If you liked planes, don't forget to check out the Edges Planes tutorial, who includes styles, edges and fills and much more.

- + \ No newline at end of file diff --git a/build/Tutorials/Components/Core/Cullers/index.html b/build/Tutorials/Components/Core/Cullers/index.html index 4b0ebe3dd..466c2b048 100644 --- a/build/Tutorials/Components/Core/Cullers/index.html +++ b/build/Tutorials/Components/Core/Cullers/index.html @@ -4,7 +4,7 @@ Cullers | That Open Docs - + @@ -12,7 +12,7 @@
Skip to main content

Cullers

Source

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

๐Ÿš… Managing Performanceโ€‹


There are occasions when your scene has too many objects. Multiple objects being rendered simultaneously lengthens computation timeโŒ›๏ธ and degrades performance.๐ŸŒก๏ธ In this tutorial, we will use ScreenCuller to improve performance by reducing unnecessary computations.๐Ÿš€

What's "culling"?

Culling is a process where we hide some objects of the scene. In this case, we'll hide objects that are not visible, either because they are outside of the scope of the camera, or because there are other objects in front of them, hiding them from the camera. The goal is simple: only compute the objects visible by the camera. This is great in BIM models, because we generally don't want to see ALL the objects 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.
  • Stats.js (optional) to measure the performance of our app.
import * as THREE from "three";
import Stats from "stats.js";
import * as OBC from "../..";

๐ŸŒŽ 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")!;

const components = new OBC.Components();

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

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

components.init();

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

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

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;

๐Ÿงฐ Creating Screen Cullerโ€‹


Although adding Screen Culler to your project can appear difficult, it is actually very easy. We just need to get the Cullers component and create a new instance of ScreenCuller. Remember that although you can instance the Cullers component, it's better to get it from the components object, as all the components are meant to be singletons within a Component instance, and this ensures that.

const cullers = components.get(OBC.Cullers);
const culler = cullers.create(world);

You can use the threshold property to control the minimum size of an element in screen in order for it to be revealed by the culler. Higher numbers result in less objects visible, but more performance:

culler.threshold = 200;

Additionally, we will activate the culler.renderDebugFrame so that we can see the 2D screen of the elements that are not occluded. We will get the domElement and attach it to the body so that we can see this frame in real-time. To see it in your app, just comment out the debugFrame.style.visibility = "collapse"; line.

culler.renderDebugFrame = true;
const debugFrame = culler.renderer.domElement;
document.body.appendChild(debugFrame);
debugFrame.style.position = "fixed";
debugFrame.style.left = "0";
debugFrame.style.bottom = "0";
debugFrame.style.visibility = "collapse";

๐Ÿงฑ Adding a ton of cubesโ€‹

We'll add the Simple 3D Cube and do it 300 times!๐Ÿคฏ We'll generate box geometry and use Lambert material.

const cubes: THREE.Mesh[] = [];
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshLambertMaterial({ color: "#6528D7" });
Randomising the Cubes Placement

We'll write a quick utility function that returns a random number between 0 and the specified upper limit. You can use this for a variety of purposes, but for this tutorial it will be used to generate random positions for cubes that we will add later to our scene.๐Ÿ“Œ

function getRandomNumber(limit: number) {
return Math.random() * limit;
}

Now, using the getRandomNumber() method we previously created, we will add the 300 cube meshes to our scene at random positions. We'll add the cube to the scene and adjust its position between 0 and 10. Additionally, we will add meshes to the culler object, which will help the culler recognize and draw the elements that are visible to the camera, which can be done with the culler's add() method.

function regenerateCubes() {
for (let i = 0; i < 300; i++) {
const cube = new THREE.Mesh(geometry, material);
cube.position.x = getRandomNumber(10);
cube.position.y = getRandomNumber(10);
cube.position.z = getRandomNumber(10);
cube.updateMatrix();
world.scene.three.add(cube);
culler.add(cube);
cubes.push(cube);
}
}

regenerateCubes();

๐Ÿ”„๏ธ Updating the Cullerโ€‹

Here comes the most crucial part! The core aim of ScreenCuller is to output just those components that are visible to the camera.

How often should you update the culler?

It depends on the experience you are looking for. Naturally, the most often you update it, the faster your user will discover new objects that were hidden before, but that also means that your app will be less performant.

In this tutorial we are updating it each time the camera stops moving, which generally works well for most apps.

culler.needsUpdate = true;
world.camera.controls.addEventListener("controlend", () => {
culler.needsUpdate = true;
});

โฑ๏ธ 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());

Great job! ๐ŸŽ‰ Now you know how to optimise your 3D scene using a Screen Culler component! Your BIM app will now have unmatched performance and can render huge scenes easily.

- + \ No newline at end of file diff --git a/build/Tutorials/Components/Core/FragmentsManager/index.html b/build/Tutorials/Components/Core/FragmentsManager/index.html index 4272af19b..d03e9de76 100644 --- a/build/Tutorials/Components/Core/FragmentsManager/index.html +++ b/build/Tutorials/Components/Core/FragmentsManager/index.html @@ -4,7 +4,7 @@ FragmentsManager | That Open Docs - + @@ -26,7 +26,7 @@ After using fragments.dispose(), you should reinitialize Fragment Manager to maintain it's original state for further uses.

function disposeFragments() {
fragments.dispose();
}

Loading a .zip fragment that you have locally is also quite easy:

function importExternalFragment() {
if (fragments.groups.size) {
return;
}
const input = document.createElement("input");
input.type = "file";
input.onchange = async () => {
if (!(input.files && input.files[0])) return;
const file = input.files[0];
if (file.name.includes(".frag")) {
const url = URL.createObjectURL(file);
const result = await fetch(url);
const data = await result.arrayBuffer();
const buffer = new Uint8Array(data);
fragments.load(buffer);
}
input.remove();
};
input.click();
}

const gui = new dat.GUI();

const actions = {
loadFragments: () => loadFragments(),
disposeFragments: () => disposeFragments(),
exportFragments: () => exportFragments(),
importExternalFragment: () => importExternalFragment(),
};

gui.add(actions, "loadFragments").name("Load Fragments");
gui.add(actions, "disposeFragments").name("Dispose Fragments");
gui.add(actions, "exportFragments").name("Export Fragments");
gui.add(actions, "importExternalFragment").name("Import External Fragment");

Congratulations ๐ŸŽ‰ on completing this short yet powerful tutorial! Now you can render or export Fragment files in your BIM Apps using Fragment Manager ๐ŸŽฏ Let's keep it up and check out another tutorial! ๐ŸŽ“

- + \ No newline at end of file diff --git a/build/Tutorials/Components/Core/Grids/index.html b/build/Tutorials/Components/Core/Grids/index.html index ecafbb87f..5c4c56c67 100644 --- a/build/Tutorials/Components/Core/Grids/index.html +++ b/build/Tutorials/Components/Core/Grids/index.html @@ -4,13 +4,13 @@ Grids | That Open Docs - +
Skip to main content

Grids

Source

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

๐Ÿ•ธ๏ธ Adding a fancy grid to our sceneโ€‹


In this tutorial you'll learn how to add a fancy grid to your scene. It's super easy and will make your app look much more professional!

Why a grid?

Grids are very common in 3D apps, and it's a great way to have a reference point for your users to navigate around, even when there are no visible objects around.

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.
import * as THREE from "three";
import * as OBC from "@thatopen/components";

๐ŸŒŽ 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")!;

const components = new OBC.Components();

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

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

components.init();

const cube = new THREE.Mesh(new THREE.BoxGeometry());
world.scene.three.add(cube);

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 the grid to the worldโ€‹


To add the grid to the world, we will use the Grids component. Instead of instantiating it, we will get it directly from the components object. Remember that all components are meant to be singletons. Then, we will call the create method to add a grid to the scene.

const grids = components.get(OBC.Grids);
const grid = grids.create(world);
console.log(grid);

โฑ๏ธ 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โ€‹


Congratulations! You have created your first infinite grid in your 3D app. As you can see, it's super easy and it looks great!

- + \ No newline at end of file diff --git a/build/Tutorials/Components/Core/Hider/index.html b/build/Tutorials/Components/Core/Hider/index.html index 154d51f13..024c654e1 100644 --- a/build/Tutorials/Components/Core/Hider/index.html +++ b/build/Tutorials/Components/Core/Hider/index.html @@ -4,7 +4,7 @@ Hider | That Open Docs - + @@ -17,22 +17,22 @@ like categories. With the FragmentHider, you'll be able to find anything, even things defined in custom categories!

First, let's start by creating a FragmentManager instance and loading a simple fragment. If you haven't checked out the tutorial -for that component yet, do it before going forward!

import Stats from "stats.js";
import * as BUI from "@thatopen/ui";
import * as OBC from "../..";

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

const components = new OBC.Components();

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

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

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

components.init();

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

Now that we have our model, let's start the FragmentHider. You +for that component yet, do it before going forward!

const fragments = new OBC.FragmentsManager(components);
const file = await fetch("https://thatopen.github.io/engine_components/resources/small.frag");
const data = await file.arrayBuffer();
const buffer = new Uint8Array(data);
const model = fragments.load(buffer);
world.scene.three.add(model);

Now that we have our model, let's start the FragmentHider. You can use the loadCached method if you had used it before: it will automatically load all the filters you created in previous sessions, -even after closing the browser and opening it again:

const fragments = new OBC.FragmentsManager(components);
const file = await fetch("https://thatopen.github.io/engine_components/resources/small.frag");
const data = await file.arrayBuffer();
const buffer = new Uint8Array(data);
const model = fragments.load(buffer);
world.scene.three.add(model);

๐Ÿ“•๐Ÿ“—๐Ÿ“˜ Setting up simple filtersโ€‹


Next, we will classify data by category and by level using the +even after closing the browser and opening it again:

const hider = new OBC.FragmentHider(components);

๐Ÿ“•๐Ÿ“—๐Ÿ“˜ Setting up simple filtersโ€‹


Next, we will classify data by category and by level using the FragmentClassifier. This will allow us to create a simple filter for both classifications. Don't worry: we'll get to -the more complex filters later!

const hider = new OBC.FragmentHider(components);

Next, we will create a simple object that we will use as the +the more complex filters later!

const classifier = new OBC.Classifier(components);
classifier.byStorey(model);
classifier.byEntity(model);

Next, we will create a simple object that we will use as the base for the floors filter. It will just be a JS object with the name of each storey as key and a boolean (true/false) as -value:

const classifier = new OBC.Classifier(components);
classifier.byStorey(model);
classifier.byEntity(model);

Now, let's do the same for categories:

const storeys: Record<string, any> = {};
const storeyNames = Object.keys(classifier.list.storeys);
for (const name of storeyNames) {
storeys[name] = true;
}

Finally, we will set up a simple menu to control -the visibility of storeys:

const classes: Record<string, any> = {};
const classNames = Object.keys(classifier.list.entities);
for (const name of classNames) {
classes[name] = true;
}

That's it! That button will open a floating menu that will allow +value:

const storeys: Record<string, any> = {};
const storeyNames = Object.keys(classifier.list.storeys);
for (const name of storeyNames) {
storeys[name] = true;
}

Now, let's do the same for categories:

const classes: Record<string, any> = {};
const classNames = Object.keys(classifier.list.entities);
for (const name of classNames) {
classes[name] = true;
}

Finally, we will set up a simple menu to control +the visibility of storeys:

BUI.Manager.init();

const panel = BUI.Component.create<BUI.PanelSection>(() => {
return BUI.html`
<bim-panel active label="Hider Tutorial"
style="position: fixed; top: 5px; right: 5px">

<bim-panel-section fixed name="Floors" style="padding-top: 10px;">
</bim-panel-section>

<bim-panel-section fixed name="Categories" style="padding-top: 10px;">
</bim-panel-section>

</bim-panel>
`;
});

document.body.append(panel);

const floorSection = panel.querySelector(
"bim-panel-section[name='Floors']",
) as BUI.PanelSection;

const categorySection = panel.querySelector(
"bim-panel-section[name='Categories']",
) as BUI.PanelSection;

for (const name in storeys) {
const panel = BUI.Component.create<BUI.Checkbox>(() => {
return BUI.html`
<bim-checkbox checked label="${name}"
@change="${({ target }: { target: BUI.Checkbox }) => {
const found = classifier.find({ storeys: [name] });
hider.set(target.value, found);
}}">
</bim-checkbox>
`;
});
floorSection.append(panel);
}

for (const name in classes) {
const checkbox = BUI.Component.create<BUI.Checkbox>(() => {
return BUI.html`
<bim-checkbox checked label="${name}"
@change="${({ target }: { target: BUI.Checkbox }) => {
const found = classifier.find({ entities: [name] });
hider.set(target.value, found);
}}">
</bim-checkbox>
`;
});
categorySection.append(checkbox);
}

That's it! That button will open a floating menu that will allow you to create custom multi-filters that work even for custom property sets and quantity sets, including logical operators. Try them out in the example below, and check out more tutorials -to bring your BIM apps to the next level!

BUI.Manager.init();

const panel = BUI.Component.create<BUI.PanelSection>(() => {
return BUI.html`
<bim-panel active label="Hider Tutorial"
style="position: fixed; top: 5px; right: 5px">

<bim-panel-section fixed name="Floors" style="padding-top: 10px;">
</bim-panel-section>

<bim-panel-section fixed name="Categories" style="padding-top: 10px;">
</bim-panel-section>

</bim-panel>
`;
});

document.body.append(panel);

const floorSection = panel.querySelector(
"bim-panel-section[name='Floors']",
) as BUI.PanelSection;

const categorySection = panel.querySelector(
"bim-panel-section[name='Categories']",
) as BUI.PanelSection;

for (const name in storeys) {
const panel = BUI.Component.create<BUI.Checkbox>(() => {
return BUI.html`
<bim-checkbox checked label="${name}"
@change="${({ target }: { target: BUI.Checkbox }) => {
const found = classifier.find({ storeys: [name] });
hider.set(target.value, found);
}}">
</bim-checkbox>
`;
});
floorSection.append(panel);
}

for (const name in classes) {
const checkbox = BUI.Component.create<BUI.Checkbox>(() => {
return BUI.html`
<bim-checkbox checked label="${name}"
@change="${({ target }: { target: BUI.Checkbox }) => {
const found = classifier.find({ entities: [name] });
hider.set(target.value, found);
}}">
</bim-checkbox>
`;
});
categorySection.append(checkbox);
}
- +to bring your BIM apps to the next level!

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Core/IfcGeometryTiler/index.html b/build/Tutorials/Components/Core/IfcGeometryTiler/index.html index deaa1333d..e61aee1e1 100644 --- a/build/Tutorials/Components/Core/IfcGeometryTiler/index.html +++ b/build/Tutorials/Components/Core/IfcGeometryTiler/index.html @@ -4,14 +4,14 @@ IfcGeometryTiler | That Open Docs - +
-
Skip to main content

IfcGeometryTiler

Source

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

๐Ÿ’ช Let's go BIGโ€‹


Do you need to open huge big IFC files fast, even on more modest devices? If so, you are in luck! We can open virtually any model on any device in seconds thanks to BIM TILES!

BIM tiles?

The idea behind BIM tiles is pretty simple! Instead of loading the whole BIM model at once, we just load the explicit geometries that are seen by the user. It's way faster than opening the IFC directly, but for this you'll need a backend (or to rely on the file system of the user if you are building a desktop or mobile app).

Let's see how to do this step by step!

๐Ÿงฉ Converting the IFC model to tilesโ€‹


The first step is to transform the IFC model into BIM tiles. The reason why we have to do this is pretty simple: geometry in IFC is implicit (e.g. a wall is defined as an extrusion). This means that it needs to be computed and converted to explicit geometry (triangles) so that it can be displayed in 3D.

note

As you know, IFC files contains two things: geometries and properties. We need to convert both things if we want to take full advantage of streaming!

The way the streaming works is by fetching files based on the visible things in the viewer. Those files contain pieces of geometry information (geometry chunks) that the engine uses in order to create and display the geometry. But, where do we get those files from? Easy! From the IFC conversion to tiles. So, let's start converting the IFC geometry to tiles and getting those files so the streamer can do its job. In order to do it, we need the first component from the collection of streaming components: FragmentIfcStreamConverter:

import Stats from "stats.js";
// @ts-ignore
import * as dat from "three/examples/jsm/libs/lil-gui.module.min";
import * as BUI from "@thatopen/ui";
import * as OBC from "../..";

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

const components = new OBC.Components();

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

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

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;

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

const grids = components.get(OBC.Grids);
grids.create(world);
// customEffects.excludedMeshes.push(grid.get());

// rendererComponent.postproduction.enabled = true;

The FragmentIfcStreamConverter class takes IFC files and transform their geometry into tiles.

danger

The converter doesn't give you the files needed to streaming right away, just the data that must be contained in those files. Is your job to create the files! Why? Because then you can have full control over when, where and how to create them.

The first file we need is a JSON which is the entry point of the geometries streaming. That JSON must have the following structure:

// We need this wasm configuration later to convert properties
const wasm = {
path: "https://unpkg.com/web-ifc@0.0.53/",
absolute: true,
};

const tiler = new OBC.IfcGeometryTiler(components);
tiler.settings.wasm = wasm;
tiler.settings.minGeometrySize = 20;
tiler.settings.minAssetsSize = 1000;

The second file is actually not just a single file, but X number of files (depends on how big is your model) that contains the required information to generate the geometry while streaming. -In order to create the JSON file and get the information with the geometry, the FragmentIfcStreamConverter as well as other components in the collection of streaming components, emits events that let you get the processed data from the conversion process.

info

Nedless to say, you need to set up your event listeners before triggering the conversion!

Let's start with the first event:

// @ts-ignore
interface GeometriesStreaming {
assets: {
id: number;
geometries: {
color: number[];
geometryID: number;
transformation: number[];
}[];
}[];

geometries: {
[id: number]: {
boundingBox: { [id: number]: number };
hasHoles: boolean;
geometryFile: "url-to-geometry-file-in-your-backend";
};
};

globalDataFileId: "url-to-fragments-group-file-in-your-backend";
}

One of the most important things to keep in mind is that the event we just setup will get fired as many times as per the "chunk" data generated by the converted. Simply put, the event will get fired several times โฒ and per each time we will produce one file data that is stored in the geometryFiles array. Later on, we will download the geometry files โฌ.

note

As you see, geometriesData is not being stored as a file to be downloaded. The reason is because that is part of the information we need to create the entry JSON file ๐Ÿš€.

Nice! Let's go with the second event that will give us more information to create the required files:

let files: { name: string; bits: (Uint8Array | string)[] }[] = [];

let geometriesData: OBC.StreamedGeometries = {};
let geometryFilesCount = 1;

tiler.onGeometryStreamed.add((geometry) => {
const { buffer, data } = geometry;
const bufferFileName = `small.ifc-processed-geometries-${geometryFilesCount}`;
for (const expressID in data) {
const value = data[expressID];
value.geometryFile = bufferFileName;
geometriesData[expressID] = value;
}
files.push({ name: bufferFileName, bits: [buffer] });
geometryFilesCount++;
});

This one is easier as the event doesn't produce binary data, but information we need to create the JSON file.

Are you familiar with That Open Engine?

If you're familiar with That Open Engine, you should recall fragments. Fragments are just a fancy word we use to refer to ThreeJS geometry efficiently created from IFC files which are the things you end up see in the viewer... one IFC file is usually composed of many fragments and all of them are grouped in a FragmentsGroup, which is the final processed IFC model.

Why do we remind you about FragmentsGroup? Because streaming also works with them! So yes, when you convert an IFC to tiles, the converter also creates a FragmentsGroup in the background, and that information is extremely important for the streamer in order to display the streamed file as everything gets grouped there. So, there is another event that gives you the FragmentsGroup binary data and we also need to create a file with that information.

let assetsData: OBC.StreamedAsset[] = [];

tiler.onAssetStreamed.add((assets) => {
assetsData = [...assetsData, ...assets];
});
danger

You can name the file whatever you want, but is extremely important you finish the file name with -global!

This is pretty much it! Now that we've setup the main listeners, the last thing is to download all the data once the conversion has fininshed. To do so, we can use the progress event:

tiler.onIfcLoaded.add((groupBuffer) => {
files.push({
name: "small.ifc-processed-global",
bits: [groupBuffer],
});
});

Great! Now that we have everything setup, is time to finally convert the IFC file. In order to trigger the conversion, we can just do the following:

function downloadFile(name: string, ...bits: (Uint8Array | string)[]) {
const file = new File(bits, name);
const anchor = document.createElement("a");
const url = URL.createObjectURL(file);
anchor.href = url;
anchor.download = file.name;
anchor.click();
URL.revokeObjectURL(url);
}

async function downloadFilesSequentially(
fileList: { name: string; bits: (Uint8Array | string)[] }[],
) {
for (const { name, bits } of fileList) {
downloadFile(name, ...bits);
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
}
}

tiler.onProgress.add((progress) => {
if (progress !== 1) return;
setTimeout(async () => {
const processedData = {
geometries: geometriesData,
assets: assetsData,
globalDataFileId: "small.ifc-processed-global",
};
files.push({
name: "small.ifc-processed.json",
bits: [JSON.stringify(processedData)],
});
await downloadFilesSequentially(files);
assetsData = [];
geometriesData = {};
files = [];
geometryFilesCount = 1;
});
});

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 ๐Ÿ’ช

async function processFile() {
const fetchedIfc = await fetch("https://thatopen.github.io/engine_components/resources/small.ifc");
const ifcBuffer = await fetchedIfc.arrayBuffer();
// We will need this information later to also convert the properties
const ifcArrayBuffer = new Uint8Array(ifcBuffer);
// This triggers the conversion, so the listeners start to be called
await tiler.streamFromBuffer(ifcArrayBuffer);
}
- +
Skip to main content

IfcGeometryTiler

Source

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

๐Ÿ’ช Let's go BIGโ€‹


Do you need to open huge big IFC files fast, even on more modest devices? If so, you are in luck! We can open virtually any model on any device in seconds thanks to BIM TILES!

BIM tiles?

The idea behind BIM tiles is pretty simple! Instead of loading the whole BIM model at once, we just load the explicit geometries that are seen by the user. It's way faster than opening the IFC directly, but for this you'll need a backend (or to rely on the file system of the user if you are building a desktop or mobile app).

Let's see how to do this step by step!

๐Ÿงฉ Converting the IFC model to tilesโ€‹


The first step is to transform the IFC model into BIM tiles. The reason why we have to do this is pretty simple: geometry in IFC is implicit (e.g. a wall is defined as an extrusion). This means that it needs to be computed and converted to explicit geometry (triangles) so that it can be displayed in 3D.

note

As you know, IFC files contains two things: geometries and properties. We need to convert both things if we want to take full advantage of streaming!

The way the streaming works is by fetching files based on the visible things in the viewer. Those files contain pieces of geometry information (geometry chunks) that the engine uses in order to create and display the geometry. But, where do we get those files from? Easy! From the IFC conversion to tiles. So, let's start converting the IFC geometry to tiles and getting those files so the streamer can do its job. In order to do it, we need the first component from the collection of streaming components: FragmentIfcStreamConverter:

// We need this wasm configuration later to convert properties
const wasm = {
path: "https://unpkg.com/web-ifc@0.0.53/",
absolute: true,
};

const tiler = new OBC.IfcGeometryTiler(components);
tiler.settings.wasm = wasm;
tiler.settings.minGeometrySize = 20;
tiler.settings.minAssetsSize = 1000;

The FragmentIfcStreamConverter class takes IFC files and transform their geometry into tiles.

danger

The converter doesn't give you the files needed to streaming right away, just the data that must be contained in those files. Is your job to create the files! Why? Because then you can have full control over when, where and how to create them.

The first file we need is a JSON which is the entry point of the geometries streaming. That JSON must have the following structure:

// @ts-ignore
interface GeometriesStreaming {
assets: {
id: number;
geometries: {
color: number[];
geometryID: number;
transformation: number[];
}[];
}[];

geometries: {
[id: number]: {
boundingBox: { [id: number]: number };
hasHoles: boolean;
geometryFile: "url-to-geometry-file-in-your-backend";
};
};

globalDataFileId: "url-to-fragments-group-file-in-your-backend";
}

The second file is actually not just a single file, but X number of files (depends on how big is your model) that contains the required information to generate the geometry while streaming. +In order to create the JSON file and get the information with the geometry, the FragmentIfcStreamConverter as well as other components in the collection of streaming components, emits events that let you get the processed data from the conversion process.

info

Nedless to say, you need to set up your event listeners before triggering the conversion!

Let's start with the first event:

let files: { name: string; bits: (Uint8Array | string)[] }[] = [];

let geometriesData: OBC.StreamedGeometries = {};
let geometryFilesCount = 1;

tiler.onGeometryStreamed.add((geometry) => {
const { buffer, data } = geometry;
const bufferFileName = `small.ifc-processed-geometries-${geometryFilesCount}`;
for (const expressID in data) {
const value = data[expressID];
value.geometryFile = bufferFileName;
geometriesData[expressID] = value;
}
files.push({ name: bufferFileName, bits: [buffer] });
geometryFilesCount++;
});

One of the most important things to keep in mind is that the event we just setup will get fired as many times as per the "chunk" data generated by the converted. Simply put, the event will get fired several times โฒ and per each time we will produce one file data that is stored in the geometryFiles array. Later on, we will download the geometry files โฌ.

note

As you see, geometriesData is not being stored as a file to be downloaded. The reason is because that is part of the information we need to create the entry JSON file ๐Ÿš€.

Nice! Let's go with the second event that will give us more information to create the required files:

let assetsData: OBC.StreamedAsset[] = [];

tiler.onAssetStreamed.add((assets) => {
assetsData = [...assetsData, ...assets];
});

This one is easier as the event doesn't produce binary data, but information we need to create the JSON file.

Are you familiar with That Open Engine?

If you're familiar with That Open Engine, you should recall fragments. Fragments are just a fancy word we use to refer to ThreeJS geometry efficiently created from IFC files which are the things you end up see in the viewer... one IFC file is usually composed of many fragments and all of them are grouped in a FragmentsGroup, which is the final processed IFC model.

Why do we remind you about FragmentsGroup? Because streaming also works with them! So yes, when you convert an IFC to tiles, the converter also creates a FragmentsGroup in the background, and that information is extremely important for the streamer in order to display the streamed file as everything gets grouped there. So, there is another event that gives you the FragmentsGroup binary data and we also need to create a file with that information.

tiler.onIfcLoaded.add((groupBuffer) => {
files.push({
name: "small.ifc-processed-global",
bits: [groupBuffer],
});
});
danger

You can name the file whatever you want, but is extremely important you finish the file name with -global!

This is pretty much it! Now that we've setup the main listeners, the last thing is to download all the data once the conversion has fininshed. To do so, we can use the progress event:

function downloadFile(name: string, ...bits: (Uint8Array | string)[]) {
const file = new File(bits, name);
const anchor = document.createElement("a");
const url = URL.createObjectURL(file);
anchor.href = url;
anchor.download = file.name;
anchor.click();
URL.revokeObjectURL(url);
}

async function downloadFilesSequentially(
fileList: { name: string; bits: (Uint8Array | string)[] }[],
) {
for (const { name, bits } of fileList) {
downloadFile(name, ...bits);
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
}
}

tiler.onProgress.add((progress) => {
if (progress !== 1) return;
setTimeout(async () => {
const processedData = {
geometries: geometriesData,
assets: assetsData,
globalDataFileId: "small.ifc-processed-global",
};
files.push({
name: "small.ifc-processed.json",
bits: [JSON.stringify(processedData)],
});
await downloadFilesSequentially(files);
assetsData = [];
geometriesData = {};
files = [];
geometryFilesCount = 1;
});
});

Great! Now that we have everything setup, is time to finally convert the IFC file. In order to trigger the conversion, we can just do the following:

async function processFile() {
const fetchedIfc = await fetch("https://thatopen.github.io/engine_components/resources/small.ifc");
const ifcBuffer = await fetchedIfc.arrayBuffer();
// We will need this information later to also convert the properties
const ifcArrayBuffer = new Uint8Array(ifcBuffer);
// This triggers the conversion, so the listeners start to be called
await tiler.streamFromBuffer(ifcArrayBuffer);
}

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 ๐Ÿ’ช

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Core/IfcLoader/index.html b/build/Tutorials/Components/Core/IfcLoader/index.html index 4e69123d3..763be6ae8 100644 --- a/build/Tutorials/Components/Core/IfcLoader/index.html +++ b/build/Tutorials/Components/Core/IfcLoader/index.html @@ -4,7 +4,7 @@ IfcLoader | That Open Docs - + @@ -12,7 +12,7 @@
Skip to main content

IfcLoader

Source

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

๐Ÿ ๐Ÿ‘‰๐Ÿค– From IFC to fragment in 1 minuteโ€‹


Fragments are great: they are lightweight, they are fast and we have tons of tools to work with them. But fragments are not used outside our libraries. So how can we convert an IFC file to fragments? -Easy: with the FragmentIfcLoader! Let's start by creating it.

import * as WEBIFC from "web-ifc";
import * as BUI from "@thatopen/ui";
import Stats from "stats.js";
import * as OBC from "../..";

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

const components = new OBC.Components();

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

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

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

components.init();

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);
Why not just IFC?

IFC is nice because it lets us exchange data with many tools in the +Easy: with the FragmentIfcLoader! Let's start by creating it.

const fragments = components.get(OBC.FragmentsManager);
const fragmentIfcLoader = components.get(OBC.IfcLoader);
Why not just IFC?

IFC is nice because it lets us exchange data with many tools in the AECO industry. But it also has some limitations. In a nutshell, data coming from IFC needs to be processed and converted to triangles to be able to draw it in 3D, and that requires processing power @@ -26,28 +26,28 @@ These files need to be available to our app, so you have 2 options:

  • Download them and serve them statically.
  • Get them from a remote server. The easiest way is getting them from unpkg, and the cool thing is that you don't need to do it manually! It can be done directly by the tool -just by writing the following:
const fragments = components.get(OBC.FragmentsManager);
const fragmentIfcLoader = components.get(OBC.IfcLoader);

Awesome! Optionally, we can exclude categories that we don't want -to convert to fragments like very easily:

await fragmentIfcLoader.setup();

/* If you want to the path to unpkg manually, then you can skip the line
above and set them manually as below:
fragmentIfcLoader.settings.wasm = {
path: "https://unpkg.com/web-ifc@0.0.53/",
absolute: true
}

We can further configure the conversion using the webIfc object. +just by writing the following:

await fragmentIfcLoader.setup();

/* If you want to the path to unpkg manually, then you can skip the line
above and set them manually as below:
fragmentIfcLoader.settings.wasm = {
path: "https://unpkg.com/web-ifc@0.0.53/",
absolute: true
}

Awesome! Optionally, we can exclude categories that we don't want +to convert to fragments like very easily:

const excludedCats = [
WEBIFC.IFCTENDONANCHOR,
WEBIFC.IFCREINFORCINGBAR,
WEBIFC.IFCREINFORCINGELEMENT,
];

for (const cat of excludedCats) {
fragmentIfcLoader.settings.excludedCategories.add(cat);
}

We can further configure the conversion using the webIfc object. In this example, we will make the IFC model go to the origin of the scene (don't worry, this supports model federation) and optimize the profiles geometry so that it generates very -efficient geometry for certain geometries (e.g. HVAC):

const excludedCats = [
WEBIFC.IFCTENDONANCHOR,
WEBIFC.IFCREINFORCINGBAR,
WEBIFC.IFCREINFORCINGELEMENT,
];

for (const cat of excludedCats) {
fragmentIfcLoader.settings.excludedCategories.add(cat);
}

๐Ÿš—๐Ÿ”ฅ Loading the IFCโ€‹


Next, let's define a function to load the IFC programmatically. +efficient geometry for certain geometries (e.g. HVAC):

fragmentIfcLoader.settings.webIfc.COORDINATE_TO_ORIGIN = true;
fragmentIfcLoader.settings.webIfc.OPTIMIZE_PROFILES = true;

๐Ÿš—๐Ÿ”ฅ Loading the IFCโ€‹


Next, let's define a function to load the IFC programmatically. We have hardcoded the path to one of our IFC files, but feel free to do this with any of your own files!

Opening local IFCs

Keep in mind that the browser can't access the file of your computer directly, so you will need to use the Open File API to -open local files.

fragmentIfcLoader.settings.webIfc.COORDINATE_TO_ORIGIN = true;
fragmentIfcLoader.settings.webIfc.OPTIMIZE_PROFILES = true;

๐ŸŽ Exporting the resultโ€‹


Once you have your precious fragments, you might want to save them +open local files.

// const cullers = components.get(OBC.Cullers);
// const culler = cullers.create(world);
// culler.enabled = true;

async function loadIfc() {
const file = await fetch("https://thatopen.github.io/engine_components/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);
// for (const mesh of model.children) {
// culler.add(mesh as any);
// }
}

// world.camera.controls.addEventListener("sleep", () => {
// culler.needsUpdate = true
// })

๐ŸŽ Exporting the resultโ€‹


Once you have your precious fragments, you might want to save them so that you don't need to open this IFC file each time your user gets into your app. Instead, the next time you can load the fragments directly. Defining a function to export fragments -is as easy as this:

// const cullers = components.get(OBC.Cullers);
// const culler = cullers.create(world);
// culler.enabled = true;

async function loadIfc() {
const file = await fetch("https://thatopen.github.io/engine_components/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);
// for (const mesh of model.children) {
// culler.add(mesh as any);
// }
}

// world.camera.controls.addEventListener("sleep", () => {
// culler.needsUpdate = true
// })

๐Ÿง ๐Ÿงผ Cleaning memoryโ€‹


Now, just like in the FragmentManager tutorial, you will need +is as easy as this:

function download(file: File) {
const link = document.createElement("a");
link.href = URL.createObjectURL(file);
link.download = file.name;
document.body.appendChild(link);
link.click();
link.remove();
}

async function exportFragments() {
if (!fragments.groups.size) {
return;
}
const group = Array.from(fragments.groups.values())[0];
const data = fragments.export(group);
download(new File([new Blob([data])], "small.frag"));

const properties = group.getLocalProperties();
if (properties) {
download(new File([JSON.stringify(properties)], "small.json"));
}
}

๐Ÿง ๐Ÿงผ Cleaning memoryโ€‹


Now, just like in the FragmentManager tutorial, you will need to dispose the memory if your user wants to reset the state of the scene, especially if you are using Single Page Application technologies like React, Angular, Vue, etc. To do that, you -can simply call the dispose method:

function download(file: File) {
const link = document.createElement("a");
link.href = URL.createObjectURL(file);
link.download = file.name;
document.body.appendChild(link);
link.click();
link.remove();
}

async function exportFragments() {
if (!fragments.groups.size) {
return;
}
const group = Array.from(fragments.groups.values())[0];
const data = fragments.export(group);
download(new File([new Blob([data])], "small.frag"));

const properties = group.getLocalProperties();
if (properties) {
download(new File([JSON.stringify(properties)], "small.json"));
}
}

That's it! Congrats, now you can load IFC files into your app, +can simply call the dispose method:

function disposeFragments() {
fragments.dispose();
}

That's it! Congrats, now you can load IFC files into your app, generate the 3D geometry and property data for them and navigate them in 3D. In other tutorials, you'll find tons of tools to -work with them and create amazing BIM apps! See you there ๐Ÿ’ช

function disposeFragments() {
fragments.dispose();
}
- +work with them and create amazing BIM apps! See you there ๐Ÿ’ช

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Core/IfcPropertiesTiler/index.html b/build/Tutorials/Components/Core/IfcPropertiesTiler/index.html index 92d79cbbf..6862bd501 100644 --- a/build/Tutorials/Components/Core/IfcPropertiesTiler/index.html +++ b/build/Tutorials/Components/Core/IfcPropertiesTiler/index.html @@ -4,7 +4,7 @@ IfcPropertiesTiler | That Open Docs - + @@ -13,11 +13,11 @@ millions of properties, and trying to save them naively in a normal DB is not very scalable/affordable. Using this system, you'll be able to store and retrieve the data of models of any size without big cloud costs. We can do this conversion -using the FragmentPropsStreamConverter:

import Stats from "stats.js";
// @ts-ignore
import * as dat from "three/examples/jsm/libs/lil-gui.module.min";
import * as BUI from "@thatopen/ui";
import * as OBC from "../..";

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

const components = new OBC.Components();

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

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

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;

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

const grids = components.get(OBC.Grids);
grids.create(world);
// customEffects.excludedMeshes.push(grid.get());

function downloadFile(name: string, bits: Blob) {
const file = new File([bits], name);
const anchor = document.createElement("a");
const url = URL.createObjectURL(file);
anchor.href = url;
anchor.download = file.name;
anchor.click();
URL.revokeObjectURL(url);
}

async function downloadFilesSequentially(
fileList: { name: string; bits: Blob }[],
) {
for (const { name, bits } of fileList) {
downloadFile(name, bits);
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
}
}

// rendererComponent.postproduction.enabled = true;

We need to generate properties JSON with the following structure

const propsStreamer = new OBC.IfcPropertiesTiler(components);

propsStreamer.settings.wasm = {
path: "https://unpkg.com/web-ifc@0.0.53/",
absolute: true,
};

Similarly to geometries, here you will also get data and progress notification +using the FragmentPropsStreamConverter:

const propsStreamer = new OBC.IfcPropertiesTiler(components);

propsStreamer.settings.wasm = {
path: "https://unpkg.com/web-ifc@0.0.53/",
absolute: true,
};

We need to generate properties JSON with the following structure

// @ts-ignore
interface StreamedProperties {
types: {
[typeID: number]: number[];
};

ids: {
[id: number]: number;
};

indexesFile: string;
}

const jsonFile: StreamedProperties = {
types: {},
ids: {},
indexesFile: "small.ifc-processed-properties-indexes",
};

Similarly to geometries, here you will also get data and progress notification using events. In addition to properties, you will get indices, which is an indexation data of the properties to be able to use them effectively when -streamed.

// @ts-ignore
interface StreamedProperties {
types: {
[typeID: number]: number[];
};

ids: {
[id: number]: number;
};

indexesFile: string;
}

const jsonFile: StreamedProperties = {
types: {},
ids: {},
indexesFile: "small.ifc-processed-properties-indexes",
};

Great! Now that we have everything setup, is time to finally convert the IFC file. In order to trigger the conversion, we can just do the following:

let counter = 0;

const files: { name: string; bits: Blob }[] = [];

propsStreamer.onPropertiesStreamed.add(async (props) => {
if (!jsonFile.types[props.type]) {
jsonFile.types[props.type] = [];
}
jsonFile.types[props.type].push(counter);

for (const id in props.data) {
jsonFile.ids[id] = counter;
}

const name = `small.ifc-processed-properties-${counter}`;
const bits = new Blob([JSON.stringify(props.data)]);
files.push({ bits, name });

counter++;
});

propsStreamer.onProgress.add(async (progress) => {
console.log(progress);
});

propsStreamer.onIndicesStreamed.add(async (props) => {
files.push({
name: `small.ifc-processed-properties.json`,
bits: new Blob([JSON.stringify(jsonFile)]),
});

const relations = components.get(OBC.IfcRelationsIndexer);
const serializedRels = relations.serializeRelations(props);

files.push({
name: "small.ifc-processed-properties-indexes",
bits: new Blob([serializedRels]),
});

await downloadFilesSequentially(files);
});

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 ๐Ÿ’ช

async function processFile() {
const fetchedIfc = await fetch("https://thatopen.github.io/engine_components/resources/small.ifc");
const ifcBuffer = await fetchedIfc.arrayBuffer();
// We will need this information later to also convert the properties
const ifcArrayBuffer = new Uint8Array(ifcBuffer);
// This triggers the conversion, so the listeners start to be called
await propsStreamer.streamFromBuffer(ifcArrayBuffer);
}
- +streamed.

let counter = 0;

const files: { name: string; bits: Blob }[] = [];

propsStreamer.onPropertiesStreamed.add(async (props) => {
if (!jsonFile.types[props.type]) {
jsonFile.types[props.type] = [];
}
jsonFile.types[props.type].push(counter);

for (const id in props.data) {
jsonFile.ids[id] = counter;
}

const name = `small.ifc-processed-properties-${counter}`;
const bits = new Blob([JSON.stringify(props.data)]);
files.push({ bits, name });

counter++;
});

propsStreamer.onProgress.add(async (progress) => {
console.log(progress);
});

propsStreamer.onIndicesStreamed.add(async (props) => {
files.push({
name: `small.ifc-processed-properties.json`,
bits: new Blob([JSON.stringify(jsonFile)]),
});

const relations = components.get(OBC.IfcRelationsIndexer);
const serializedRels = relations.serializeRelations(props);

files.push({
name: "small.ifc-processed-properties-indexes",
bits: new Blob([serializedRels]),
});

await downloadFilesSequentially(files);
});

Great! Now that we have everything setup, is time to finally convert the IFC file. In order to trigger the conversion, we can just do the following:

async function processFile() {
const fetchedIfc = await fetch("https://thatopen.github.io/engine_components/resources/small.ifc");
const ifcBuffer = await fetchedIfc.arrayBuffer();
// We will need this information later to also convert the properties
const ifcArrayBuffer = new Uint8Array(ifcBuffer);
// This triggers the conversion, so the listeners start to be called
await propsStreamer.streamFromBuffer(ifcArrayBuffer);
}

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 ๐Ÿ’ช

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Core/IfcRelationsIndexer/index.html b/build/Tutorials/Components/Core/IfcRelationsIndexer/index.html index eaf50b7c6..a31d18792 100644 --- a/build/Tutorials/Components/Core/IfcRelationsIndexer/index.html +++ b/build/Tutorials/Components/Core/IfcRelationsIndexer/index.html @@ -4,14 +4,14 @@ IfcRelationsIndexer | That Open Docs - +
Skip to main content

IfcRelationsIndexer

Source

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

Getting entity relations the easy way ๐Ÿ’ชโ€‹


If you're aware of the IFC schema, you should know that all the possible information an entity have is not directly inside its attributes. For example, the property sets, classifications, materials, etc, of a wall (or any other element) are not directly in the wall attributes ๐Ÿคฏ but in other entities which are related to the wall using relations. -Now, that is perfect for an schema like the IFC which aims to store all the building data within a single text file in the easiest way possible. However, is not that easy to work just because you need to find the relations you want to get to the element data you're looking for ๐Ÿ˜ช. Luckily for you, the IfcRelationsIndexer already gives you an easy way to get the entities which are related with your elements thanks to the inverse attributes! ๐Ÿ”ฅ๐Ÿ”ฅ

Loading a model ๐Ÿฆโ€‹

First things first, lets load an IFC model to process its relations.

tip

If you're unsure on the details to load a model, just tool at the IfcLoader tutorial!

// @ts-ignore
import * as dat from "three/examples/jsm/libs/lil-gui.module.min";
// import Stats from "stats.js";
// import * as BUI from "@thatopen/ui";
import * as BUI from "@thatopen/ui";
import * as OBC from "../..";

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

const components = new OBC.Components();

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

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

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

components.init();

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

Once the model is loaded in memory, you just need to get an instance of the IfcRelationsIndexer and process the model... it's as easy as that! ๐Ÿ˜Ž

const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const file = await fetch("/resources/small.ifc");
const buffer = await file.arrayBuffer();
const typedArray = new Uint8Array(buffer);
const model = await ifcLoader.load(typedArray);
world.scene.three.add(model);

The result of that is basically a map where the keys are the expressIDs and the values are other expressIDs related to the first one and grouped by the type of relation. You don't need to worry too much about the details of that, as the usage is pretty straighforward ๐Ÿ”. The only thing that matters is you've now an easy way to access the entities related to your element ๐Ÿ™‚

Getting element psets ๐Ÿ“„โ€‹

One of the most important relations between different entities is the IfcRelDefinesByProperties. That relation links together an arbitrary entity with a set of IfcPropertySet entities that applies properties. Getting them with the IfcRelationsIndexer once the model is indexed is pretty easy:

const indexer = components.get(OBC.IfcRelationsIndexer);
await indexer.process(model);
tip

IsDefinedBy is the inverse attribute name in the IFC Schema that holds the relations with property sets ๐Ÿ˜‰

Awesome! really easy right?

Exporting the indexationโ€‹

In bigger models, the process to calculate the relations index may take some time. The important thing is that there is no reason to calculate over and over the relations index every time you load a model. If the model hasn't change, their properties shouldn't neither! So, let's download the relations index to load it later.

const psets = indexer.getEntityRelations(model, 6518, "IsDefinedBy");
if (psets) {
for (const expressID of psets) {
// You can get the pset attributes like this
const pset = await model.getProperties(expressID);
console.log(pset);
// You can get the pset props like this or iterate over pset.HasProperties yourself
await OBC.IfcPropertiesUtils.getPsetProps(
model,
expressID,
async (propExpressID) => {
const prop = await model.getProperties(propExpressID);
console.log(prop);
},
);
}
}
tip

As @thatopen/components can be used in either NodeJS and Browser environments, the logic to generate a JSON file may vary!

Now, in case you've loaded several models and want to get all the computed relations, there is also a handy method to do it.

const downloadJSON = (json: string, name: string) => {
const file = new File([json], name);
const a = document.createElement("a");
a.href = URL.createObjectURL(file);
a.download = file.name;
a.click();
URL.revokeObjectURL(a.href);
};

const json = indexer.serializeModelRelations(model);
console.log(json);

Loading back the relations indexโ€‹

What do we gain with having a pre-processed relations index if we can't use it again, right? Well, let's use the downloaded relations index ๐Ÿ˜Ž

const allRelationsJSON = indexer.serializeAllRelations();

Great! Now try to get again the property sets and you will see everything working nice and neat. In fact, lets try to get the building storey of one element in the IFC ๐Ÿ‘‡

// Lets first delete the existing model relations
delete indexer.relationMaps[model.uuid];
const relationsIndexFile = await fetch("/resources/small-relations.json");
const relationsIndex = indexer.getRelationsMapFromJSON(
await relationsIndexFile.text(),
);
indexer.setRelationMap(model, relationsIndex);
tip

Despite there are some relations that corresponds to only one element (e.g., an element can only have one associated building storey) the getEntityRelations will always return an array. That's the reason we take the first buildingStorey relation despite it will always be the only one.

Congratulations! Now you know how to get an easy way to get the relations of your model. Keep going with more tutorials! ๐Ÿ’ช

const buildingStorey = indexer.getEntityRelations(
model,
6518,
"ContainedInStructure",
);

if (buildingStorey && buildingStorey[0]) {
const storey = await model.getProperties(buildingStorey[0]);
console.log(storey);
}
- +Now, that is perfect for an schema like the IFC which aims to store all the building data within a single text file in the easiest way possible. However, is not that easy to work just because you need to find the relations you want to get to the element data you're looking for ๐Ÿ˜ช. Luckily for you, the IfcRelationsIndexer already gives you an easy way to get the entities which are related with your elements thanks to the inverse attributes! ๐Ÿ”ฅ๐Ÿ”ฅ

Loading a model ๐Ÿฆโ€‹

First things first, lets load an IFC model to process its relations.

tip

If you're unsure on the details to load a model, just tool at the IfcLoader tutorial!

const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const file = await fetch("/resources/small.ifc");
const buffer = await file.arrayBuffer();
const typedArray = new Uint8Array(buffer);
const model = await ifcLoader.load(typedArray);
world.scene.three.add(model);

Once the model is loaded in memory, you just need to get an instance of the IfcRelationsIndexer and process the model... it's as easy as that! ๐Ÿ˜Ž

const indexer = components.get(OBC.IfcRelationsIndexer);
await indexer.process(model);

The result of that is basically a map where the keys are the expressIDs and the values are other expressIDs related to the first one and grouped by the type of relation. You don't need to worry too much about the details of that, as the usage is pretty straighforward ๐Ÿ”. The only thing that matters is you've now an easy way to access the entities related to your element ๐Ÿ™‚

Getting element psets ๐Ÿ“„โ€‹

One of the most important relations between different entities is the IfcRelDefinesByProperties. That relation links together an arbitrary entity with a set of IfcPropertySet entities that applies properties. Getting them with the IfcRelationsIndexer once the model is indexed is pretty easy:

const psets = indexer.getEntityRelations(model, 6518, "IsDefinedBy");
if (psets) {
for (const expressID of psets) {
// You can get the pset attributes like this
const pset = await model.getProperties(expressID);
console.log(pset);
// You can get the pset props like this or iterate over pset.HasProperties yourself
await OBC.IfcPropertiesUtils.getPsetProps(
model,
expressID,
async (propExpressID) => {
const prop = await model.getProperties(propExpressID);
console.log(prop);
},
);
}
}
tip

IsDefinedBy is the inverse attribute name in the IFC Schema that holds the relations with property sets ๐Ÿ˜‰

Awesome! really easy right?

Exporting the indexationโ€‹

In bigger models, the process to calculate the relations index may take some time. The important thing is that there is no reason to calculate over and over the relations index every time you load a model. If the model hasn't change, their properties shouldn't neither! So, let's download the relations index to load it later.

const downloadJSON = (json: string, name: string) => {
const file = new File([json], name);
const a = document.createElement("a");
a.href = URL.createObjectURL(file);
a.download = file.name;
a.click();
URL.revokeObjectURL(a.href);
};

const json = indexer.serializeModelRelations(model);
console.log(json);
tip

As @thatopen/components can be used in either NodeJS and Browser environments, the logic to generate a JSON file may vary!

Now, in case you've loaded several models and want to get all the computed relations, there is also a handy method to do it.

const allRelationsJSON = indexer.serializeAllRelations();

Loading back the relations indexโ€‹

What do we gain with having a pre-processed relations index if we can't use it again, right? Well, let's use the downloaded relations index ๐Ÿ˜Ž

// Lets first delete the existing model relations
delete indexer.relationMaps[model.uuid];
const relationsIndexFile = await fetch("/resources/small-relations.json");
const relationsIndex = indexer.getRelationsMapFromJSON(
await relationsIndexFile.text(),
);
indexer.setRelationMap(model, relationsIndex);

Great! Now try to get again the property sets and you will see everything working nice and neat. In fact, lets try to get the building storey of one element in the IFC ๐Ÿ‘‡

const buildingStorey = indexer.getEntityRelations(
model,
6518,
"ContainedInStructure",
);

if (buildingStorey && buildingStorey[0]) {
const storey = await model.getProperties(buildingStorey[0]);
console.log(storey);
}
tip

Despite there are some relations that corresponds to only one element (e.g., an element can only have one associated building storey) the getEntityRelations will always return an array. That's the reason we take the first buildingStorey relation despite it will always be the only one.

Congratulations! Now you know how to get an easy way to get the relations of your model. Keep going with more tutorials! ๐Ÿ’ช

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Core/MiniMap/index.html b/build/Tutorials/Components/Core/MiniMap/index.html index ba57db954..42afbc630 100644 --- a/build/Tutorials/Components/Core/MiniMap/index.html +++ b/build/Tutorials/Components/Core/MiniMap/index.html @@ -4,14 +4,14 @@ MiniMap | That Open Docs - +
Skip to main content

MiniMap

Source

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

๐Ÿ—บ๏ธ Orientating your user in the sceneโ€‹


In this tutorial you'll learn how to use the Minimap, which is a small 2D representation of the 3D world.

Do you mean a floorplans?

Not quite. The minimap is a simple 2D representation of the 3D world. It is useful to help your user understand where they are, and to have a simple top view of their surrounding.

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 Stats from "stats.js";
import * as BUI from "@thatopen/ui";
import * as THREE from "three";
import * as OBC from "@thatopen/components";

๐ŸŒŽ 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")!;

const components = new OBC.Components();

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

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

world.scene.setup();

components.init();

const grids = components.get(OBC.Grids);
grids.create(world);

world.camera.controls.setLookAt(1, 2, -2, -2, 0, -5);

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;

๐Ÿ  Loading a modelโ€‹


Now that we have a scene, let's load a model. We will use the Fragment Manager for it.

Showing Fragments in the Scene

๐Ÿ”๏ธ There is a dedicated tutorial on how to use Fragment Manager to load IFC files, check it out if you haven't already!

const fragments = new OBC.FragmentsManager(components);

const file = await fetch(
"https://thatopen.github.io/engine_components/resources/small.frag",
);
const dataBlob = await file.arrayBuffer();
const buffer = new Uint8Array(dataBlob);
const model = fragments.load(buffer);
world.scene.three.add(model);

๐Ÿ—บ Setting up the mapโ€‹


Now, that we have our setup ready. Let's start with the exciting stuff. We will use MiniMap component, which does all the work for us.๐Ÿ”ฎ

const maps = new OBC.MiniMaps(components);
const map = maps.create(world);

We have already created a simple DIV element with id minimap in our HTML file. We need to fetch it to add the canvas where our minimap is rendered to it. We'll also add a rounded border to make it look better.

const mapContainer = document.getElementById("minimap") as HTMLDivElement;
const canvas = map.renderer.domElement;
canvas.style.borderRadius = "12px";
mapContainer.append(canvas);
map.resize();

โฑ๏ธ 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();

we'll also need a reference to the size of the minimap to control it:

const mapSize = map.getSize();

Now we will create a new panel with some inputs to control the different parameters of the MiniMap, like zoom, size and front offset. 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="Minimap Tutorial" class="options-menu">
<bim-panel-section collapsed label="Controls">

<bim-checkbox checked="true" label="Enabled"
@change="${({ target }: { target: BUI.Checkbox }) => {
map.enabled = target.value;
}}">
</bim-checkbox>

<bim-checkbox checked label="Lock rotation"
@change="${({ target }: { target: BUI.Checkbox }) => {
map.lockRotation = target.value;
}}">
</bim-checkbox>

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


<bim-number-input
slider label="Zoom" value="${map.zoom}" min="0.01" max="0.5" step="0.01"
@change="${({ target }: { target: BUI.NumberInput }) => {
map.zoom = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Front offset" value="${map.frontOffset}" min="0" max="5" step="1"
@change="${({ target }: { target: BUI.NumberInput }) => {
map.frontOffset = target.value;
}}">
</bim-number-input>

<div style="display: flex; gap: 12px">

<bim-number-input slider value="${mapSize.x}" pref="Size X" min="100" max="500" step="10"
@change="${({ target }: { target: BUI.NumberInput }) => {
const size = map.getSize();
size.x = target.value;
map.resize(size);
}}">
</bim-number-input>

<bim-number-input slider value="${mapSize.y}" pref="Size Y" min="100" max="500" step="10"
@change="${({ target }: { target: BUI.NumberInput }) => {
const size = map.getSize();
size.y = target.value;
map.resize(size);
}}">
</bim-number-input>
</div>


</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 a simple app that loads a BIM model and displays a MiniMap of it. You can play around with the different parameters of the MiniMap and see how it changes in real time.

- + \ No newline at end of file diff --git a/build/Tutorials/Components/Core/OrthoPerspectiveCamera/index.html b/build/Tutorials/Components/Core/OrthoPerspectiveCamera/index.html index 37102a96b..258b27d3d 100644 --- a/build/Tutorials/Components/Core/OrthoPerspectiveCamera/index.html +++ b/build/Tutorials/Components/Core/OrthoPerspectiveCamera/index.html @@ -4,13 +4,13 @@ OrthoPerspectiveCamera | That Open Docs - +
Skip to main content

OrthoPerspectiveCamera

Source

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

๐Ÿ“น How to handle a fancy cameraโ€‹


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.

Orthographic and Perspective cameras

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.

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 Stats from "stats.js";
import * as THREE from "three";
import * as BUI from "@thatopen/ui";
import * as OBC from "@thatopen/components";

๐ŸŒŽ Setting up the world AND the cameraโ€‹


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<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBC.SimpleRenderer
>();

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

world.scene.setup();

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

components.init();

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;

Easy, right? Believe it or not, this is all you need to use the OrthoPerspectiveCamera. Now, let's see it in action!

๐ŸงŠ Creating a cubeโ€‹


We will start by creating a simple cube and a grid that will serve as a reference point for our camera.

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);

๐ŸŽŸ๏ธ 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;
grid.fade = projection === "Perspective";
});

โฑ๏ธ 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());

๐Ÿงฉ Building a camera UIโ€‹


Now we will use @thatopen/ui to create a simple UI for the OrthoPerspectiveCamera. It will have 4 elements:

๐ŸŽ›๏ธ 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).

๐Ÿ“ Projectionsโ€‹

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!

โŒ 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.

๐Ÿ”Ž 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.init();

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

<bim-dropdown required label="Navigation mode"
@change="${({ target }: { target: BUI.Dropdown }) => {
const selected = target.value[0] as OBC.NavModeID;

const { current } = world.camera.projection;
const isOrtho = current === "Orthographic";
const isFirstPerson = selected === "FirstPerson";
if (isOrtho && isFirstPerson) {
alert("First person is not compatible with ortho!");
target.value[0] = world.camera.mode.id;
return;
}
world.camera.set(selected);
}}">

<bim-option checked label="Orbit"></bim-option>
<bim-option label="FirstPerson"></bim-option>
<bim-option label="Plan"></bim-option>
</bim-dropdown>


<bim-dropdown required label="Camera projection"
@change="${({ target }: { target: BUI.Dropdown }) => {
const selected = target.value[0] as OBC.CameraProjection;
const isOrtho = selected === "Orthographic";
const isFirstPerson = world.camera.mode.id === "FirstPerson";
if (isOrtho && isFirstPerson) {
alert("First person is not compatible with ortho!");
target.value[0] = world.camera.projection.current;
return;
}
world.camera.projection.set(selected);
}}">
<bim-option checked label="Perspective"></bim-option>
<bim-option label="Orthographic"></bim-option>
</bim-dropdown>

<bim-checkbox
label="Allow user input" checked
@change="${({ target }: { target: BUI.Checkbox }) => {
world.camera.setUserInput(target.checked);
}}">
</bim-checkbox>

<bim-button
label="Fit cube"
@click="${() => {
world.camera.fit([cube]);
}}">
</bim-button>

</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! 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/build/Tutorials/Components/Core/Raycasters/index.html b/build/Tutorials/Components/Core/Raycasters/index.html index b39cc72f6..1bdd32177 100644 --- a/build/Tutorials/Components/Core/Raycasters/index.html +++ b/build/Tutorials/Components/Core/Raycasters/index.html @@ -4,13 +4,13 @@ Raycasters | That Open Docs - +
Skip to main content

Raycasters

Source

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

๐Ÿ 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.

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 Stats from "stats.js";
import * as OBC from "@thatopen/components";

๐ŸŒŽ 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")!;

const components = new OBC.Components();

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

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

components.init();

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

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 some cubes to the 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.

const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const greenMaterial = new THREE.MeshStandardMaterial({ color: "#BCF124" });

const boxGeometry = new THREE.BoxGeometry(3, 3, 3);

const cube1 = new THREE.Mesh(boxGeometry, cubeMaterial);
const cube2 = new THREE.Mesh(boxGeometry, cubeMaterial);
const cube3 = new THREE.Mesh(boxGeometry, cubeMaterial);
world.scene.three.add(cube1, cube2, cube3);
const cubes = [cube1, cube2, cube3];

cube2.position.x = 5;
cube3.position.x = -5;

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;

function rotateCubes() {
cube1.rotation.x += oneDegree;
cube1.rotation.y += oneDegree;
cube2.rotation.x += oneDegree;
cube2.rotation.z += oneDegree;
cube3.rotation.y += oneDegree;
cube3.rotation.z += oneDegree;
}

world.renderer.onBeforeUpdate.add(rotateCubes);

โšก Setting up the raycasterโ€‹


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.

const casters = components.get(OBC.Raycasters);

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 caster = casters.get(world);

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 = () => {
const result = caster.castRay(cubes);
if (previousSelection) {
previousSelection.material = cubeMaterial;
}
if (!result || !(result.object instanceof THREE.Mesh)) {
return;
}
result.object.material = greenMaterial;
previousSelection = result.object;
};

โฑ๏ธ 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! 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/build/Tutorials/Components/Core/Worlds/index.html b/build/Tutorials/Components/Core/Worlds/index.html index 0f83cf956..c1827533e 100644 --- a/build/Tutorials/Components/Core/Worlds/index.html +++ b/build/Tutorials/Components/Core/Worlds/index.html @@ -4,13 +4,13 @@ Worlds | That Open Docs - +
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 * as OBC from "@thatopen/components";
import Stats from "stats.js";

๐Ÿ–ผ๏ธ 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'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);

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

world.scene.setup();

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.three.background = 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 }) => {
for (const child of world.scene.three.children) {
if (child instanceof THREE.DirectionalLight) {
child.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 }) => {
for (const child of world.scene.three.children) {
if (child instanceof THREE.AmbientLight) {
child.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.

- + \ No newline at end of file diff --git a/build/Tutorials/Components/Front/AngleMeasurement/index.html b/build/Tutorials/Components/Front/AngleMeasurement/index.html index 72d55717c..c6a218b3c 100644 --- a/build/Tutorials/Components/Front/AngleMeasurement/index.html +++ b/build/Tutorials/Components/Front/AngleMeasurement/index.html @@ -4,7 +4,7 @@ AngleMeasurement | That Open Docs - + @@ -14,23 +14,23 @@ Dimension Tool allows you to perform measurements effortlessly.

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

This tutorial will show you how to add Dimension Tool to your projects, which can be used to acquire accurate dimensions for any 3D Object.๐Ÿ”ญ

๐ŸŽฒ Creating a Cube Meshโ€‹


For this tutorial we will use a Cube, you can add any geometry as per your preference. We will create a Cube -with 3x3x3 dimensions and use red color for the material.

import Stats from "stats.js";
import * as OBC from "@thatopen/components";
import * as THREE from "three";
import * as OBCF from "../..";

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

const components = new OBC.Components();

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

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

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

components.init();

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

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, -which is simply an array of all the meshes in the Scene.๐Ÿ—„๏ธ

const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);
Collection of Meshes

๐Ÿ“ฆ Components.meshes keeps all your meshes including IFC Models, Fragments in +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" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, +which is simply an array of all the meshes in the Scene.๐Ÿ—„๏ธ

world.scene.three.add(cube);
world.meshes.add(cube);
Collection of Meshes

๐Ÿ“ฆ Components.meshes keeps all your meshes including IFC Models, Fragments in one place.

๐Ÿ› ๏ธ Creating Dimension Toolโ€‹


A lot of logic is usually needed to compute dimensions for any item, beginning with ray casting, finding the vertices to snap to, and rendering the UI for every line element.๐Ÿ™„ This may appear to be a lot of effort, but we are handling all the heavy lifting for you, -and you only need to write a few lines for creating the Dimension Tool.๐Ÿ’ช

world.scene.three.add(cube);
world.meshes.add(cube);

We will build dimensions by supplying the components to OBC.SimpleDimensions.

DIMENSIONS AND UI

Read the Simple Dimensions API for more on this. +and you only need to write a few lines for creating the Dimension Tool.๐Ÿ’ช

const angles = components.get(OBCF.AngleMeasurement);
angles.world = world;

We will build dimensions by supplying the components to OBC.SimpleDimensions.

DIMENSIONS AND UI

Read the Simple Dimensions API for more on this. The Simple Dimensions API provides you with a compact UI as well to display the measurements.

๐ŸŽจ SimpleDimensions has several properties that help you to customize the behaviour of the Line Element. -One such property which you can use is dimensions.color which modifies the color of the Line Element.

const angles = components.get(OBCF.AngleMeasurement);
angles.world = world;

๐Ÿ–ฑ๏ธ Managing Eventsโ€‹


You can use the Dimension Tool to construct several dimension lines. Let's see how you handle them.

โœ๏ธ Creating the Dimensionsโ€‹

Now that we've generated the dimensions object, we need to attach the line tooltip to a vertex of the 3D object. +One such property which you can use is dimensions.color which modifies the color of the Line Element.

angles.enabled = true;

๐Ÿ–ฑ๏ธ Managing Eventsโ€‹


You can use the Dimension Tool to construct several dimension lines. Let's see how you handle them.

โœ๏ธ Creating the Dimensionsโ€‹

Now that we've generated the dimensions object, we need to attach the line tooltip to a vertex of the 3D object. We'll use the double click event to invoke dimensions.create(). When this event occurs, a line element is generated, -and the distance is calculated in real-time inside the label associated with that line.๐Ÿท๏ธ

angles.enabled = true;

๐Ÿงน Deleting the Dimensionsโ€‹

Now that we know how to make multiple dimension lines, we must also know how to delete them when necessary. +and the distance is calculated in real-time inside the label associated with that line.๐Ÿท๏ธ

container.ondblclick = () => angles.create();

๐Ÿงน Deleting the Dimensionsโ€‹

Now that we know how to make multiple dimension lines, we must also know how to delete them when necessary. Dimensions can be removed using dimensions.delete(). dimensions.delete() deletes the dimension on which your mouse pointer is now located.

Deleting all the Dimensions

โŽ If you want to safely delete all the dimensions that were created you can simply call -dimensions.deleteAll()

container.ondblclick = () => angles.create();

๐ŸŽ›๏ธ Check Toolbar and UIManager tutorial if you have any doubts!

๐Ÿ–Œ๏ธ Adding Stylesโ€‹


Few final things, we need to add styles for the labels which display the measurement information.

  • ifcjs-dimension-label - The label which is used to show the metric value after both the tooltips are attached.
  • ifcjs-dimension-label:hover - Changing the styling when someone hovers on the dimension label.
  • ifcjs-dimension-preview - The label which shows the measurement when the tooltip is not yet attached.
style
.ifcjs-dimension-label {
background-color: black;
font-family: sans-serif;
color: white;
padding: 8px;
border-radius: 8px;
pointer-events: all;
transition: background-color 200ms ease-in-out;
}
.ifcjs-dimension-label:hover {
background-color: grey;
}
.ifcjs-dimension-preview {
background-color: #ffffff;
width: 2rem;
height: 2rem;
opacity: 0.3;
padding: 8px;
border-radius: 100%;
}

Congratulations ๐ŸŽ‰ on completing this tutorial! Now you can measure any BIM Model or any 3D Object easily using +dimensions.deleteAll()

window.onkeydown = (event) => {
if (event.code === "Delete" || event.code === "Backspace") {
angles.delete();
}
};

// const mainToolbar = new OBC.Toolbar(components, {
// name: "Main Toolbar",
// position: "bottom",
// });
// mainToolbar.addChild(dimensions.uiElement.get("main"));
// components.ui.addToolbar(mainToolbar);

๐ŸŽ›๏ธ Check Toolbar and UIManager tutorial if you have any doubts!

๐Ÿ–Œ๏ธ Adding Stylesโ€‹


Few final things, we need to add styles for the labels which display the measurement information.

style
.ifcjs-dimension-label {
background-color: black;
font-family: sans-serif;
color: white;
padding: 8px;
border-radius: 8px;
pointer-events: all;
transition: background-color 200ms ease-in-out;
}
.ifcjs-dimension-label:hover {
background-color: grey;
}
.ifcjs-dimension-preview {
background-color: #ffffff;
width: 2rem;
height: 2rem;
opacity: 0.3;
padding: 8px;
border-radius: 100%;
}

Congratulations ๐ŸŽ‰ on completing this tutorial! Now you can measure any BIM Model or any 3D Object easily using Simple Dimension Component ๐Ÿ“ -Let's keep it up and check out another tutorial! ๐ŸŽ“

window.onkeydown = (event) => {
if (event.code === "Delete" || event.code === "Backspace") {
angles.delete();
}
};

// const mainToolbar = new OBC.Toolbar(components, {
// name: "Main Toolbar",
// position: "bottom",
// });
// mainToolbar.addChild(dimensions.uiElement.get("main"));
// components.ui.addToolbar(mainToolbar);
- +Let's keep it up and check out another tutorial! ๐ŸŽ“

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Front/AreaMeasurement/index.html b/build/Tutorials/Components/Front/AreaMeasurement/index.html index d3307e2bb..6f19291f8 100644 --- a/build/Tutorials/Components/Front/AreaMeasurement/index.html +++ b/build/Tutorials/Components/Front/AreaMeasurement/index.html @@ -4,7 +4,7 @@ AreaMeasurement | That Open Docs - + @@ -14,23 +14,23 @@ Dimension Tool allows you to perform measurements effortlessly.

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

This tutorial will show you how to add Dimension Tool to your projects, which can be used to acquire accurate dimensions for any 3D Object.๐Ÿ”ญ

๐ŸŽฒ Creating a Cube Meshโ€‹


For this tutorial we will use a Cube, you can add any geometry as per your preference. We will create a Cube -with 3x3x3 dimensions and use red color for the material.

import Stats from "stats.js";
import * as OBC from "@thatopen/components";
import * as THREE from "three";
import * as OBCF from "../..";

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

const components = new OBC.Components();

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

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

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

components.init();

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

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, -which is simply an array of all the meshes in the Scene.๐Ÿ—„๏ธ

const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);
Collection of Meshes

๐Ÿ“ฆ Components.meshes keeps all your meshes including IFC Models, Fragments in +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" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, +which is simply an array of all the meshes in the Scene.๐Ÿ—„๏ธ

world.scene.three.add(cube);
world.meshes.add(cube);
Collection of Meshes

๐Ÿ“ฆ Components.meshes keeps all your meshes including IFC Models, Fragments in one place.

๐Ÿ› ๏ธ Creating Dimension Toolโ€‹


A lot of logic is usually needed to compute dimensions for any item, beginning with ray casting, finding the vertices to snap to, and rendering the UI for every line element.๐Ÿ™„ This may appear to be a lot of effort, but we are handling all the heavy lifting for you, -and you only need to write a few lines for creating the Dimension Tool.๐Ÿ’ช

world.scene.three.add(cube);
world.meshes.add(cube);

We will build dimensions by supplying the components to OBC.SimpleDimensions.

DIMENSIONS AND UI

Read the Simple Dimensions API for more on this. +and you only need to write a few lines for creating the Dimension Tool.๐Ÿ’ช

const areaDims = new OBCF.AreaMeasurement(components);
areaDims.world = world;

We will build dimensions by supplying the components to OBC.SimpleDimensions.

DIMENSIONS AND UI

Read the Simple Dimensions API for more on this. The Simple Dimensions API provides you with a compact UI as well to display the measurements.

๐ŸŽจ SimpleDimensions has several properties that help you to customize the behaviour of the Line Element. -One such property which you can use is dimensions.color which modifies the color of the Line Element.

const areaDims = new OBCF.AreaMeasurement(components);
areaDims.world = world;

๐Ÿ–ฑ๏ธ Managing Eventsโ€‹


You can use the Dimension Tool to construct several dimension lines. Let's see how you handle them.

โœ๏ธ Creating the Dimensionsโ€‹

Now that we've generated the dimensions object, we need to attach the line tooltip to a vertex of the 3D object. +One such property which you can use is dimensions.color which modifies the color of the Line Element.

areaDims.enabled = true;

๐Ÿ–ฑ๏ธ Managing Eventsโ€‹


You can use the Dimension Tool to construct several dimension lines. Let's see how you handle them.

โœ๏ธ Creating the Dimensionsโ€‹

Now that we've generated the dimensions object, we need to attach the line tooltip to a vertex of the 3D object. We'll use the double click event to invoke dimensions.create(). When this event occurs, a line element is generated, -and the distance is calculated in real-time inside the label associated with that line.๐Ÿท๏ธ

areaDims.enabled = true;

๐Ÿงน Deleting the Dimensionsโ€‹

Now that we know how to make multiple dimension lines, we must also know how to delete them when necessary. +and the distance is calculated in real-time inside the label associated with that line.๐Ÿท๏ธ

container.ondblclick = () => areaDims.create();
container.oncontextmenu = () => areaDims.endCreation();

๐Ÿงน Deleting the Dimensionsโ€‹

Now that we know how to make multiple dimension lines, we must also know how to delete them when necessary. Dimensions can be removed using dimensions.delete(). dimensions.delete() deletes the dimension on which your mouse pointer is now located.

Deleting all the Dimensions

โŽ If you want to safely delete all the dimensions that were created you can simply call -dimensions.deleteAll()

container.ondblclick = () => areaDims.create();
container.oncontextmenu = () => areaDims.endCreation();

๐ŸŽ›๏ธ Check Toolbar and UIManager tutorial if you have any doubts!

๐Ÿ–Œ๏ธ Adding Stylesโ€‹


Few final things, we need to add styles for the labels which display the measurement information.

  • ifcjs-dimension-label - The label which is used to show the metric value after both the tooltips are attached.
  • ifcjs-dimension-label:hover - Changing the styling when someone hovers on the dimension label.
  • ifcjs-dimension-preview - The label which shows the measurement when the tooltip is not yet attached.
style
.ifcjs-dimension-label {
background-color: black;
font-family: sans-serif;
color: white;
padding: 8px;
border-radius: 8px;
pointer-events: all;
transition: background-color 200ms ease-in-out;
}
.ifcjs-dimension-label:hover {
background-color: grey;
}
.ifcjs-dimension-preview {
background-color: #ffffff;
width: 2rem;
height: 2rem;
opacity: 0.3;
padding: 8px;
border-radius: 100%;
}

Congratulations ๐ŸŽ‰ on completing this tutorial! Now you can measure any BIM Model or any 3D Object easily using +dimensions.deleteAll()

window.onkeydown = (event) => {
if (event.code === "Delete" || event.code === "Backspace") {
// WORK IN PROGRESS
// dimensions.delete();
}
};

๐ŸŽ›๏ธ Check Toolbar and UIManager tutorial if you have any doubts!

๐Ÿ–Œ๏ธ Adding Stylesโ€‹


Few final things, we need to add styles for the labels which display the measurement information.

style
.ifcjs-dimension-label {
background-color: black;
font-family: sans-serif;
color: white;
padding: 8px;
border-radius: 8px;
pointer-events: all;
transition: background-color 200ms ease-in-out;
}
.ifcjs-dimension-label:hover {
background-color: grey;
}
.ifcjs-dimension-preview {
background-color: #ffffff;
width: 2rem;
height: 2rem;
opacity: 0.3;
padding: 8px;
border-radius: 100%;
}

Congratulations ๐ŸŽ‰ on completing this tutorial! Now you can measure any BIM Model or any 3D Object easily using Simple Dimension Component ๐Ÿ“ -Let's keep it up and check out another tutorial! ๐ŸŽ“

window.onkeydown = (event) => {
if (event.code === "Delete" || event.code === "Backspace") {
// WORK IN PROGRESS
// dimensions.delete();
}
};
- +Let's keep it up and check out another tutorial! ๐ŸŽ“

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Front/EdgesClipper/index.html b/build/Tutorials/Components/Front/EdgesClipper/index.html index 968344e23..9f2ff4edc 100644 --- a/build/Tutorials/Components/Front/EdgesClipper/index.html +++ b/build/Tutorials/Components/Front/EdgesClipper/index.html @@ -4,7 +4,7 @@ EdgesClipper | That Open Docs - + @@ -13,23 +13,23 @@ One such essential component is Edges Clipper which helps you to add Clipping Planes along with beautiful yet functional edges.๐Ÿ–๏ธ

Advanced but Simple to use

โšก๏ธ Simple Clipper and Edges Clipper are similar, but Edges Clipper offers more advanced options. If you want to learn more about Simple Clipper, visit the tutorial.

In this tutorial, we'll use the EdgesClipper to slice two distinct Cubes that each have a unique set of edge effects. -With the help of this tutorial, you can quickly add Clipping Planes and Configurable Edges to your project.๐Ÿš€

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

๐Ÿงฉ Adding Objects to Sceneโ€‹


Let's start by adding two Cubes, we will create a Box Geometry and use it for both Meshes.

import Stats from "stats.js";
import * as BUI from "@thatopen/ui";
import * as THREE from "three";
import * as OBC from "@thatopen/components";
import * as OBCF from "../..";

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

const components = new OBC.Components();

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

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

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

world.renderer.postproduction.enabled = true;
world.renderer.postproduction.customEffects.outlineEnabled = true;

components.init();

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.config.color.setHex(0x666666);
const grid = grids.create(world);
world.renderer.postproduction.customEffects.excludedMeshes.push(grid.three);
Storing Components

๐Ÿงฐ After adding cubes to the scene, we must also add them to components.meshes, +With the help of this tutorial, you can quickly add Clipping Planes and Configurable Edges to your project.๐Ÿš€

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

๐Ÿงฉ Adding Objects to Sceneโ€‹


Let's start by adding two Cubes, we will create a Box Geometry and use it for both Meshes.

const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);

const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(-2, 1.5, 0);
world.scene.three.add(cube);
world.meshes.add(cube);

const cube2 = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube2.position.set(2, 1.5, 0);
world.scene.three.add(cube2);
world.meshes.add(cube2);
Storing Components

๐Ÿงฐ After adding cubes to the scene, we must also add them to components.meshes, which is just an array of all the meshes in the scene.๐Ÿ—„๏ธ

โš”๏ธ Slicing Some Cubesโ€‹


Now that the setup is complete. Let's get started with the interesting part! We will create Edges Clipper and pass the components and -Edges Plane to the constructor.

const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);

const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(-2, 1.5, 0);
world.scene.three.add(cube);
world.meshes.add(cube);

const cube2 = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube2.position.set(2, 1.5, 0);
world.scene.three.add(cube2);
world.meshes.add(cube2);
PLANE WITH EDGES and TRANSFORMATION CONTROLS

๐ŸŸฆ Edges Plane helps us in adding Clipping Planes to the Clipper Component.

๐Ÿ–Œ๏ธ Creating Fine Edgesโ€‹


Let's now prepare the materials that will be visible on the cube edges. +Edges Plane to the constructor.

const casters = components.get(OBC.Raycasters);
casters.get(world);

const clipper = components.get(OBC.Clipper);
clipper.enabled = true;

const edges = components.get(OBCF.ClipEdges);
clipper.Type = OBCF.EdgesPlane;
PLANE WITH EDGES and TRANSFORMATION CONTROLS

๐ŸŸฆ Edges Plane helps us in adding Clipping Planes to the Clipper Component.

๐Ÿ–Œ๏ธ Creating Fine Edgesโ€‹


Let's now prepare the materials that will be visible on the cube edges. We will use LineMaterial for creating edges.

๐Ÿ’ซ Using Line Materialโ€‹

After creating the Line Material we will add it to the clipper -using clipper.styles.create(styleName: string, mesh: Mesh[], material: LineMaterial)

const casters = components.get(OBC.Raycasters);
casters.get(world);

const clipper = components.get(OBC.Clipper);
clipper.enabled = true;

const edges = components.get(OBCF.ClipEdges);
clipper.Type = OBCF.EdgesPlane;

๐Ÿค Performing Clipping Eventsโ€‹


We need a method for instantly producing a clipping plane; +using clipper.styles.create(styleName: string, mesh: Mesh[], material: LineMaterial)

const blueFill = new THREE.MeshBasicMaterial({ color: "lightblue", side: 2 });
const blueLine = new THREE.LineBasicMaterial({ color: "blue" });
const blueOutline = new THREE.MeshBasicMaterial({
color: "blue",
opacity: 0.5,
side: 2,
transparent: true,
});

edges.styles.create(
"Red lines",
new Set([cube]),
world,
blueLine,
blueFill,
blueOutline,
);

const salmonFill = new THREE.MeshBasicMaterial({ color: "salmon", side: 2 });
const redLine = new THREE.LineBasicMaterial({ color: "red" });
const redOutline = new THREE.MeshBasicMaterial({
color: "red",
opacity: 0.5,
side: 2,
transparent: true,
});

edges.styles.create(
"Blue lines",
new Set([cube2]),
world,
redLine,
salmonFill,
redOutline,
);

๐Ÿค Performing Clipping Eventsโ€‹


We need a method for instantly producing a clipping plane; this can be accomplished with either a single click or a double click of the mouse. For this tutorial, we will use Double Click, to create a Clipper that will generate a -plane on the 3D object's face.

const blueFill = new THREE.MeshBasicMaterial({ color: "lightblue", side: 2 });
const blueLine = new THREE.LineBasicMaterial({ color: "blue" });
const blueOutline = new THREE.MeshBasicMaterial({
color: "blue",
opacity: 0.5,
side: 2,
transparent: true,
});

edges.styles.create(
"Red lines",
new Set([cube]),
world,
blueLine,
blueFill,
blueOutline,
);

const salmonFill = new THREE.MeshBasicMaterial({ color: "salmon", side: 2 });
const redLine = new THREE.LineBasicMaterial({ color: "red" });
const redOutline = new THREE.MeshBasicMaterial({
color: "red",
opacity: 0.5,
side: 2,
transparent: true,
});

edges.styles.create(
"Blue lines",
new Set([cube2]),
world,
redLine,
salmonFill,
redOutline,
);
Raycaster below the hood ๐ŸŽฉ

We use the Simple Raycaster to determine if the intersection has occurred. +plane on the 3D object's face.

container.ondblclick = () => clipper.create(world);
Raycaster below the hood ๐ŸŽฉ

We use the Simple Raycaster to determine if the intersection has occurred. The clipper places a plane after detecting the face on which the mouse was clicked. Here, the EdgesClipper handles everything for you ๐Ÿ˜Ž

๐Ÿงน Deleting the Clipping Planesโ€‹


Now that we know how to make multiple clippers, we must also know how to delete them when necessary. Clipping Edges can be removed using clipper.delete() or clipper.delete(plane), which deletes a single plane. -clipper.delete() deletes the plane on which your mouse pointer is now located.

container.ondblclick = () => clipper.create(world);
Delete all Clipping Planes

โŽ If you want to safely delete all the clipping edges that were created you can simply call +clipper.delete() deletes the plane on which your mouse pointer is now located.

window.onkeydown = (event) => {
if (event.code === "Delete" || event.code === "Backspace") {
clipper.delete(world);
}
};
Delete all Clipping Planes

โŽ If you want to safely delete all the clipping edges that were created you can simply call clipper.deleteAll()

Great job! ๐ŸŽ‰ Using the Clipper Component, you can now effortlessly check BIM models or any other 3D objects with stunning edges.๐Ÿง -Let's keep it up and check out another tutorial! ๐ŸŽ“

window.onkeydown = (event) => {
if (event.code === "Delete" || event.code === "Backspace") {
clipper.delete(world);
}
};
- +Let's keep it up and check out another tutorial! ๐ŸŽ“

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Front/IfcStreamer/index.html b/build/Tutorials/Components/Front/IfcStreamer/index.html index 3708d5a0a..02eac2df3 100644 --- a/build/Tutorials/Components/Front/IfcStreamer/index.html +++ b/build/Tutorials/Components/Front/IfcStreamer/index.html @@ -4,22 +4,22 @@ IfcStreamer | That Open Docs - +
Skip to main content

IfcStreamer

Source

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

Now, streaming works by updating the scene depending on the user's perspective and getting the necessary geometries from the backend. A simple way to achieve -this is by updating the scene each time the user stops the camera:

import Stats from "stats.js";
// @ts-ignore
import * as dat from "three/examples/jsm/libs/lil-gui.module.min";
import * as OBC from "@thatopen/components";
import * as OBCF from "../..";

// customEffects.excludedMeshes.push(grid.get());

// rendererComponent.postproduction.enabled = true;

// Set up scene (see SimpleScene tutorial)

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

const components = new OBC.Components();

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

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

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

components.init();

world.scene.setup();

// rendererComponent.postproduction.enabled = true;

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

const grids = components.get(OBC.Grids);
grids.create(world);

const loader = new OBCF.IfcStreamer(components);
loader.world = world;
loader.url = "https://thatopen.github.io/engine_components/resources/streaming/";
// const fragments = components.get(OBC.FragmentsManager);

async function loadModel(geometryURL: string, propertiesURL?: string) {
const rawGeometryData = await fetch(geometryURL);
const geometryData = await rawGeometryData.json();
let propertiesData;
if (propertiesURL) {
const rawPropertiesData = await fetch(propertiesURL);
propertiesData = await rawPropertiesData.json();
}

const model = await loader.load(geometryData, true, propertiesData);

console.log(model);
const props = await model.getProperties(186);
console.log(props);
}

await loadModel(
"https://thatopen.github.io/engine_components/resources/streaming/small.ifc-processed.json",
"https://thatopen.github.io/engine_components/resources/streaming/small.ifc-processed-properties.json",
);

As you can imagine, downloading the geometries from the server each time can +this is by updating the scene each time the user stops the camera:

world.camera.controls.addEventListener("sleep", () => {
loader.culler.needsUpdate = true;
});

As you can imagine, downloading the geometries from the server each time can take time, especially for heavier geometries. This is why the stream loader automatically caches the files locally to get them much faster. This means that the loading experience the first time might be a bit slower, but then later it will be much better. You can control this using the useCache property -and clear the cache using the clearCache() method:

world.camera.controls.addEventListener("sleep", () => {
loader.culler.needsUpdate = true;
});

You can also customize the loader through the culler property:

  • Threshold determines how bit an object must be in the screen to stream it.
  • maxHiddenTime determines how long an object must be lost to remove it from the scene.
  • maxLostTime determines how long an object must be lost to remove it from memory.
loader.useCache = true;

async function clearCache() {
await loader.clearCache();
window.location.reload();
}

This is it! Now you should be able to stream your own IFC models and open them anywhere, +and clear the cache using the clearCache() method:

loader.useCache = true;

async function clearCache() {
await loader.clearCache();
window.location.reload();
}

You can also customize the loader through the culler property:

  • Threshold determines how bit an object must be in the screen to stream it.
  • maxHiddenTime determines how long an object must be lost to remove it from the scene.
  • maxLostTime determines how long an object must be lost to remove it from memory.
loader.culler.threshold = 10;
loader.culler.maxHiddenTime = 1000;
loader.culler.maxLostTime = 40000;

This is it! Now you should be able to stream your own IFC models and open them anywhere, no matter how big they are! ๐Ÿ’ช We will keep improving and making this API more powerful -to handle any model on any device smoothly.

loader.culler.threshold = 10;
loader.culler.maxHiddenTime = 1000;
loader.culler.maxLostTime = 40000;
- +to handle any model on any device smoothly.

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Front/LengthMeasurement/index.html b/build/Tutorials/Components/Front/LengthMeasurement/index.html index e0bcabf93..201e8a20f 100644 --- a/build/Tutorials/Components/Front/LengthMeasurement/index.html +++ b/build/Tutorials/Components/Front/LengthMeasurement/index.html @@ -4,7 +4,7 @@ LengthMeasurement | That Open Docs - + @@ -14,24 +14,24 @@ Dimension Tool allows you to perform measurements effortlessly.

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

This tutorial will show you how to add Dimension Tool to your projects, which can be used to acquire accurate dimensions for any 3D Object.๐Ÿ”ญ

๐ŸŽฒ Creating a Cube Meshโ€‹


For this tutorial we will use a Cube, you can add any geometry as per your preference. We will create a Cube -with 3x3x3 dimensions and use red color for the material.

import Stats from "stats.js";

import * as OBC from "@thatopen/components";
import * as THREE from "three";
import * as BUI from "@thatopen/ui";
import * as OBCF from "../..";

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

const components = new OBC.Components();

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

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

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

components.init();

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

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.create(world);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, -which is simply an array of all the meshes in the Scene.๐Ÿ—„๏ธ

const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);
Collection of Meshes

๐Ÿ“ฆ Components.meshes keeps all your meshes including IFC Models, Fragments in +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" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, +which is simply an array of all the meshes in the Scene.๐Ÿ—„๏ธ

world.scene.three.add(cube);
world.meshes.add(cube);
Collection of Meshes

๐Ÿ“ฆ Components.meshes keeps all your meshes including IFC Models, Fragments in one place.

๐Ÿ› ๏ธ Creating Dimension Toolโ€‹


A lot of logic is usually needed to compute dimensions for any item, beginning with ray casting, finding the vertices to snap to, and rendering the UI for every line element.๐Ÿ™„ This may appear to be a lot of effort, but we are handling all the heavy lifting for you, -and you only need to write a few lines for creating the Dimension Tool.๐Ÿ’ช

world.scene.three.add(cube);
world.meshes.add(cube);

We will build dimensions by supplying the components to OBC.SimpleDimensions.

DIMENSIONS AND UI

Read the Simple Dimensions API for more on this. +and you only need to write a few lines for creating the Dimension Tool.๐Ÿ’ช

const dimensions = new OBCF.LengthMeasurement(components);
dimensions.world = world;

We will build dimensions by supplying the components to OBC.SimpleDimensions.

DIMENSIONS AND UI

Read the Simple Dimensions API for more on this. The Simple Dimensions API provides you with a compact UI as well to display the measurements.

๐ŸŽจ SimpleDimensions has several properties that help you to customize the behaviour of the Line Element. One such property which you can use is dimensions.color which modifies the color of the Line Element. Now, let's enable dimensions and tell them to be snapped at a distance of one unit. snapDistance helps in attaching the tooltip temporarily at regular intervals, -making it easier to interact with items.๐Ÿ“

const dimensions = new OBCF.LengthMeasurement(components);
dimensions.world = world;

๐Ÿ–ฑ๏ธ Managing Eventsโ€‹


You can use the Dimension Tool to construct several dimension lines. Let's see how you handle them.

โœ๏ธ Creating the Dimensionsโ€‹

Now that we've generated the dimensions object, we need to attach the line tooltip to a vertex of the 3D object. +making it easier to interact with items.๐Ÿ“

dimensions.enabled = true;
dimensions.snapDistance = 1;

๐Ÿ–ฑ๏ธ Managing Eventsโ€‹


You can use the Dimension Tool to construct several dimension lines. Let's see how you handle them.

โœ๏ธ Creating the Dimensionsโ€‹

Now that we've generated the dimensions object, we need to attach the line tooltip to a vertex of the 3D object. We'll use the double click event to invoke dimensions.create(). When this event occurs, a line element is generated, -and the distance is calculated in real-time inside the label associated with that line.๐Ÿท๏ธ

dimensions.enabled = true;
dimensions.snapDistance = 1;

๐Ÿงน Deleting the Dimensionsโ€‹

Now that we know how to make multiple dimension lines, we must also know how to delete them when necessary. +and the distance is calculated in real-time inside the label associated with that line.๐Ÿท๏ธ

container.ondblclick = () => dimensions.create();

๐Ÿงน Deleting the Dimensionsโ€‹

Now that we know how to make multiple dimension lines, we must also know how to delete them when necessary. Dimensions can be removed using dimensions.delete(). dimensions.delete() deletes the dimension on which your mouse pointer is now located.

Deleting all the Dimensions

โŽ If you want to safely delete all the dimensions that were created you can simply call -dimensions.deleteAll()

container.ondblclick = () => dimensions.create();
- +dimensions.deleteAll()

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Front/PostproductionRenderer/index.html b/build/Tutorials/Components/Front/PostproductionRenderer/index.html index 098d233c6..314cb56b5 100644 --- a/build/Tutorials/Components/Front/PostproductionRenderer/index.html +++ b/build/Tutorials/Components/Front/PostproductionRenderer/index.html @@ -4,7 +4,7 @@ PostproductionRenderer | That Open Docs - + @@ -12,11 +12,11 @@
Skip to main content

PostproductionRenderer

Source

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

๐Ÿงช Cool Post-Production Effectsโ€‹


Post-production effects enrich your 3D scenes. There are several post-production effects, such as adding shadows, rendering outlines, adding ambient occlusion and applying bloom, that can enhance and make your scene look cool.๐Ÿน

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

In this tutorial we will use Post-Production Renderer to add neat Outlines and Ambient Occlusion to the 3D Model.๐Ÿฆพ

๐Ÿข Adding Fragmentsโ€‹


We'll start by adding a Fragment to our scene using Fragment Manager. -We'll use a simple fragment for the purposes of this tutorial, but the code is capable of handling big files as well.๐Ÿ—๏ธ

Using Fragment Manager!

๐Ÿ‹๏ธ There is a dedicated tutorial on how to use Fragment Manager to load IFC files!

import * as THREE from "three";
import Stats from "stats.js";
import * as BUI from "@thatopen/ui";
import * as OBC from "@thatopen/components";
import * as OBCF from "../..";

// @ts-ignore

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

const components = new OBC.Components();

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

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

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

world.scene.three.background = null;

components.init();

world.camera.controls.setLookAt(12, 6, 8, 0, 0, -10);

world.scene.setup();

const grids = components.get(OBC.Grids);
grids.config.color.set(0x666666);
const grid = grids.create(world);

// Set up stats

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

๐ŸŽฌ Activating the Post-Productionโ€‹


We will activate the post-production effect. -Also, we will enable the visibility for post-production layer.

  • postproduction.active - Enable or Disable the active status of the post-processing effect
  • postproduction.visible - Toggle the visibility of post-processing layer that is created to display the effect.
const fragments = new OBC.FragmentsManager(components);
const file = await fetch(
"https://thatopen.github.io/engine_components/resources/small.frag",
);
const data = await file.arrayBuffer();
const buffer = new Uint8Array(data);
const model = fragments.load(buffer);
world.scene.three.add(model);

// const meshes = [];

// const culler = new OBC.ScreenCuller(components);
// culler.setup();

// for (const fragment of model.items) {
// meshes.push(fragment.mesh);
// culler.elements.add(fragment.mesh);
// }
// culler.elements.needsUpdate = true;

// const controls = cameraComponent.controls;
// controls.addEventListener("controlend", () => {
// culler.elements.needsUpdate = true;
// });

Congratulations ๐ŸŽ‰ on completing this tutorial! Now you know how to add cool effects easily using +We'll use a simple fragment for the purposes of this tutorial, but the code is capable of handling big files as well.๐Ÿ—๏ธ

Using Fragment Manager!

๐Ÿ‹๏ธ There is a dedicated tutorial on how to use Fragment Manager to load IFC files!

const fragments = new OBC.FragmentsManager(components);
const file = await fetch(
"https://thatopen.github.io/engine_components/resources/small.frag",
);
const data = await file.arrayBuffer();
const buffer = new Uint8Array(data);
const model = fragments.load(buffer);
world.scene.three.add(model);

// const meshes = [];

// const culler = new OBC.ScreenCuller(components);
// culler.setup();

// for (const fragment of model.items) {
// meshes.push(fragment.mesh);
// culler.elements.add(fragment.mesh);
// }
// culler.elements.needsUpdate = true;

// const controls = cameraComponent.controls;
// controls.addEventListener("controlend", () => {
// culler.elements.needsUpdate = true;
// });

๐ŸŽฌ Activating the Post-Productionโ€‹


We will activate the post-production effect. +Also, we will enable the visibility for post-production layer.

  • postproduction.active - Enable or Disable the active status of the post-processing effect
  • postproduction.visible - Toggle the visibility of post-processing layer that is created to display the effect.
const { postproduction } = world.renderer;
postproduction.enabled = true;
postproduction.customEffects.excludedMeshes.push(grid.three);

const ao = postproduction.n8ao.configuration;

BUI.Manager.init();

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

<bim-panel-section collapsed label="Gamma">
<bim-checkbox checked label="Gamma Correction"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.setPasses({ gamma: target.value });
}}">
</bim-checkbox>
</bim-panel-section>

<bim-panel-section collapsed label="Custom effects" >
<bim-checkbox checked label="Custom effects"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.setPasses({ custom: target.value });
}}">
</bim-checkbox>

<bim-checkbox checked label="Gamma Correction"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.customEffects.glossEnabled = target.value;
}}">
</bim-checkbox>

<bim-number-input
slider step="0.01" label="Opacity"
value="${postproduction.customEffects.opacity}" min="0" max="1"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.opacity = target.value;
}}">
</bim-number-input>

<bim-number-input
slider step="0.1" label="Tolerance"
value="${postproduction.customEffects.tolerance}" min="0" max="6"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.tolerance = target.value;
}}">
</bim-number-input>

<bim-color-input label="Line color"
@input="${({ target }: { target: BUI.ColorInput }) => {
const color = new THREE.Color(target.value.color);
postproduction.customEffects.lineColor = color.getHex();
}}">
</bim-color-input>

<bim-number-input
slider label="Gloss exponent" step="0.1"
value="${postproduction.customEffects.glossExponent}" min="0" max="5"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.glossExponent = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Max gloss" step="0.05"
value="${postproduction.customEffects.maxGloss}" min="-2" max="2"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.maxGloss = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Min gloss" step="0.05"
value="${postproduction.customEffects.minGloss}" min="-2" max="2"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.minGloss = target.value;
}}">
</bim-number-input>

</bim-panel-section>

<bim-panel-section collapsed label="Ambient Oclussion">

<bim-checkbox label="AO enabled"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.setPasses({ ao: target.value });
}}">
</bim-checkbox>

<bim-checkbox checked label="Half resolution"
@change="${({ target }: { target: BUI.Checkbox }) => {
ao.halfRes = target.value;
}}">
</bim-checkbox>

<bim-checkbox label="Screen space radius"
@change="${({ target }: { target: BUI.Checkbox }) => {
ao.screenSpaceRadius = target.value;
}}">
</bim-checkbox>


<bim-color-input label="AO color"
@input="${({ target }: { target: BUI.ColorInput }) => {
const color = new THREE.Color(target.value.color);
ao.color.r = color.r;
ao.color.g = color.g;
ao.color.b = color.b;
}}">
</bim-color-input>

<bim-number-input
slider label="AO Samples" step="1"
value="${ao.aoSamples}" min="1" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.aoSamples = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Denoise Samples" step="1"
value="${ao.denoiseSamples}" min="1" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.denoiseSamples = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Denoise Radius" step="1"
value="${ao.denoiseRadius}" min="0" max="100"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.denoiseRadius = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="AO Radius" step="1"
value="${ao.aoRadius}" min="0" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.aoRadius = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Distance falloff" step="1"
value="${ao.distanceFalloff}" min="0" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.distanceFalloff = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Intensity" step="1"
value="${ao.intensity}" min="0" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.intensity = target.value;
}}">
</bim-number-input>

</bim-panel-section>

</bim-panel>
`;
});

document.body.append(panel);

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);

Congratulations ๐ŸŽ‰ on completing this tutorial! Now you know how to add cool effects easily using Post Production ๐Ÿ–ผ๏ธ -Let's keep it up and check out another tutorial! ๐ŸŽ“

const { postproduction } = world.renderer;
postproduction.enabled = true;
postproduction.customEffects.excludedMeshes.push(grid.three);

const ao = postproduction.n8ao.configuration;

BUI.Manager.init();

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

<bim-panel-section collapsed label="Gamma">
<bim-checkbox checked label="Gamma Correction"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.setPasses({ gamma: target.value });
}}">
</bim-checkbox>
</bim-panel-section>

<bim-panel-section collapsed label="Custom effects" >
<bim-checkbox checked label="Custom effects"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.setPasses({ custom: target.value });
}}">
</bim-checkbox>

<bim-checkbox checked label="Gamma Correction"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.customEffects.glossEnabled = target.value;
}}">
</bim-checkbox>

<bim-number-input
slider step="0.01" label="Opacity"
value="${postproduction.customEffects.opacity}" min="0" max="1"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.opacity = target.value;
}}">
</bim-number-input>

<bim-number-input
slider step="0.1" label="Tolerance"
value="${postproduction.customEffects.tolerance}" min="0" max="6"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.tolerance = target.value;
}}">
</bim-number-input>

<bim-color-input label="Line color"
@input="${({ target }: { target: BUI.ColorInput }) => {
const color = new THREE.Color(target.value.color);
postproduction.customEffects.lineColor = color.getHex();
}}">
</bim-color-input>

<bim-number-input
slider label="Gloss exponent" step="0.1"
value="${postproduction.customEffects.glossExponent}" min="0" max="5"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.glossExponent = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Max gloss" step="0.05"
value="${postproduction.customEffects.maxGloss}" min="-2" max="2"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.maxGloss = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Min gloss" step="0.05"
value="${postproduction.customEffects.minGloss}" min="-2" max="2"
@change="${({ target }: { target: BUI.NumberInput }) => {
postproduction.customEffects.minGloss = target.value;
}}">
</bim-number-input>

</bim-panel-section>

<bim-panel-section collapsed label="Ambient Oclussion">

<bim-checkbox label="AO enabled"
@change="${({ target }: { target: BUI.Checkbox }) => {
postproduction.setPasses({ ao: target.value });
}}">
</bim-checkbox>

<bim-checkbox checked label="Half resolution"
@change="${({ target }: { target: BUI.Checkbox }) => {
ao.halfRes = target.value;
}}">
</bim-checkbox>

<bim-checkbox label="Screen space radius"
@change="${({ target }: { target: BUI.Checkbox }) => {
ao.screenSpaceRadius = target.value;
}}">
</bim-checkbox>


<bim-color-input label="AO color"
@input="${({ target }: { target: BUI.ColorInput }) => {
const color = new THREE.Color(target.value.color);
ao.color.r = color.r;
ao.color.g = color.g;
ao.color.b = color.b;
}}">
</bim-color-input>

<bim-number-input
slider label="AO Samples" step="1"
value="${ao.aoSamples}" min="1" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.aoSamples = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Denoise Samples" step="1"
value="${ao.denoiseSamples}" min="1" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.denoiseSamples = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Denoise Radius" step="1"
value="${ao.denoiseRadius}" min="0" max="100"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.denoiseRadius = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="AO Radius" step="1"
value="${ao.aoRadius}" min="0" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.aoRadius = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Distance falloff" step="1"
value="${ao.distanceFalloff}" min="0" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.distanceFalloff = target.value;
}}">
</bim-number-input>

<bim-number-input
slider label="Intensity" step="1"
value="${ao.intensity}" min="0" max="16"
@change="${({ target }: { target: BUI.NumberInput }) => {
ao.intensity = target.value;
}}">
</bim-number-input>

</bim-panel-section>

</bim-panel>
`;
});

document.body.append(panel);

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);
- +Let's keep it up and check out another tutorial! ๐ŸŽ“

+ \ No newline at end of file diff --git a/build/Tutorials/Components/Front/ShadowDropper/index.html b/build/Tutorials/Components/Front/ShadowDropper/index.html index 6aaaf7398..80dc334d3 100644 --- a/build/Tutorials/Components/Front/ShadowDropper/index.html +++ b/build/Tutorials/Components/Front/ShadowDropper/index.html @@ -4,7 +4,7 @@ ShadowDropper | That Open Docs - + @@ -14,19 +14,19 @@ In this tutorial, we'll show you how to use Shadow Dropper to quickly apply shadows. In less than 5 minutes, you can create realistic shadows for all the meshes inside your scene.โฑ๏ธ

First, let's set up a simple scene!

๐Ÿ‘€ If you haven't started there, check out that tutorial first!

๐ŸŽฒ Creating a Cube Meshโ€‹


Let's start by adding a Cube, which we can dissect. We will create a Cube -with 3x3x3 dimensions and use red color for the material.

import * as THREE from "three";
import * as OBC from "@thatopen/components";
import Stats from "stats.js";
import * as BUI from "@thatopen/ui";
import * as OBCF from "../..";

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

const components = new OBC.Components();

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

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

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

world.scene.setup();

components.init();

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

container.appendChild(world.renderer.three2D.domElement);

const grids = components.get(OBC.Grids);
grids.config.color.setHex(0xdddddd);
grids.create(world);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, +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" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);

Now, we will add the Cube to the Scene. We must also add the cube to components.meshes, which is simply an array of all the meshes in the Scene ๐Ÿ—„๏ธ. -components.meshes acts as a store to help you manage your elements centrally.

const cubeGeometry = new THREE.BoxGeometry(3, 3, 3);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: "#6528D7" });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 1.5, 0);

๐ŸŒš Adding Beautiful Shadowโ€‹


This completes our scene setup. Let's now include Shadows, -we'll use ShadowDropper and pass components as an argument to it.๐Ÿ”—

world.scene.three.background = new THREE.Color("white");
world.scene.three.add(cube);
world.meshes.add(cube);

Shadow Dropper Component not only adds shadows to the scene, but it also helps you manage the Shadows. -To obtain the required results, you can alter the ShadowDropper parameters.๐Ÿ”ง

const shadows = new OBCF.ShadowDropper(components);