Skip to content

Commit 2dc87d8

Browse files
committed
feat: implement culler
1 parent ead30d4 commit 2dc87d8

12 files changed

Lines changed: 167 additions & 228 deletions

File tree

File renamed without changes.

temp/core/ScreenCuller/example.ts renamed to packages/components/src/core/Cullers/example.ts

Lines changed: 30 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
// Set up scene (see SimpleScene tutorial)
2-
31
import * as THREE from "three";
2+
// eslint-disable-next-line import/no-extraneous-dependencies
43
import Stats from "stats.js";
54
import * as OBC from "../..";
65

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

98
const components = new OBC.Components();
109

11-
const sceneComponent = new OBC.SimpleScene(components);
12-
sceneComponent.setup();
13-
components.scene = sceneComponent;
14-
15-
const rendererComponent = new OBC.PostproductionRenderer(components, container);
16-
components.renderer = rendererComponent;
10+
const worlds = components.get(OBC.Worlds);
11+
const world = new OBC.SimpleWorld<
12+
OBC.SimpleScene,
13+
OBC.SimpleCamera,
14+
OBC.SimpleRenderer
15+
>(components);
1716

18-
const cameraComponent = new OBC.SimpleCamera(components);
19-
components.camera = cameraComponent;
17+
world.scene = new OBC.SimpleScene(components);
18+
world.renderer = new OBC.SimpleRenderer(components, container);
19+
world.camera = new OBC.SimpleCamera(components);
2020

21-
components.raycaster = new OBC.SimpleRaycaster(components);
21+
worlds.add(world);
2222

2323
components.init();
2424

25-
const scene = components.scene.get();
25+
world.camera.controls.setLookAt(13, 13, 13, 0, 0, 0);
2626

27-
cameraComponent.controls.setLookAt(13, 13, 13, 0, 0, 0);
27+
world.scene.setup();
2828

29-
// @ts-ignore
30-
const grid = new OBC.SimpleGrid(components);
29+
const grids = components.get(OBC.Grids);
30+
grids.create(world);
3131

3232
/* MD
3333
@@ -53,8 +53,8 @@ const grid = new OBC.SimpleGrid(components);
5353
5454
*/
5555

56-
const culler = new OBC.ScreenCuller(components);
57-
await culler.setup();
56+
const cullers = new OBC.Cullers(components);
57+
const culler = cullers.create(world);
5858

5959
/* MD
6060
@@ -63,18 +63,18 @@ await culler.setup();
6363
6464
*/
6565

66-
culler.elements.threshold = 200;
66+
culler.threshold = 200;
6767

6868
/* MD
6969
70-
Additionally, we will activate the `culler.elements.renderDebugFrame`
70+
Additionally, we will activate the `culler.renderDebugFrame`
7171
so that we can see the 2D screen of the elements that are not occluded.💻
7272
Also, we will get the **domElement** and attach it to the body so that we can see this frame in real-time.📊
7373
7474
*/
7575

76-
culler.elements.renderDebugFrame = true;
77-
const debugFrame = culler.elements.get().domElement;
76+
culler.renderDebugFrame = true;
77+
const debugFrame = culler.renderer.domElement;
7878
document.body.appendChild(debugFrame);
7979
debugFrame.style.position = "fixed";
8080
debugFrame.style.left = "0";
@@ -117,7 +117,7 @@ const material = new THREE.MeshLambertMaterial({ color: "#6528D7" });
117117
and randomly position them. We'll add the cube to the scene and adjust its position between 0 and 10.
118118
119119
Additionally, we will add meshes to the `culler` object, which will help **SimpleCuller** to recognize and
120-
draw the elements that are visible to the camera. To do this, **`culler.elements.add(cube)`** will be used.
120+
draw the elements that are visible to the camera. To do this, **`culler.add(cube)`** will be used.
121121
122122
Also, now that we can create multiple cubes, we will write a function to remove the cubes from scene on demand.
123123
`resetCubes()` iteratively removes the **cubes** using [**`cube.removeFromParent`**](https://threejs.org/docs/index.html?q=obje#api/en/core/Object3D.removeFromParent).
@@ -139,8 +139,8 @@ function regenerateCubes() {
139139
cube.position.y = getRandomNumber(10);
140140
cube.position.z = getRandomNumber(10);
141141
cube.updateMatrix();
142-
scene.add(cube);
143-
culler.elements.add(cube);
142+
world.scene.three.add(cube);
143+
culler.add(cube);
144144
cubes.push(cube);
145145
}
146146
}
@@ -158,16 +158,16 @@ regenerateCubes();
158158
Here comes the most crucial part! The core aim of **ScreenCuller** is to output just those components that are
159159
visible to the camera.
160160
161-
`culler.elements.needsUpdate = true` instructs the ScreenCuller to render the updated view.
161+
`culler.needsUpdate = true` instructs the ScreenCuller to render the updated view.
162162
163163
** Remember to update culler every time the camera is updated ❕ **
164164
165165
In this tutorial we are updating it each time the camera stops moving.
166166
*/
167167

