Skip to content

Commit 8f7b3cc

Browse files
committed
docs: inverted stencil buffer example from r3f examples
1 parent a60e3b7 commit 8f7b3cc

File tree

9 files changed

+372
-2
lines changed

9 files changed

+372
-2
lines changed

apps/kitchen-sink/public/angular.glb

5.54 KB
Binary file not shown.

apps/kitchen-sink/src/app/cannon/cube-heap/experience.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { NgtArgs, injectBeforeRender } from 'angular-three';
1515
import { NgtcPhysics } from 'angular-three-cannon';
1616
import { NgtcBodyPublicApi, injectBox, injectPlane, injectSphere } from 'angular-three-cannon/body';
1717
import { Color, InstancedMesh, Mesh } from 'three';
18-
import niceColors from '../colors';
18+
import niceColors from '../../colors';
1919
import { shape } from './state';
2020

2121
@Component({

apps/kitchen-sink/src/app/cannon/kinematic-cube/experience.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { NgtcPhysics } from 'angular-three-cannon';
1313
import { injectBox, injectPlane, injectSphere } from 'angular-three-cannon/body';
1414
import { NgtcDebug } from 'angular-three-cannon/debug';
1515
import { Color, InstancedMesh, Mesh } from 'three';
16-
import niceColors from '../colors';
16+
import niceColors from '../../colors';
1717

1818
@Component({
1919
selector: 'app-plane',
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
CUSTOM_ELEMENTS_SCHEMA,
5+
effect,
6+
ElementRef,
7+
viewChild,
8+
} from '@angular/core';
9+
import { extend, NgtArgs } from 'angular-three';
10+
import { NgtsCameraControls } from 'angular-three-soba/controls';
11+
import { shaderMaterial } from 'angular-three-soba/vanilla-exports';
12+
import { BoxGeometry, Color, InstancedMesh, Object3D, Vector3 } from 'three';
13+
import niceColors from '../../colors';
14+
15+
const MeshEdgesMaterial = shaderMaterial(
16+
{
17+
color: new Color('white'),
18+
size: new Vector3(1, 1, 1),
19+
thickness: 0.01,
20+
smoothness: 0.2,
21+
},
22+
/* language=glsl glsl */ `varying vec3 vPosition;
23+
void main() {
24+
vPosition = position;
25+
gl_Position = projectionMatrix * viewMatrix * instanceMatrix * vec4(position, 1.0);
26+
}`,
27+
/* language=glsl glsl*/ `varying vec3 vPosition;
28+
uniform vec3 size;
29+
uniform vec3 color;
30+
uniform float thickness;
31+
uniform float smoothness;
32+
void main() {
33+
vec3 d = abs(vPosition) - (size * 0.5);
34+
float a = smoothstep(thickness, thickness + smoothness, min(min(length(d.xy), length(d.yz)), length(d.xz)));
35+
gl_FragColor = vec4(color, 1.0 - a);
36+
}`,
37+
);
38+
39+
@Component({
40+
selector: 'app-boxes',
41+
standalone: true,
42+
template: `
43+
<ngt-group>
44+
<ngt-instanced-mesh #instances *args="[undefined, undefined, length]">
45+
<ngt-box-geometry #boxGeometry *args="[0.15, 0.15, 0.15]">
46+
<ngt-instanced-buffer-attribute attach="attributes.color" *args="[randomColors, 3]" />
47+
</ngt-box-geometry>
48+
<ngt-mesh-lambert-material [vertexColors]="true" [toneMapped]="false" />
49+
</ngt-instanced-mesh>
50+
<ngt-instanced-mesh #outlines *args="[undefined, undefined, length]">
51+
<ngt-mesh-edges-material
52+
[transparent]="true"
53+
[polygonOffset]="true"
54+
[polygonOffsetFactor]="-10"
55+
[size]="[0.15, 0.15, 0.15]"
56+
color="black"
57+
[thickness]="0.001"
58+
[smoothness]="0.005"
59+
/>
60+
</ngt-instanced-mesh>
61+
</ngt-group>
62+
`,
63+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
64+
changeDetection: ChangeDetectionStrategy.OnPush,
65+
imports: [NgtArgs],
66+
})
67+
export class Boxes {
68+
protected length = 100_000;
69+
70+
private c = new Color();
71+
protected randomColors = new Float32Array(
72+
Array.from({ length: this.length }, () => this.c.set(niceColors[Math.floor(Math.random() * 5)]).toArray()).flat(),
73+
);
74+
75+
private instancesRef = viewChild<ElementRef<InstancedMesh>>('instances');
76+
private outlinesRef = viewChild<ElementRef<InstancedMesh>>('outlines');
77+
private boxGeometryRef = viewChild<ElementRef<BoxGeometry>>('boxGeometry');
78+
79+
constructor() {
80+
extend({ MeshEdgesMaterial });
81+
82+
const o = new Object3D();
83+
84+
effect(() => {
85+
const [instances, outlines, boxGeometry] = [
86+
this.instancesRef()?.nativeElement,
87+
this.outlinesRef()?.nativeElement,
88+
this.boxGeometryRef()?.nativeElement,
89+
];
90+
if (!instances || !outlines || !boxGeometry) return;
91+
92+
let i = 0;
93+
const root = Math.round(Math.pow(this.length, 1 / 3));
94+
const halfRoot = root / 2;
95+
for (let x = 0; x < root; x++)
96+
for (let y = 0; y < root; y++)
97+
for (let z = 0; z < root; z++) {
98+
const id = i++;
99+
o.rotation.set(Math.random(), Math.random(), Math.random());
100+
o.position.set(halfRoot - x + Math.random(), halfRoot - y + Math.random(), halfRoot - z + Math.random());
101+
o.updateMatrix();
102+
instances.setMatrixAt(id, o.matrix);
103+
}
104+
instances.instanceMatrix.needsUpdate = true;
105+
// Re-use geometry + instance matrix
106+
outlines.geometry = boxGeometry;
107+
outlines.instanceMatrix = instances.instanceMatrix;
108+
});
109+
}
110+
}
111+
112+
@Component({
113+
standalone: true,
114+
template: `
115+
<ngt-color attach="background" *args="['#e0e0e0']" />
116+
117+
<ngt-ambient-light [intensity]="0.85" />
118+
<ngt-directional-light [position]="[150, 150, 150]" [intensity]="1" />
119+
120+
<app-boxes />
121+
122+
<ngts-camera-controls />
123+
`,
124+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
125+
changeDetection: ChangeDetectionStrategy.OnPush,
126+
host: { class: 'instances-soba-experience' },
127+
imports: [NgtsCameraControls, Boxes, NgtArgs],
128+
})
129+
export class Experience {}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
2+
import { NgtCanvas } from 'angular-three';
3+
import { Experience } from './exprience';
4+
5+
@Component({
6+
standalone: true,
7+
template: `
8+
<ngt-canvas [sceneGraph]="scene" [camera]="{ position: [0, 0, 0.01] }" />
9+
`,
10+
changeDetection: ChangeDetectionStrategy.OnPush,
11+
host: { class: 'instances-soba' },
12+
imports: [NgtCanvas],
13+
})
14+
export default class Instances {
15+
protected scene = Experience;
16+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
computed,
5+
CUSTOM_ELEMENTS_SCHEMA,
6+
input,
7+
signal,
8+
Signal,
9+
} from '@angular/core';
10+
import { NgtArgs, NgtEuler, NgtVector3 } from 'angular-three';
11+
import { NgtsRoundedBox } from 'angular-three-soba/abstractions';
12+
import { NgtsOrbitControls, NgtsPivotControls } from 'angular-three-soba/controls';
13+
import { injectGLTF } from 'angular-three-soba/loaders';
14+
import { injectMask, NgtsBounds, NgtsEnvironment, NgtsFloat, NgtsMask } from 'angular-three-soba/staging';
15+
import { ColorRepresentation, Mesh, MeshPhongMaterial, MeshStandardMaterial } from 'three';
16+
import { GLTF } from 'three-stdlib';
17+
18+
export const invert = signal(false);
19+
20+
@Component({
21+
selector: 'app-frame',
22+
standalone: true,
23+
template: `
24+
<ngt-mesh [position]="position()">
25+
<ngt-ring-geometry *args="[1.095, 1.155, 64]" />
26+
<ngt-mesh-phong-material color="black" />
27+
</ngt-mesh>
28+
`,
29+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
30+
changeDetection: ChangeDetectionStrategy.OnPush,
31+
imports: [NgtArgs],
32+
})
33+
export class Frame {
34+
position = input<NgtVector3>([0, 0, 0]);
35+
}
36+
37+
@Component({
38+
selector: 'app-circular-mask',
39+
standalone: true,
40+
template: `
41+
<ngt-group [position]="position()">
42+
<ngts-pivot-controls
43+
[options]="{ offset: [0, 0, 1], activeAxes: [true, true, false], disableRotations: true, depthTest: false }"
44+
>
45+
<app-frame [position]="[0, 0, 1]" />
46+
<ngts-mask id="1" [options]="{ position: [0, 0, 0.95] }">
47+
<ngt-circle-geometry *args="[1.15, 64]" />
48+
</ngts-mask>
49+
</ngts-pivot-controls>
50+
</ngt-group>
51+
`,
52+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
53+
changeDetection: ChangeDetectionStrategy.OnPush,
54+
imports: [NgtsPivotControls, Frame, NgtsMask, NgtArgs],
55+
})
56+
export class CircularMask {
57+
position = input<NgtVector3>([0, 0, 0]);
58+
}
59+
60+
@Component({
61+
selector: 'app-box',
62+
standalone: true,
63+
template: `
64+
<ngts-rounded-box
65+
[options]="{
66+
width: width(),
67+
height: height(),
68+
depth: depth(),
69+
radius: 0.05,
70+
smoothness: 4,
71+
position: position(),
72+
rotation: rotation(),
73+
}"
74+
>
75+
<ngt-mesh-phong-material [color]="color()" />
76+
</ngts-rounded-box>
77+
`,
78+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
79+
changeDetection: ChangeDetectionStrategy.OnPush,
80+
imports: [NgtsRoundedBox],
81+
})
82+
export class Box {
83+
width = input.required<number>();
84+
height = input.required<number>();
85+
depth = input.required<number>();
86+
color = input.required<ColorRepresentation>();
87+
position = input<NgtVector3>([0, 0, 0]);
88+
rotation = input<NgtEuler>([0, 0, 0]);
89+
}
90+
91+
type AngularGLTF = GLTF & {
92+
nodes: { Curve: Mesh; Curve001: Mesh; Curve002: Mesh; Curve003: Mesh };
93+
materials: { SVGMat: MeshStandardMaterial };
94+
};
95+
96+
@Component({
97+
selector: 'app-angular',
98+
standalone: true,
99+
template: `
100+
@if (gltf(); as gltf) {
101+
<ngt-group [dispose]="null" [scale]="scale()" [position]="[-2.5, -3, 0]" [rotation]="[Math.PI / 2, 0, 0]">
102+
<ngt-mesh
103+
[castShadow]="true"
104+
[receiveShadow]="true"
105+
[geometry]="gltf.nodes.Curve.geometry"
106+
[material]="material()"
107+
/>
108+
<ngt-mesh
109+
[castShadow]="true"
110+
[receiveShadow]="true"
111+
[geometry]="gltf.nodes.Curve001.geometry"
112+
[material]="material()"
113+
/>
114+
<ngt-mesh
115+
[castShadow]="true"
116+
[receiveShadow]="true"
117+
[geometry]="gltf.nodes.Curve002.geometry"
118+
[material]="material()"
119+
/>
120+
<ngt-mesh
121+
[castShadow]="true"
122+
[receiveShadow]="true"
123+
[geometry]="gltf.nodes.Curve003.geometry"
124+
[material]="material()"
125+
/>
126+
</ngt-group>
127+
}
128+
`,
129+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
130+
changeDetection: ChangeDetectionStrategy.OnPush,
131+
})
132+
export class Angular {
133+
protected readonly Math = Math;
134+
135+
invert = input(false);
136+
scale = input(1);
137+
138+
protected gltf = injectGLTF(() => './angular.glb') as Signal<AngularGLTF | null>;
139+
protected stencilParameters = injectMask(() => 1, this.invert);
140+
141+
protected material = computed(() => {
142+
const stencilParameters = this.stencilParameters();
143+
return new MeshPhongMaterial({ color: '#E72BAA', ...stencilParameters });
144+
});
145+
}
146+
147+
@Component({
148+
standalone: true,
149+
template: `
150+
<ngt-color attach="background" *args="['#e0e0e0']" />
151+
152+
<ngt-directional-light [position]="[1, 2, 1.5]" [intensity]="Math.PI * 0.5" [castShadow]="true" />
153+
<ngt-hemisphere-light [intensity]="Math.PI * 1.5" groundColor="red" />
154+
155+
<app-circular-mask />
156+
<app-circular-mask [position]="[2, 0, 0]" />
157+
<ngts-bounds [options]="{ fit: true, clip: true, observe: true }">
158+
<ngts-float [options]="{ floatIntensity: 4, rotationIntensity: 0, speed: 4 }">
159+
<app-angular [invert]="invert()" [scale]="20" />
160+
</ngts-float>
161+
<app-box
162+
color="#EAC435"
163+
[width]="1"
164+
[height]="5"
165+
[depth]="1"
166+
[rotation]="[0, Math.PI / 4, 0]"
167+
[position]="[0, 0, -2]"
168+
/>
169+
<app-box color="#03CEA4" [width]="2" [height]="2" [depth]="2" [position]="[-2, 0, -2]" />
170+
<app-box color="#FB4D3D" [width]="2" [height]="2" [depth]="2" [position]="[2, 0, -2]" />
171+
</ngts-bounds>
172+
173+
<ngts-environment [options]="{ preset: 'city' }" />
174+
<ngts-orbit-controls [options]="{ makeDefault: true }" />
175+
`,
176+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
177+
changeDetection: ChangeDetectionStrategy.OnPush,
178+
host: { class: 'inverted-stencil-buffer-soba-experience' },
179+
imports: [CircularMask, NgtsBounds, NgtsFloat, Angular, Box, NgtsEnvironment, NgtsOrbitControls, NgtArgs],
180+
})
181+
export class Experience {
182+
protected readonly Math = Math;
183+
protected invert = invert;
184+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
2+
import { NgtCanvas } from 'angular-three';
3+
import { Experience, invert } from './experience';
4+
5+
@Component({
6+
standalone: true,
7+
template: `
8+
<ngt-canvas [sceneGraph]="scene" [shadows]="true" [gl]="{ stencil: true }" />
9+
<label class="absolute top-2 right-2 font-mono flex gap-2 items-center">
10+
<input type="checkbox" [value]="invert()" (change)="onChange($event)" />
11+
invert
12+
</label>
13+
<a
14+
class="absolute top-2 left-2 font-mono underline"
15+
href="https://pmndrs.github.io/examples/demos/inverted-stencil-buffer"
16+
target="_blank"
17+
>
18+
credit: R3F Inverted Stencil Buffer
19+
</a>
20+
`,
21+
host: { class: 'inverted-stencil-buffer-soba' },
22+
changeDetection: ChangeDetectionStrategy.OnPush,
23+
imports: [NgtCanvas],
24+
})
25+
export default class InvertedStencilBuffer {
26+
protected scene = Experience;
27+
protected invert = invert;
28+
29+
onChange(event: Event) {
30+
const target = event.target as HTMLInputElement;
31+
this.invert.set(target.checked);
32+
}
33+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ const routes: Routes = [
4545
path: 'porsche',
4646
loadComponent: () => import('./porsche/porsche'),
4747
},
48+
{
49+
path: 'instances',
50+
loadComponent: () => import('./instances/instances'),
51+
},
52+
{
53+
path: 'inverted-stencil-buffer',
54+
loadComponent: () => import('./inverted-stencil-buffer/inverted-stencil-buffer'),
55+
},
4856
{
4957
path: '',
5058
redirectTo: 'stars',

0 commit comments

Comments
 (0)