Skip to content

Commit 15b0a27

Browse files
committed
docs: adjust sandbox app component
1 parent 81ec14d commit 15b0a27

File tree

10 files changed

+257
-36
lines changed

10 files changed

+257
-36
lines changed

apps/sandbox/src/app/app.component.css

Lines changed: 0 additions & 5 deletions
This file was deleted.

apps/sandbox/src/app/app.component.ts

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { NgtsLoader, injectNgtsGLTFLoader } from 'angular-three-soba/loaders';
1212
import { injectNgtsAnimations } from 'angular-three-soba/misc';
1313
import * as THREE from 'three';
1414
import { SkyDivingScene } from './skydiving/scene.component';
15+
import { VaporwareScene } from './vaporware/scene.component';
1516

1617
extend(THREE);
1718

@@ -125,48 +126,67 @@ export class Scene {
125126
});
126127
}
127128

129+
const scenes = {
130+
bot: {
131+
scene: Scene,
132+
cameraOptions: {},
133+
glOptions: {},
134+
},
135+
cannon: {
136+
scene: CannonScene,
137+
cameraOptions: { position: [0, 0, 15] },
138+
glOptions: { useLegacyLights: true },
139+
},
140+
skydiving: {
141+
scene: SkyDivingScene,
142+
cameraOptions: { fov: 70, position: [0, 0, 3] },
143+
glOptions: { useLegacyLights: true },
144+
},
145+
vaporware: {
146+
scene: VaporwareScene,
147+
cameraOptions: { near: 0.01, far: 20, position: [0, 0.06, 1.1] },
148+
glOptions: { useLegacyLights: true },
149+
},
150+
} as const;
151+
const availableScenes = Object.keys(scenes) as [keyof typeof scenes];
152+
153+
type AvailableScene = (typeof availableScenes)[number];
154+
128155
@Component({
129156
standalone: true,
130157
imports: [NgtCanvas, NgtKey, NgtsLoader],
131158
selector: 'sandbox-root',
132159
template: `
133160
<ngt-canvas
134161
*key="scene"
135-
[sceneGraph]="scenes[scene].scene"
136-
[camera]="scenes[scene].cameraOptions"
162+
[sceneGraph]="currentScene.scene"
163+
[camera]="currentScene.cameraOptions"
164+
[gl]="currentScene.glOptions"
137165
[shadows]="true"
138-
[gl]="scenes[scene].glOptions"
139166
/>
140167
<ngts-loader />
141-
<!-- <button (click)="onToggle()">Toggle scene: {{ scene }}</button> -->
168+
<button class="cycle" (click)="cycleScene()">Current scene: {{ scene }}</button>
142169
`,
143-
styleUrls: ['./app.component.css'],
170+
host: {
171+
'[style.--background]': 'background',
172+
style: 'background-color: var(--background); display: block; height: 100%; width: 100%',
173+
},
144174
})
145175
export class AppComponent {
146-
scene: 'cannon' | 'bot' | 'skydiving' = 'skydiving';
147-
scenes = {
148-
bot: {
149-
scene: Scene,
150-
cameraOptions: {},
151-
glOptions: {},
152-
},
153-
cannon: {
154-
scene: CannonScene,
155-
cameraOptions: { position: [0, 0, 15] },
156-
glOptions: { useLegacyLights: true },
157-
},
158-
skydiving: {
159-
scene: SkyDivingScene,
160-
cameraOptions: { fov: 70, position: [0, 0, 3] },
161-
glOptions: { useLegacyLights: true },
162-
},
163-
} as const;
164-
165-
onToggle() {
166-
if (this.scene === 'bot') {
167-
this.scene = 'cannon';
168-
} else {
169-
this.scene = 'bot';
170-
}
176+
scene: AvailableScene = 'vaporware';
177+
178+
get currentScene() {
179+
return scenes[this.scene];
180+
}
181+
182+
get background() {
183+
if (this.scene === 'skydiving') return '#272727';
184+
if (this.scene === 'vaporware') return 'black';
185+
return 'white';
186+
}
187+
188+
cycleScene() {
189+
const index = availableScenes.indexOf(this.scene);
190+
this.scene = availableScenes[(index + 1) % availableScenes.length];
171191
}
172192
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { NgIf } from '@angular/common';
2+
import { CUSTOM_ELEMENTS_SCHEMA, Component, ElementRef, ViewChild, effect } from '@angular/core';
3+
import {
4+
NgtArgs,
5+
createAttachFunction,
6+
extend,
7+
injectBeforeRender,
8+
injectNgtRef,
9+
injectNgtStore,
10+
type NgtNode,
11+
} from 'angular-three';
12+
import { NgtsOrbitControls } from 'angular-three-soba/controls';
13+
import { injectNgtsTextureLoader } from 'angular-three-soba/loaders';
14+
import { Mesh, PlaneGeometry } from 'three';
15+
import {
16+
EffectComposer,
17+
GammaCorrectionShader,
18+
RGBShiftShader,
19+
RenderPass,
20+
ShaderPass,
21+
UnrealBloomPass,
22+
type Pass,
23+
} from 'three-stdlib';
24+
25+
// Using "extend" to extend the THREE catalogue of Angular Three custom renderer
26+
extend({ EffectComposer, RenderPass, ShaderPass, UnrealBloomPass });
27+
28+
declare global {
29+
interface HTMLElementTagNameMap {
30+
'ngt-shader-pass': NgtNode<ShaderPass, typeof ShaderPass>;
31+
}
32+
}
33+
34+
/**
35+
* Vaporware Scene is ported from Maxime's article: https://blog.maximeheckel.com/posts/vaporwave-3d-scene-with-threejs/
36+
*/
37+
@Component({
38+
standalone: true,
39+
template: `
40+
<!-- Top-level elements with "attach" will get attached to the root THREE.Scene -->
41+
<!-- Here, the Fog will get attached to scene.fog -->
42+
<ngt-fog attach="fog" *args="['#000', 1, 2.5]" />
43+
44+
<ngt-ambient-light [intensity]="Math.PI" />
45+
46+
<ngt-spot-light
47+
color="#d53c3d"
48+
[position]="[0.5, 0.75, 2.2]"
49+
[intensity]="20"
50+
[distance]="25"
51+
[angle]="Math.PI * 0.1"
52+
[penumbra]="0.25"
53+
[decay]="10"
54+
>
55+
<ngt-vector3 *args="[-0.25, 0.25, 0.25]" attach="target.position" />
56+
</ngt-spot-light>
57+
58+
<ngt-spot-light
59+
color="#d53c3d"
60+
[position]="[-0.5, 0.75, 2.2]"
61+
[intensity]="20"
62+
[distance]="25"
63+
[angle]="Math.PI * 0.1"
64+
[penumbra]="0.25"
65+
[decay]="10"
66+
>
67+
<ngt-vector3 *args="[0.25, 0.25, 0.25]" attach="target.position" />
68+
</ngt-spot-light>
69+
70+
<!-- Good ol' ngIf-as trick to delay rendering of the whole block. -->
71+
<!-- We don't want to render anything until the textures are loaded -->
72+
<ng-container *ngIf="textures() as textures">
73+
<!-- We use geometryRef for geometry because *args structural directive wraps ngt-plane-geometry -->
74+
<!-- A simple Template Variable "#geometry" won't work. -->
75+
<ngt-plane-geometry *args="[1, 2, 24, 24]" [ref]="geometryRef" />
76+
77+
<!-- For material (or any element that doesn't have a structural directive attached to them) -->
78+
<!-- we can use Template Variable to get a hold of that element instance -->
79+
<ngt-mesh-standard-material
80+
#material
81+
[map]="textures.grid"
82+
[displacementMap]="textures.displacement"
83+
[metalnessMap]="textures.metalness"
84+
[displacementScale]="0.4"
85+
[metalness]="0.96"
86+
[roughness]="0.5"
87+
/>
88+
89+
<!-- With access to geometry and material, we can reuse them -->
90+
<ngt-mesh
91+
#frontPlane
92+
[rotation]="[-Math.PI / 2, 0, 0]"
93+
[position]="[0, 0, 0.15]"
94+
[geometry]="geometryRef.nativeElement"
95+
[material]="material"
96+
/>
97+
98+
<ngt-mesh
99+
#backPlane
100+
[rotation]="[-Math.PI / 2, 0, 0]"
101+
[position]="[0, 0, -1.85]"
102+
[geometry]="geometryRef.nativeElement"
103+
[material]="material"
104+
/>
105+
</ng-container>
106+
107+
<ngt-effect-composer *args="[gl()]" [ref]="composerRef">
108+
<ngt-render-pass *args="[scene(), camera()]" [attach]="passAttach" />
109+
<ngt-shader-pass
110+
*args="[RGBShiftShader]"
111+
[attach]="passAttach"
112+
(afterAttach)="$event.node.uniforms['amount'].value = 0.0015"
113+
/>
114+
<ngt-shader-pass *args="[GammaCorrectionShader]" [attach]="passAttach" />
115+
<ngt-unreal-bloom-pass *args="[size().width / size().height, 0.2, 0.8, 0]" [attach]="passAttach" />
116+
</ngt-effect-composer>
117+
118+
<ngts-orbit-controls
119+
[maxPolarAngle]="Math.PI / 2.1"
120+
[minPolarAngle]="Math.PI / 3"
121+
[minAzimuthAngle]="-Math.PI / 2"
122+
[maxAzimuthAngle]="Math.PI / 2"
123+
/>
124+
`,
125+
imports: [NgtArgs, NgtsOrbitControls, NgIf],
126+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
127+
})
128+
export class VaporwareScene {
129+
Math = Math;
130+
RGBShiftShader = RGBShiftShader;
131+
GammaCorrectionShader = GammaCorrectionShader;
132+
133+
// injectNgtStore returns the Canvas Store that keeps all the data to the current Scene Graph under ngt-canvas
134+
private store = injectNgtStore();
135+
gl = this.store.select('gl');
136+
scene = this.store.select('scene');
137+
camera = this.store.select('camera');
138+
size = this.store.select('size');
139+
140+
// injectNgtRef returns a NgtInjectedRef, which is similar to ElementRef but is enhanced with Signal to track some data changes.
141+
composerRef = injectNgtRef<EffectComposer>();
142+
geometryRef = injectNgtRef<PlaneGeometry>();
143+
144+
// Angular Three supports using ViewChild to get the instaces of the THREE objects via ElementRef
145+
@ViewChild('frontPlane') frontPlane?: ElementRef<Mesh>;
146+
@ViewChild('backPlane') backPlane?: ElementRef<Mesh>;
147+
148+
// most Angular Three's custom inject fns accept Functions as arguments so that consumers can pass in Signals
149+
// and the internal will react to changes.
150+
textures = injectNgtsTextureLoader(() => ({
151+
grid: 'assets/grid.png',
152+
displacement: 'assets/displacement.png',
153+
metalness: 'assets/metalness.png',
154+
}));
155+
156+
// [attach] can accept an AttachFunction.
157+
passAttach = createAttachFunction<EffectComposer, Pass>(({ parent, child }) => {
158+
parent.addPass(child);
159+
// optionally returns a clean up function that will get called when the child is destroyed
160+
return () => parent.removePass(child);
161+
});
162+
163+
constructor() {
164+
effect(() => {
165+
const [size, composer] = [this.size(), this.composerRef.nativeElement];
166+
if (!composer) return;
167+
composer.setSize(size.width, size.height);
168+
});
169+
170+
// injectBeforeRender allows consumers to hook into Animation Loop. This runs outside of Zone
171+
injectBeforeRender(({ clock }) => {
172+
const [frontPlane, backPlane] = [this.frontPlane?.nativeElement, this.backPlane?.nativeElement];
173+
if (frontPlane && backPlane) {
174+
const elapsedTime = clock.getElapsedTime();
175+
frontPlane.position.z = (elapsedTime * 0.15) % 2;
176+
backPlane.position.z = ((elapsedTime * 0.15) % 2) - 2;
177+
}
178+
});
179+
180+
// injectBeforeRender can also accept a priority option. When priority differs between different
181+
// beforeRender callbacks, Angular Three opts out off automatic rendering. In this case,
182+
// the EffectComposer is responsible for rendering our scene with composer.render()
183+
injectBeforeRender(
184+
({ delta }) => {
185+
const composer = this.composerRef.nativeElement;
186+
if (!composer) return;
187+
composer.render(delta);
188+
},
189+
{ priority: 1 },
190+
);
191+
}
192+
}
722 Bytes
Loading

apps/sandbox/src/assets/grid.png

26 KB
Loading

apps/sandbox/src/assets/metalness.png

665 Bytes
Loading

apps/sandbox/src/styles.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
box-sizing: border-box;
55
}
66

7+
:root {
8+
--background: white;
9+
}
10+
711
html,
812
body {
913
width: 100%;
1014
height: 100%;
1115
margin: 0;
1216
padding: 0;
13-
background-color: #272727;
1417
-webkit-touch-callout: none;
1518
-webkit-user-select: none;
1619
-khtml-user-select: none;
@@ -32,3 +35,9 @@ body {
3235
color: black;
3336
-webkit-font-smoothing: antialiased;
3437
}
38+
39+
button.cycle {
40+
position: absolute;
41+
top: 1rem;
42+
left: 1rem;
43+
}

libs/core/src/lib/renderer/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ class NgtRenderer implements Renderer2 {
392392
}
393393

394394
setProperty(el: NgtRendererNode, name: string, value: any): void {
395+
// TODO: should we support ref value
396+
395397
const rS = el.__ngt_renderer__;
396398
if (rS[NgtRendererClassId.type] === 'compound') {
397399
// we don't have the compound instance yet

libs/core/src/lib/renderer/store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ export class NgtRendererStore {
260260
) {
261261
value = compound[NgtCompoundClassId.props][name];
262262
}
263+
263264
applyProps(node, { [name]: value });
264265
this.updateNativeProps(node, name, value);
265266
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"scripts": {
66
"release": "dotenv release-it --",
77
"storybook": "nx storybook soba",
8+
"sandbox": "nx serve sandbox",
9+
"example": "nx serve examples",
810
"generate-soba": "node tools/scripts/generate-soba-json.mjs",
911
"publish": "nx run-many --target=publish --projects=core,soba,postprocessing,cannon --parallel=false",
1012
"publish:beta": "nx run-many --target=publish --projects=core,soba,postprocessing,cannon --parallel=false --tag=beta"

0 commit comments

Comments
 (0)