168-
culler.elements.needsUpdate = true;
169-
cameraComponent.controls.addEventListener("controlend", () => {
170-
culler.elements.needsUpdate = true;
168+
culler.needsUpdate = true;
169+
world.camera.controls.addEventListener("controlend", () => {
170+
culler.needsUpdate = true;
171171
});
172172

173173
/* MD
@@ -182,30 +182,5 @@ const stats = new Stats();
182182
stats.showPanel(2);
183183
document.body.append(stats.dom);
184184
stats.dom.style.left = "0px";
185-
rendererComponent.onBeforeUpdate.add(() => stats.begin());
186-
rendererComponent.onAfterUpdate.add(() => stats.end());
187-
188-
// Set up GUI
189-
190-
const optionsDrawer = new OBC.Drawer(components);
191-
optionsDrawer.size = "15rem";
192-
components.ui.add(optionsDrawer);
193-
optionsDrawer.visible = true;
194-
optionsDrawer.alignment = "right";
195-
196-
const regenerateButton = new OBC.Button(components);
197-
regenerateButton.domElement.classList.add("shadow-md");
198-
regenerateButton.domElement.classList.add("backdrop-blur-xl");
199-
optionsDrawer.addChild(regenerateButton);
200-
regenerateButton.label = "Regenerate";
201-
regenerateButton.materialIcon = "cached";
202-
regenerateButton.onClick.add(() => {
203-
culler.elements.needsUpdate = true;
204-
});
205-
206-
const visibleCheckbox = new OBC.CheckboxInput(components);
207-
optionsDrawer.addChild(visibleCheckbox);
208-
visibleCheckbox.label = "Debug frame visible";
209-
visibleCheckbox.onChange.add((value: boolean) => {
210-
debugFrame.style.visibility = value ? "visible" : "collapse";
211-
});
185+
world.renderer.onBeforeUpdate.add(() => stats.begin());
186+
world.renderer.onAfterUpdate.add(() => stats.end());
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Components } from "../Components";
2+
import { MeshCullerRenderer, CullerRendererSettings } from "./src";
3+
import { Component, Event, Disposable, World } from "../Types";
4+
5+
/**
6+
* A tool to handle big scenes efficiently by automatically hiding the objects
7+
* that are not visible to the camera.
8+
*/
9+
export class Cullers extends Component implements Disposable {
10+
static readonly uuid = "69f2a50d-c266-44fc-b1bd-fa4d34be89e6" as const;
11+
12+
private _enabled = true;
13+
14+
list = new Map<string, MeshCullerRenderer>();
15+
16+
/** {@link Disposable.onDisposed} */
17+
readonly onDisposed = new Event();
18+
19+
/** {@link Component.enabled} */
20+
get enabled() {
21+
return this._enabled;
22+
}
23+
24+
set enabled(value: boolean) {
25+
this._enabled = value;
26+
for (const [_id, renderer] of this.list) {
27+
renderer.enabled = value;
28+
}
29+
}
30+
31+
constructor(components: Components) {
32+
super(components);
33+
components.add(Cullers.uuid, this);
34+
}
35+
36+
create(world: World, config?: Partial<CullerRendererSettings>) {
37+
if (this.list.has(world.uuid)) {
38+
return this.list.get(world.uuid) as MeshCullerRenderer;
39+
}
40+
const culler = new MeshCullerRenderer(this.components, world, config);
41+
this.list.set(world.uuid, culler);
42+
return culler;
43+
}
44+
45+
remove(world: World) {
46+
const culler = this.list.get(world.uuid);
47+
if (culler) {
48+
culler.dispose();
49+
}
50+
this.list.delete(world.uuid);
51+
}
52+
53+
/** {@link Disposable.dispose} */
54+
dispose() {
55+
this.enabled = false;
56+
this.onDisposed.trigger(Cullers.uuid);
57+
this.onDisposed.reset();
58+
for (const [_id, culler] of this.list) {
59+
culler.dispose();
60+
}
61+
this.list.clear();
62+
}
63+
}

temp/core/ScreenCuller/src/culler-renderer.ts renamed to packages/components/src/core/Cullers/src/culler-renderer.ts

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as THREE from "three";
2-
import { Component, Disposable, Event } from "../../../base-types";
32
import { Components } from "../../Components";
43
import { readPixelsAsync } from "./screen-culler-helper";
4+
import { Event, World } from "../../Types";
55

66
export interface CullerRendererSettings {
77
updateInterval?: number;
@@ -13,7 +13,7 @@ export interface CullerRendererSettings {
1313
/**
1414
* A base renderer to determine visibility on screen
1515
*/
16-
export class CullerRenderer extends Component<THREE.WebGLRenderer> {
16+
export class CullerRenderer {
1717
/** {@link Disposable.onDisposed} */
1818
readonly onDisposed = new Event<string>();
1919

@@ -39,35 +39,53 @@ export class CullerRenderer extends Component<THREE.WebGLRenderer> {
3939
*/
4040
renderDebugFrame = false;
4141

42-
private _width = 512;
43-
private _height = 512;
42+
components: Components;
43+
44+
readonly world: World;
45+
46+
readonly renderer: THREE.WebGLRenderer;
4447

4548
protected autoUpdate = true;
49+
4650
protected updateInterval = 1000;
4751

4852
protected readonly worker: Worker;
4953

50-
protected readonly renderer: THREE.WebGLRenderer;
51-
private readonly renderTarget: THREE.WebGLRenderTarget;
5254
protected readonly scene = new THREE.Scene();
5355

54-
private readonly bufferSize: number;
56+
private _width = 512;
57+
58+
private _height = 512;
5559

5660
private _availableColor = 1;
5761

62+
private readonly renderTarget: THREE.WebGLRenderTarget;
63+
64+
private readonly bufferSize: number;
65+
5866
private readonly _buffer: Uint8Array;
5967

60-
constructor(components: Components, settings?: CullerRendererSettings) {
61-
super(components);
68+
constructor(
69+
components: Components,
70+
world: World,
71+
settings?: CullerRendererSettings,
72+
) {
73+
if (!world.renderer) {
74+
throw new Error("The given world must have a renderer!");
75+
}
76+
77+
this.components = components;
6278
this.applySettings(settings);
6379

80+
this.world = world;
6481
this.renderer = new THREE.WebGLRenderer();
65-
const planes = this.components.renderer.clippingPlanes;
66-
this.renderer.clippingPlanes = planes;
82+
6783
this.renderTarget = new THREE.WebGLRenderTarget(this._width, this._height);
6884
this.bufferSize = this._width * this._height * 4;
6985
this._buffer = new Uint8Array(this.bufferSize);
7086

87+
this.renderer.clippingPlanes = world.renderer.clippingPlanes;
88+
7189
const code = `
7290
addEventListener("message", (event) => {
7391
const { buffer } = event.data;
@@ -91,15 +109,8 @@ export class CullerRenderer extends Component<THREE.WebGLRenderer> {
91109
this.worker = new Worker(URL.createObjectURL(blob));
92110
}
93111

94-
/**
95-
* {@link Component.get}.
96-
*/
97-
get() {
98-
return this.renderer;
99-
}
100-
101112
/** {@link Disposable.dispose} */
102-
async dispose() {
113+
dispose() {
103114
this.enabled = false;
104115
for (const child of this.scene.children) {
105116
child.removeFromParent();
@@ -122,7 +133,7 @@ export class CullerRenderer extends Component<THREE.WebGLRenderer> {
122133
if (!this.enabled) return;
123134
if (!this.needsUpdate && !force) return;
124135

125-
const camera = this.components.camera.get();
136+
const camera = this.world.camera.three;
126137
camera.updateMatrix();
127138

128139
this.renderer.setSize(this._width, this._height);
@@ -138,7 +149,7 @@ export class CullerRenderer extends Component<THREE.WebGLRenderer> {
138149
this._height,
139150
context.RGBA,
140151
context.UNSIGNED_BYTE,
141-
this._buffer
152+
this._buffer,
142153
);
143154

144155
this.renderer.setRenderTarget(null);
File renamed without changes.

0 commit comments

Comments
 (0)