|
1 | | -import { NgIf } from '@angular/common'; |
2 | | -import { CUSTOM_ELEMENTS_SCHEMA, Component, Input, computed, signal } from '@angular/core'; |
3 | | -import { Triplet } from '@pmndrs/cannon-worker-api'; |
4 | | -import { NgtArgs, NgtCanvas, NgtKey, extend } from 'angular-three'; |
5 | | -import { NgtcPhysics } from 'angular-three-cannon'; |
6 | | -import { NgtcDebug } from 'angular-three-cannon/debug'; |
7 | | -import { injectBox, injectPlane } from 'angular-three-cannon/services'; |
8 | | -import { NgtpBloom, NgtpEffectComposer } from 'angular-three-postprocessing'; |
9 | | -import { NgtsGrid } from 'angular-three-soba/abstractions'; |
10 | | -import { NgtsOrbitControls } from 'angular-three-soba/controls'; |
11 | | -import { NgtsLoader, injectNgtsGLTFLoader } from 'angular-three-soba/loaders'; |
12 | | -import { injectNgtsAnimations } from 'angular-three-soba/misc'; |
| 1 | +import { Component, Type, ViewChild, ViewContainerRef, effect, signal } from '@angular/core'; |
| 2 | +import { NgtCanvas, NgtKey, extend } from 'angular-three'; |
| 3 | +import { NgtsLoader } from 'angular-three-soba/loaders'; |
13 | 4 | import * as THREE from 'three'; |
14 | | -import { SkyDivingScene } from './skydiving/scene.component'; |
15 | | -import { VaporwareScene } from './vaporware/scene.component'; |
| 5 | +import { AviatorCanvas } from './aviator/canvas.component'; |
| 6 | +import { BotCanvas } from './bot/canvas.component'; |
| 7 | +import { CannonCanvas } from './cannon/canvas.component'; |
| 8 | +import { SkyDivingCanvas } from './skydiving/canvas.component'; |
| 9 | +import { VaporwareCanvas } from './vaporware/canvas.component'; |
16 | 10 |
|
17 | 11 | extend(THREE); |
18 | 12 |
|
19 | | -@Component({ |
20 | | - selector: 'app-plane', |
21 | | - standalone: true, |
22 | | - template: ` |
23 | | - <ngt-mesh [ref]="planeApi.ref" [receiveShadow]="true"> |
24 | | - <ngt-plane-geometry *args="[1000, 1000]" /> |
25 | | - <ngt-mesh-standard-material color="#171717" /> |
26 | | - </ngt-mesh> |
27 | | - `, |
28 | | - imports: [NgtArgs], |
29 | | - schemas: [CUSTOM_ELEMENTS_SCHEMA], |
30 | | -}) |
31 | | -export class Plane { |
32 | | - Math = Math; |
33 | | - @Input() position: Triplet = [0, 0, 0]; |
34 | | - |
35 | | - planeApi = injectPlane(() => ({ mass: 0, position: this.position, args: [1000, 1000] })); |
36 | | -} |
37 | | - |
38 | | -@Component({ |
39 | | - selector: 'app-box', |
40 | | - standalone: true, |
41 | | - template: ` |
42 | | - <ngt-mesh [ref]="boxApi.ref" [receiveShadow]="true" [castShadow]="true"> |
43 | | - <ngt-box-geometry *args="[2, 2, 2]" /> |
44 | | - <ngt-mesh-standard-material [roughness]="0.5" color="#575757" /> |
45 | | - </ngt-mesh> |
46 | | - `, |
47 | | - imports: [NgtArgs], |
48 | | - schemas: [CUSTOM_ELEMENTS_SCHEMA], |
49 | | -}) |
50 | | -export class Box { |
51 | | - @Input() position: Triplet = [0, 0, 0]; |
52 | | - |
53 | | - boxApi = injectBox(() => ({ mass: 10000, position: this.position, args: [2, 2, 2] })); |
54 | | -} |
55 | | - |
56 | | -@Component({ |
57 | | - standalone: true, |
58 | | - template: ` |
59 | | - <ngt-point-light [position]="[-10, -10, 30]" [intensity]="0.25 * Math.PI" /> |
60 | | - <ngt-spot-light |
61 | | - [intensity]="0.3 * Math.PI" |
62 | | - [position]="[30, 30, 50]" |
63 | | - [angle]="0.2" |
64 | | - [penumbra]="1" |
65 | | - [castShadow]="true" |
66 | | - /> |
67 | | - <ngtc-physics [gravity]="[0, 0, -25]" [iterations]="10"> |
68 | | - <ngtc-debug color="white" [disabled]="true"> |
69 | | - <app-plane [position]="[0, 0, -10]" /> |
70 | | - <app-plane *ngIf="showPlane()" /> |
71 | | - <app-box [position]="[1, 0, 1]" /> |
72 | | - <app-box [position]="[2, 1, 5]" /> |
73 | | - <app-box [position]="[0, 0, 6]" /> |
74 | | - <app-box [position]="[-1, 1, 8]" /> |
75 | | - <app-box [position]="[-2, 2, 13]" /> |
76 | | - <app-box [position]="[2, -1, 13]" /> |
77 | | - <app-box *ngIf="!showPlane()" [position]="[0.5, 1.0, 20]" /> |
78 | | - </ngtc-debug> |
79 | | - </ngtc-physics> |
80 | | - `, |
81 | | - imports: [Box, Plane, NgtcPhysics, NgIf, NgtcDebug], |
82 | | - schemas: [CUSTOM_ELEMENTS_SCHEMA], |
83 | | -}) |
84 | | -export class CannonScene { |
85 | | - Math = Math; |
86 | | - showPlane = signal(true); |
87 | | - |
88 | | - ngOnInit() { |
89 | | - setTimeout(() => { |
90 | | - this.showPlane.set(false); |
91 | | - }, 5000); |
92 | | - } |
93 | | -} |
94 | | - |
95 | | -@Component({ |
96 | | - standalone: true, |
97 | | - template: ` |
98 | | - <ngt-ambient-light /> |
99 | | - <ngt-point-light /> |
100 | | - <ngt-primitive *args="[bot()]" [ref]="animations.ref" [position]="[0, -1, 0]" /> |
101 | | - <ngts-orbit-controls /> |
102 | | - <ngts-grid [position]="[0, -1, 0]" [args]="[10, 10]" /> |
103 | | -
|
104 | | - <ngtp-effect-composer> |
105 | | - <ngtp-bloom [intensity]="1.5" /> |
106 | | - </ngtp-effect-composer> |
107 | | - `, |
108 | | - imports: [NgtArgs, NgtsOrbitControls, NgtsGrid, NgtpEffectComposer, NgtpBloom], |
109 | | - schemas: [CUSTOM_ELEMENTS_SCHEMA], |
110 | | -}) |
111 | | -export class Scene { |
112 | | - Math = Math; |
113 | | - |
114 | | - active = signal(false); |
115 | | - hover = signal(false); |
116 | | - |
117 | | - private yBotGltf = injectNgtsGLTFLoader(() => 'assets/ybot.glb'); |
118 | | - |
119 | | - animations = injectNgtsAnimations(() => this.yBotGltf()?.animations || []); |
120 | | - bot = computed(() => { |
121 | | - const gltf = this.yBotGltf(); |
122 | | - if (gltf) { |
123 | | - return gltf.scene; |
124 | | - } |
125 | | - return null; |
126 | | - }); |
127 | | -} |
128 | | - |
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 | | - }, |
| 13 | +const canvases = { |
| 14 | + bot: BotCanvas, |
| 15 | + skydiving: SkyDivingCanvas, |
| 16 | + vaporware: VaporwareCanvas, |
| 17 | + aviator: AviatorCanvas, |
| 18 | + cannon: CannonCanvas, |
150 | 19 | } as const; |
151 | | -const availableScenes = Object.keys(scenes) as [keyof typeof scenes]; |
152 | 20 |
|
153 | | -type AvailableScene = (typeof availableScenes)[number]; |
| 21 | +const availableCanvases = Object.keys(canvases) as [keyof typeof canvases]; |
| 22 | +type AvailableCanvas = (typeof availableCanvases)[number]; |
154 | 23 |
|
155 | 24 | @Component({ |
156 | 25 | standalone: true, |
157 | 26 | imports: [NgtCanvas, NgtKey, NgtsLoader], |
158 | 27 | selector: 'sandbox-root', |
159 | 28 | template: ` |
160 | | - <ngt-canvas |
161 | | - *key="scene" |
162 | | - [sceneGraph]="currentScene.scene" |
163 | | - [camera]="currentScene.cameraOptions" |
164 | | - [gl]="currentScene.glOptions" |
165 | | - [shadows]="true" |
166 | | - /> |
| 29 | + <ng-container #anchor /> |
167 | 30 | <ngts-loader /> |
168 | | - <button class="cycle" (click)="cycleScene()">Current scene: {{ scene }}</button> |
| 31 | + <button class="cycle" (click)="cycleCanvas()">Current canvas: {{ canvas() }}</button> |
169 | 32 | `, |
170 | 33 | host: { |
171 | 34 | '[style.--background]': 'background', |
172 | | - style: 'background-color: var(--background); display: block; height: 100%; width: 100%', |
| 35 | + style: 'background: var(--background); display: block; height: 100%; width: 100%', |
173 | 36 | }, |
174 | 37 | }) |
175 | 38 | export class AppComponent { |
176 | | - scene: AvailableScene = 'vaporware'; |
| 39 | + canvas = signal<AvailableCanvas>('aviator'); |
177 | 40 |
|
178 | | - get currentScene() { |
179 | | - return scenes[this.scene]; |
| 41 | + @ViewChild('anchor', { static: true, read: ViewContainerRef }) vcr!: ViewContainerRef; |
| 42 | + |
| 43 | + constructor() { |
| 44 | + effect((onCleanup) => { |
| 45 | + const ref = this.vcr.createComponent(canvases[this.canvas()] as Type<unknown>); |
| 46 | + onCleanup(ref.destroy.bind(ref)); |
| 47 | + }); |
180 | 48 | } |
181 | 49 |
|
182 | | - get background() { |
183 | | - if (this.scene === 'skydiving') return '#272727'; |
184 | | - if (this.scene === 'vaporware') return 'black'; |
185 | | - return 'white'; |
| 50 | + cycleCanvas() { |
| 51 | + const index = availableCanvases.indexOf(this.canvas()); |
| 52 | + this.canvas.set(availableCanvases[(index + 1) % availableCanvases.length]); |
186 | 53 | } |
187 | 54 |
|
188 | | - cycleScene() { |
189 | | - const index = availableScenes.indexOf(this.scene); |
190 | | - this.scene = availableScenes[(index + 1) % availableScenes.length]; |
| 55 | + get background() { |
| 56 | + if (this.canvas() === 'skydiving') return '#272727'; |
| 57 | + if (this.canvas() === 'vaporware') return 'black'; |
| 58 | + if (this.canvas() === 'aviator') return 'linear-gradient(#101232, #101560)'; |
| 59 | + return 'white'; |
191 | 60 | } |
192 | 61 | } |
0 commit comments