Skip to content

Commit cfaf85e

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
docs: migrate camera sandbox
1 parent 27d401f commit cfaf85e

File tree

3 files changed

+197
-0
lines changed

3 files changed

+197
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<div class="block h-full w-full bg-black">
2+
<ngt-canvas
3+
[sceneGraph]="scene"
4+
[gl]="{ alpha: false }"
5+
[camera]="{ fov: 50, near: 1, far: 10000, position: [0, 0, 2500] }"
6+
(created)="onCreated($event)"
7+
/>
8+
</div>
9+
<div class="absolute text-center text-white w-full top-0 left-0 text-xl">
10+
<a href="https://threejs.org" target="_blank" rel="noopener" class="underline">three.js</a>
11+
-
12+
<a href="https://angular-threejs.netlify.app" target="_blank" rel="noopener" class="underline">Angular Three</a>
13+
- cameras
14+
<br />
15+
<b class="text-green-400">O</b>
16+
orthographic
17+
<b class="text-green-400">P</b>
18+
perspective
19+
</div>
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
AfterViewInit,
3+
CUSTOM_ELEMENTS_SCHEMA,
4+
Component,
5+
ElementRef,
6+
OnInit,
7+
ViewChild,
8+
computed,
9+
inject,
10+
signal,
11+
} from '@angular/core';
12+
import { NgtArgs, NgtCanvas, NgtRenderState, NgtState, NgtStore, injectBeforeRender } from 'angular-three';
13+
import * as THREE from 'three';
14+
15+
@Component({
16+
standalone: true,
17+
templateUrl: 'scene.html',
18+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
19+
imports: [NgtArgs],
20+
host: { '(document:keydown)': 'onKeyDown($event)' },
21+
})
22+
class SceneGraph implements OnInit, AfterViewInit {
23+
readonly Math = Math;
24+
readonly #aspect = inject(NgtStore).select('viewport', 'aspect');
25+
26+
readonly perspectiveAspect = computed(() => this.#aspect() / 2);
27+
readonly left = computed(() => (300 * this.#aspect()) / -2);
28+
readonly right = computed(() => (300 * this.#aspect()) / 2);
29+
30+
readonly vertices = Array.from({ length: 10000 }, () => [
31+
THREE.MathUtils.randFloatSpread(2000),
32+
THREE.MathUtils.randFloatSpread(2000),
33+
THREE.MathUtils.randFloatSpread(2000),
34+
]).flat();
35+
36+
readonly #activeCamera = signal<THREE.Camera>(null!);
37+
readonly #activeHelper = signal<THREE.CameraHelper>(null!);
38+
39+
@ViewChild('perspectiveCamera', { static: true }) perspectiveCamera!: ElementRef<THREE.PerspectiveCamera>;
40+
@ViewChild('orthographicCamera', { static: true }) orthographicCamera!: ElementRef<THREE.OrthographicCamera>;
41+
@ViewChild('perspectiveHelper') perspectiveHelper?: ElementRef<THREE.CameraHelper>;
42+
@ViewChild('orthographicHelper') orthographicHelper?: ElementRef<THREE.CameraHelper>;
43+
@ViewChild('cameras', { static: true }) cameraGroup!: ElementRef<THREE.Group>;
44+
@ViewChild('mesh', { static: true }) mesh!: ElementRef<THREE.Mesh>;
45+
46+
constructor() {
47+
injectBeforeRender(this.#onBeforeRender.bind(this), { priority: 1 });
48+
}
49+
50+
ngOnInit() {
51+
this.#activeCamera.set(this.perspectiveCamera.nativeElement);
52+
}
53+
54+
ngAfterViewInit() {
55+
this.#activeHelper.set(this.perspectiveHelper!.nativeElement);
56+
}
57+
58+
onKeyDown({ key }: KeyboardEvent) {
59+
switch (key.toLowerCase()) {
60+
case 'o':
61+
this.#activeCamera.set(this.orthographicCamera.nativeElement);
62+
this.#activeHelper.set(this.orthographicHelper!.nativeElement);
63+
break;
64+
case 'p':
65+
this.#activeCamera.set(this.perspectiveCamera.nativeElement);
66+
this.#activeHelper.set(this.perspectiveHelper!.nativeElement);
67+
}
68+
}
69+
70+
#onBeforeRender({ gl, size, camera, scene }: NgtRenderState) {
71+
if (!this.#activeCamera() || !this.#activeHelper()) return;
72+
const r = Date.now() * 0.0005;
73+
// reassign shorthands
74+
const mesh = this.mesh.nativeElement;
75+
const cameraGroup = this.cameraGroup.nativeElement;
76+
const perspectiveCamera = this.perspectiveCamera.nativeElement;
77+
const perspectiveHelper = this.perspectiveHelper?.nativeElement;
78+
const orthographicCamera = this.orthographicCamera.nativeElement;
79+
const orthographicHelper = this.orthographicHelper?.nativeElement;
80+
81+
mesh.position.x = 700 * Math.cos(r);
82+
mesh.position.z = 700 * Math.sin(r);
83+
mesh.position.y = 700 * Math.sin(r);
84+
85+
mesh.children[0].position.x = 70 * Math.cos(2 * r);
86+
mesh.children[0].position.y = 70 * Math.sin(r);
87+
88+
if (perspectiveCamera && orthographicCamera && perspectiveHelper && orthographicHelper) {
89+
if (this.#activeCamera() === perspectiveCamera) {
90+
perspectiveCamera.fov = 35 + 30 * Math.sin(0.5 * r);
91+
perspectiveCamera.far = mesh.position.length();
92+
perspectiveCamera.updateProjectionMatrix();
93+
94+
perspectiveHelper.update();
95+
perspectiveHelper.visible = true;
96+
97+
orthographicHelper.visible = false;
98+
} else {
99+
orthographicCamera.far = mesh.position.length();
100+
orthographicCamera.updateProjectionMatrix();
101+
102+
orthographicHelper.update();
103+
orthographicHelper.visible = true;
104+
105+
perspectiveHelper.visible = false;
106+
}
107+
}
108+
109+
cameraGroup.lookAt(mesh.position);
110+
111+
gl.clear();
112+
113+
this.#activeHelper().visible = false;
114+
gl.setViewport(0, 0, size.width / 2, size.height);
115+
gl.render(scene, this.#activeCamera());
116+
117+
this.#activeHelper().visible = true;
118+
119+
gl.setViewport(size.width / 2, 0, size.width / 2, size.height);
120+
gl.render(scene, camera);
121+
}
122+
}
123+
124+
@Component({
125+
standalone: true,
126+
templateUrl: 'index.html',
127+
imports: [NgtCanvas],
128+
})
129+
export default class Camera {
130+
readonly scene = SceneGraph;
131+
132+
onCreated({ gl, camera, viewport }: NgtState) {
133+
gl.autoClear = false;
134+
(camera as THREE.PerspectiveCamera).aspect = viewport.aspect / 2;
135+
}
136+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<ngt-group #cameras>
2+
<ngt-perspective-camera
3+
#perspectiveCamera
4+
[aspect]="perspectiveAspect()"
5+
[near]="150"
6+
[far]="1000"
7+
[rotation]="[0, Math.PI, 0]"
8+
/>
9+
<ngt-orthographic-camera
10+
#orthographicCamera
11+
[left]="left()"
12+
[right]="right()"
13+
[top]="300"
14+
[bottom]="-300"
15+
[near]="150"
16+
[far]="1000"
17+
[rotation]="[0, Math.PI, 0]"
18+
/>
19+
20+
<ngt-mesh [position]="[0, 0, 150]">
21+
<ngt-sphere-geometry *args="[5, 16, 8]" />
22+
<ngt-mesh-basic-material color="#0000ff" [wireframe]="true" />
23+
</ngt-mesh>
24+
</ngt-group>
25+
26+
<ngt-camera-helper #perspectiveHelper *args="[perspectiveCamera]" />
27+
<ngt-camera-helper #orthographicHelper *args="[orthographicCamera]" />
28+
29+
<ngt-mesh #mesh>
30+
<ngt-sphere-geometry *args="[100, 16, 8]" />
31+
<ngt-mesh-basic-material [wireframe]="true" />
32+
33+
<ngt-mesh [position]="[0, 150, 0]">
34+
<ngt-sphere-geometry *args="[50, 16, 8]" />
35+
<ngt-mesh-basic-material color="#00ff00" [wireframe]="true" />
36+
</ngt-mesh>
37+
</ngt-mesh>
38+
39+
<ngt-points>
40+
<ngt-float32-buffer-attribute *args="[vertices, 3]" attach="geometry.attributes.position" />
41+
<ngt-points-material color="#888888" />
42+
</ngt-points>

0 commit comments

Comments
 (0)