|
| 1 | +import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, viewChild } from '@angular/core'; |
| 2 | +import { extend, injectBeforeRender, NgtAfterAttach, NgtArgs } from 'angular-three'; |
| 3 | +import { NgtsOrbitControls } from 'angular-three-soba/controls'; |
| 4 | +import * as THREE from 'three/webgpu'; |
| 5 | + |
| 6 | +@Component({ |
| 7 | + selector: 'app-scene-graph', |
| 8 | + template: ` |
| 9 | + <ngt-color *args="['#c1c1c1']" attach="background" /> |
| 10 | +
|
| 11 | + <ngt-group #group static> |
| 12 | + @for (obj of objects; track $index) { |
| 13 | + <ngt-mesh |
| 14 | + [geometry]="obj.geometry" |
| 15 | + [frustumCulled]="false" |
| 16 | + [userData]="obj.userData" |
| 17 | + (attached)="onAttached($event)" |
| 18 | + > |
| 19 | + <ngt-mesh-toon-material [color]="obj.color" [side]="DoubleSide" /> |
| 20 | + </ngt-mesh> |
| 21 | + } |
| 22 | + </ngt-group> |
| 23 | +
|
| 24 | + <ngt-directional-light [intensity]="Math.PI" /> |
| 25 | +
|
| 26 | + <ngts-orbit-controls [options]="{ autoRotate: true, enableZoom: false, autoRotateSpeed: 1 }" /> |
| 27 | + `, |
| 28 | + imports: [NgtArgs, NgtsOrbitControls], |
| 29 | + changeDetection: ChangeDetectionStrategy.OnPush, |
| 30 | + schemas: [CUSTOM_ELEMENTS_SCHEMA], |
| 31 | +}) |
| 32 | +export class SceneGraph { |
| 33 | + protected readonly Math = Math; |
| 34 | + protected readonly DoubleSide = THREE.DoubleSide; |
| 35 | + |
| 36 | + protected readonly geometries = [ |
| 37 | + new THREE.ConeGeometry(1.0, 2.0, 3, 1), |
| 38 | + new THREE.BoxGeometry(2.0, 2.0, 2.0), |
| 39 | + new THREE.PlaneGeometry(2.0, 2, 1, 1), |
| 40 | + new THREE.CapsuleGeometry(), |
| 41 | + new THREE.CircleGeometry(1.0, 3), |
| 42 | + new THREE.CylinderGeometry(1.0, 1.0, 2.0, 3, 1), |
| 43 | + new THREE.DodecahedronGeometry(1.0, 0), |
| 44 | + new THREE.IcosahedronGeometry(1.0, 0), |
| 45 | + new THREE.OctahedronGeometry(1.0, 0), |
| 46 | + new THREE.PolyhedronGeometry([0, 0, 0], [0, 0, 0], 1, 0), |
| 47 | + new THREE.RingGeometry(1.0, 1.5, 3), |
| 48 | + new THREE.SphereGeometry(1.0, 3, 2), |
| 49 | + new THREE.TetrahedronGeometry(1.0, 0), |
| 50 | + new THREE.TorusGeometry(1.0, 0.5, 3, 3), |
| 51 | + new THREE.TorusKnotGeometry(1.0, 0.5, 20, 3, 1, 1), |
| 52 | + ]; |
| 53 | + |
| 54 | + protected readonly objects = Array.from({ length: 3000 }, (_, i) => { |
| 55 | + const color = Math.random() * 0xffffff; |
| 56 | + const geometry = this.geometries[i % this.geometries.length]; |
| 57 | + const rotationSpeed = this.randomizeRotationSpeed(new THREE.Euler()); |
| 58 | + |
| 59 | + return { |
| 60 | + color, |
| 61 | + geometry, |
| 62 | + userData: { rotationSpeed }, |
| 63 | + }; |
| 64 | + }); |
| 65 | + |
| 66 | + private groupRef = viewChild.required<ElementRef<THREE.Group>>('group'); |
| 67 | + |
| 68 | + private position = new THREE.Vector3(); |
| 69 | + private rotation = new THREE.Euler(); |
| 70 | + private quaternion = new THREE.Quaternion(); |
| 71 | + private scale = new THREE.Vector3(); |
| 72 | + |
| 73 | + constructor() { |
| 74 | + extend(THREE); |
| 75 | + |
| 76 | + injectBeforeRender(() => { |
| 77 | + const group = this.groupRef().nativeElement; |
| 78 | + for (const child of group.children) { |
| 79 | + const { rotationSpeed } = child.userData; |
| 80 | + child.rotation.set( |
| 81 | + child.rotation.x + rotationSpeed.x, |
| 82 | + child.rotation.y + rotationSpeed.y, |
| 83 | + child.rotation.z + rotationSpeed.z, |
| 84 | + ); |
| 85 | + } |
| 86 | + }); |
| 87 | + } |
| 88 | + |
| 89 | + protected onAttached(event: NgtAfterAttach<THREE.Mesh>) { |
| 90 | + this.randomizeMatrix(event.node.matrix); |
| 91 | + event.node.matrix.decompose(event.node.position, event.node.quaternion, event.node.scale); |
| 92 | + } |
| 93 | + |
| 94 | + private randomizeMatrix(matrix: THREE.Matrix4) { |
| 95 | + this.position.x = Math.random() * 80 - 40; |
| 96 | + this.position.y = Math.random() * 80 - 40; |
| 97 | + this.position.z = Math.random() * 80 - 40; |
| 98 | + this.rotation.x = Math.random() * 2 * Math.PI; |
| 99 | + this.rotation.y = Math.random() * 2 * Math.PI; |
| 100 | + this.rotation.z = Math.random() * 2 * Math.PI; |
| 101 | + this.quaternion.setFromEuler(this.rotation); |
| 102 | + const factorScale = 1; |
| 103 | + this.scale.x = this.scale.y = this.scale.z = 0.35 * factorScale + Math.random() * 0.5 * factorScale; |
| 104 | + return matrix.compose(this.position, this.quaternion, this.scale); |
| 105 | + } |
| 106 | + |
| 107 | + private randomizeRotationSpeed(rotation: THREE.Euler) { |
| 108 | + rotation.x = Math.random() * 0.05; |
| 109 | + rotation.y = Math.random() * 0.05; |
| 110 | + rotation.z = Math.random() * 0.05; |
| 111 | + return rotation; |
| 112 | + } |
| 113 | +} |
0 commit comments