Skip to content

Commit 85569b0

Browse files
committed
docs: add instanced vertex colors
1 parent 5758b6d commit 85569b0

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
CUSTOM_ELEMENTS_SCHEMA,
5+
ElementRef,
6+
signal,
7+
untracked,
8+
viewChild,
9+
} from '@angular/core';
10+
import { injectBeforeRender, NgtArgs, NgtThreeEvent } from 'angular-three';
11+
import { NgtpBloom, NgtpEffectComposer } from 'angular-three-postprocessing';
12+
import { NgtpN8AO } from 'angular-three-postprocessing/n8ao';
13+
import { Color, InstancedMesh, Object3D } from 'three';
14+
import niceColors from '../../colors';
15+
16+
const tempObject = new Object3D();
17+
const tempColor = new Color();
18+
const data = Array.from({ length: 1000 }, () => ({ color: niceColors[Math.floor(Math.random() * 5)], scale: 1 }));
19+
20+
@Component({
21+
selector: 'app-boxes',
22+
standalone: true,
23+
template: `
24+
<ngt-instanced-mesh
25+
#mesh
26+
*args="[undefined, undefined, 1000]"
27+
(pointermove)="onPointerMove($any($event))"
28+
(pointerout)="onPointerOut($any($event))"
29+
>
30+
<ngt-box-geometry *args="[0.6, 0.6, 0.6]">
31+
<ngt-instanced-buffer-attribute attach="attributes.color" *args="[colors, 3]" />
32+
</ngt-box-geometry>
33+
<ngt-mesh-basic-material [vertexColors]="true" [toneMapped]="false" />
34+
</ngt-instanced-mesh>
35+
`,
36+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
37+
changeDetection: ChangeDetectionStrategy.OnPush,
38+
imports: [NgtArgs],
39+
})
40+
export class Boxes {
41+
private meshRef = viewChild<ElementRef<InstancedMesh>>('mesh');
42+
43+
protected colors = Float32Array.from(
44+
Array.from({ length: 1000 }, (_, i) => i).flatMap((_, i) => tempColor.set(data[i].color).toArray()),
45+
);
46+
47+
protected hovered = signal<number | undefined>(undefined);
48+
protected prev?: number;
49+
50+
constructor() {
51+
injectBeforeRender(({ clock }) => {
52+
const instanced = this.meshRef()?.nativeElement;
53+
if (!instanced) return;
54+
55+
const time = clock.getElapsedTime();
56+
instanced.rotation.x = Math.sin(time / 4);
57+
instanced.rotation.y = Math.sin(time / 2);
58+
59+
const hovered = this.hovered();
60+
61+
let i = 0;
62+
for (let x = 0; x < 10; x++) {
63+
for (let y = 0; y < 10; y++) {
64+
for (let z = 0; z < 10; z++) {
65+
const id = i++;
66+
tempObject.position.set(5 - x, 5 - y, 5 - z);
67+
tempObject.rotation.y = Math.sin(x / 4 + time) + Math.sin(y / 4 + time) + Math.sin(z / 4 + time);
68+
tempObject.rotation.z = tempObject.rotation.y * 2;
69+
70+
if (hovered !== this.prev) {
71+
(id === hovered ? tempColor.setRGB(10, 10, 10) : tempColor.set(data[id].color)).toArray(
72+
this.colors,
73+
id * 3,
74+
);
75+
instanced.geometry.attributes['color'].needsUpdate = true;
76+
}
77+
78+
tempObject.updateMatrix();
79+
instanced.setMatrixAt(id, tempObject.matrix);
80+
}
81+
}
82+
}
83+
84+
instanced.instanceMatrix.needsUpdate = true;
85+
});
86+
}
87+
88+
onPointerMove(event: NgtThreeEvent<PointerEvent>) {
89+
event.stopPropagation();
90+
this.prev = untracked(this.hovered);
91+
this.hovered.set(event.instanceId);
92+
}
93+
94+
onPointerOut(event: NgtThreeEvent<PointerEvent>) {
95+
event.stopPropagation();
96+
this.prev = untracked(this.hovered);
97+
this.hovered.set(undefined);
98+
}
99+
}
100+
101+
@Component({
102+
standalone: true,
103+
template: `
104+
<ngt-color attach="background" *args="['#282828']" />
105+
<app-boxes />
106+
<ngtp-effect-composer [options]="{ enableNormalPass: false }">
107+
<ngtp-n8ao [options]="{ aoRadius: 0.5, intensity: Math.PI }" />
108+
<ngtp-bloom [options]="{ luminanceThreshold: 1, intensity: 0.5 * Math.PI, levels: 9, mipmapBlur: true }" />
109+
</ngtp-effect-composer>
110+
`,
111+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
112+
changeDetection: ChangeDetectionStrategy.OnPush,
113+
host: { class: 'instanced-vertex-colors-soba-experience' },
114+
imports: [NgtArgs, Boxes, NgtpEffectComposer, NgtpN8AO, NgtpBloom],
115+
})
116+
export class Experience {
117+
protected readonly Math = Math;
118+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
2+
import { NgtCanvas } from 'angular-three';
3+
import { Experience } from './experience';
4+
5+
@Component({
6+
standalone: true,
7+
template: `
8+
<ngt-canvas
9+
[sceneGraph]="sceneGraph"
10+
[gl]="{ antialias: false }"
11+
[camera]="{ position: [0, 0, 15], near: 5, far: 20 }"
12+
/>
13+
`,
14+
changeDetection: ChangeDetectionStrategy.OnPush,
15+
imports: [NgtCanvas],
16+
host: { class: 'instanced-vertex-colors-soba' },
17+
})
18+
export default class InstancedVertexColors {
19+
protected sceneGraph = Experience;
20+
}

apps/kitchen-sink/src/app/soba/soba.routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ const routes: Routes = [
6565
path: 'bruno-simons-20k',
6666
loadComponent: () => import('./bruno-simons-20k/bruno-simons-20k'),
6767
},
68+
{
69+
path: 'instanced-vertex-colors',
70+
loadComponent: () => import('./instanced-vertex-colors/instanced-vertex-colors'),
71+
},
6872
{
6973
path: '',
7074
redirectTo: 'stars',

0 commit comments

Comments
 (0)