|
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