Skip to content

Commit 549e49d

Browse files
committed
grid
1 parent c1ddf9a commit 549e49d

File tree

12 files changed

+426
-17
lines changed

12 files changed

+426
-17
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { Component, Input } from '@angular/core';
2+
import { NgtArgs, extend, injectBeforeRender, injectNgtRef, signalStore, type NgtMesh } from 'angular-three';
3+
import { GridMaterial, type NgtsGridMaterialState } from 'angular-three-soba/shaders';
4+
import * as THREE from 'three';
5+
import { Mesh, PlaneGeometry } from 'three';
6+
7+
extend({ GridMaterial, Mesh, PlaneGeometry });
8+
9+
export type NgtsGridState = {
10+
/** Default plane-geometry arguments */
11+
args?: ConstructorParameters<typeof THREE.PlaneGeometry>;
12+
} & NgtsGridMaterialState;
13+
14+
declare global {
15+
interface HTMLElementTagNameMap {
16+
/**
17+
* @extends ngt-mesh
18+
*/
19+
'ngts-grid': NgtsGridState & NgtMesh;
20+
}
21+
}
22+
23+
@Component({
24+
selector: 'ngts-grid',
25+
standalone: true,
26+
template: `
27+
<ngt-mesh ngtCompound [ref]="gridRef" [frustumCulled]="false">
28+
<ngt-plane-geometry *args="args()" />
29+
<ngt-grid-material
30+
[transparent]="true"
31+
[side]="side()"
32+
[cellSize]="cellSize()"
33+
[cellColor]="cellColor()"
34+
[cellThickness]="cellThickness()"
35+
[sectionSize]="sectionSize()"
36+
[sectionColor]="sectionColor()"
37+
[sectionThickness]="sectionThickness()"
38+
[fadeDistance]="fadeDistance()"
39+
[fadeStrength]="fadeStrength()"
40+
[infiniteGrid]="infiniteGrid()"
41+
[followCamera]="followCamera()"
42+
>
43+
<ngt-value attach="extensions.derivatives" [rawValue]="false" />
44+
</ngt-grid-material>
45+
<ng-content />
46+
</ngt-mesh>
47+
`,
48+
imports: [NgtArgs],
49+
})
50+
export class NgtsGrid {
51+
private inputs = signalStore<NgtsGridState>({
52+
cellColor: '#000000',
53+
sectionColor: '#2080ff',
54+
cellSize: 0.5,
55+
sectionSize: 1,
56+
followCamera: false,
57+
infiniteGrid: false,
58+
fadeDistance: 100,
59+
fadeStrength: 1,
60+
cellThickness: 0.5,
61+
sectionThickness: 1,
62+
side: THREE.BackSide,
63+
args: [1, 1, 1, 1],
64+
});
65+
66+
@Input() gridRef = injectNgtRef<THREE.Mesh>();
67+
@Input({ alias: 'cellSize' }) set _cellSize(cellSize: NgtsGridState['cellSize']) {
68+
this.inputs.set({ cellSize });
69+
}
70+
71+
@Input({ alias: 'cellThickness' }) set _cellThickness(cellThickness: NgtsGridState['cellThickness']) {
72+
this.inputs.set({ cellThickness });
73+
}
74+
75+
@Input({ alias: 'cellColor' }) set _cellColor(cellColor: NgtsGridState['cellColor']) {
76+
this.inputs.set({ cellColor });
77+
}
78+
79+
@Input({ alias: 'sectionSize' }) set _sectionSize(sectionSize: NgtsGridState['sectionSize']) {
80+
this.inputs.set({ sectionSize });
81+
}
82+
83+
@Input({ alias: 'sectionThickness' }) set _sectionThickness(sectionThickness: NgtsGridState['sectionThickness']) {
84+
this.inputs.set({ sectionThickness });
85+
}
86+
87+
@Input({ alias: 'sectionColor' }) set _sectionColor(sectionColor: NgtsGridState['sectionColor']) {
88+
this.inputs.set({ sectionColor });
89+
}
90+
91+
@Input({ alias: 'followCamera' }) set _followCamera(followCamera: NgtsGridState['followCamera']) {
92+
this.inputs.set({ followCamera });
93+
}
94+
95+
@Input({ alias: 'infiniteGrid' }) set _infiniteGrid(infiniteGrid: NgtsGridState['infiniteGrid']) {
96+
this.inputs.set({ infiniteGrid });
97+
}
98+
99+
@Input({ alias: 'fadeDistance' }) set _fadeDistance(fadeDistance: NgtsGridState['fadeDistance']) {
100+
this.inputs.set({ fadeDistance });
101+
}
102+
103+
@Input({ alias: 'fadeStrength' }) set _fadeStrength(fadeStrength: NgtsGridState['fadeStrength']) {
104+
this.inputs.set({ fadeStrength });
105+
}
106+
107+
@Input({ alias: 'side' }) set _side(side: NgtsGridState['side']) {
108+
this.inputs.set({ side });
109+
}
110+
111+
@Input({ alias: 'args' }) set _args(args: NgtsGridState['args']) {
112+
this.inputs.set({ args });
113+
}
114+
115+
cellSize = this.inputs.select('cellSize');
116+
sectionSize = this.inputs.select('sectionSize');
117+
fadeDistance = this.inputs.select('fadeDistance');
118+
fadeStrength = this.inputs.select('fadeStrength');
119+
cellThickness = this.inputs.select('cellThickness');
120+
sectionThickness = this.inputs.select('sectionThickness');
121+
infiniteGrid = this.inputs.select('infiniteGrid');
122+
followCamera = this.inputs.select('followCamera');
123+
cellColor = this.inputs.select('cellColor');
124+
sectionColor = this.inputs.select('sectionColor');
125+
side = this.inputs.select('side');
126+
args = this.inputs.select('args');
127+
128+
private plane = new THREE.Plane();
129+
private upVector = new THREE.Vector3(0, 1, 0);
130+
private zeroVector = new THREE.Vector3(0, 0, 0);
131+
132+
constructor() {
133+
injectBeforeRender(({ camera }) => {
134+
this.plane
135+
.setFromNormalAndCoplanarPoint(this.upVector, this.zeroVector)
136+
.applyMatrix4(this.gridRef.nativeElement.matrixWorld);
137+
138+
const gridMaterial = this.gridRef.nativeElement.material as THREE.ShaderMaterial;
139+
const worldCamProjPosition = gridMaterial.uniforms['worldCamProjPosition'] as THREE.Uniform<THREE.Vector3>;
140+
const worldPlanePosition = gridMaterial.uniforms['worldPlanePosition'] as THREE.Uniform<THREE.Vector3>;
141+
142+
this.plane.projectPoint(camera.position, worldCamProjPosition.value);
143+
worldPlanePosition.value.set(0, 0, 0).applyMatrix4(this.gridRef.nativeElement.matrixWorld);
144+
});
145+
}
146+
}

libs/soba/abstractions/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './billboard/billboard';
2+
export * from './grid/grid';
23
export * from './text/text';

libs/soba/project.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@
6969
"libs/soba/loaders/**/*.ts",
7070
"libs/soba/loaders/**/*.html",
7171
"libs/soba/cameras/**/*.ts",
72-
"libs/soba/cameras/**/*.html"
72+
"libs/soba/cameras/**/*.html",
73+
"libs/soba/shaders/**/*.ts",
74+
"libs/soba/shaders/**/*.html"
7375
]
7476
}
7577
},

libs/soba/shaders/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# angular-three-soba/shaders
2+
3+
Secondary entry point of `angular-three-soba`. It can be used by importing from `angular-three-soba/shaders`.

libs/soba/shaders/ng-package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"lib": {
3+
"entryFile": "src/index.ts"
4+
}
5+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import type { NgtShaderMaterial } from 'angular-three';
2+
import * as THREE from 'three';
3+
import { shaderMaterial } from '../shader-material/shader-material';
4+
5+
export const GridMaterial = shaderMaterial(
6+
{
7+
cellSize: 0.5,
8+
sectionSize: 1,
9+
fadeDistance: 100,
10+
fadeStrength: 1,
11+
cellThickness: 0.5,
12+
sectionThickness: 1,
13+
cellColor: new THREE.Color(),
14+
sectionColor: new THREE.Color(),
15+
infiniteGrid: false,
16+
followCamera: false,
17+
worldCamProjPosition: new THREE.Vector3(),
18+
worldPlanePosition: new THREE.Vector3(),
19+
},
20+
/* glsl */ `
21+
varying vec3 localPosition;
22+
varying vec4 worldPosition;
23+
24+
uniform vec3 worldCamProjPosition;
25+
uniform vec3 worldPlanePosition;
26+
uniform float fadeDistance;
27+
uniform bool infiniteGrid;
28+
uniform bool followCamera;
29+
30+
void main() {
31+
localPosition = position.xzy;
32+
if (infiniteGrid) localPosition *= 1.0 + fadeDistance;
33+
34+
worldPosition = modelMatrix * vec4(localPosition, 1.0);
35+
if (followCamera) {
36+
worldPosition.xyz += (worldCamProjPosition - worldPlanePosition);
37+
localPosition = (inverse(modelMatrix) * worldPosition).xyz;
38+
}
39+
40+
gl_Position = projectionMatrix * viewMatrix * worldPosition;
41+
}
42+
`,
43+
/* glsl */ `
44+
varying vec3 localPosition;
45+
varying vec4 worldPosition;
46+
47+
uniform vec3 worldCamProjPosition;
48+
uniform float cellSize;
49+
uniform float sectionSize;
50+
uniform vec3 cellColor;
51+
uniform vec3 sectionColor;
52+
uniform float fadeDistance;
53+
uniform float fadeStrength;
54+
uniform float cellThickness;
55+
uniform float sectionThickness;
56+
57+
float getGrid(float size, float thickness) {
58+
vec2 r = localPosition.xz / size;
59+
vec2 grid = abs(fract(r - 0.5) - 0.5) / fwidth(r);
60+
float line = min(grid.x, grid.y) + 1.0 - thickness;
61+
return 1.0 - min(line, 1.0);
62+
}
63+
64+
void main() {
65+
float g1 = getGrid(cellSize, cellThickness);
66+
float g2 = getGrid(sectionSize, sectionThickness);
67+
68+
float dist = distance(worldCamProjPosition, worldPosition.xyz);
69+
float d = 1.0 - min(dist / fadeDistance, 1.0);
70+
vec3 color = mix(cellColor, sectionColor, min(1.0, sectionThickness * g2));
71+
72+
gl_FragColor = vec4(color, (g1 + g2) * pow(d, fadeStrength));
73+
gl_FragColor.a = mix(0.75 * gl_FragColor.a, gl_FragColor.a, g2);
74+
if (gl_FragColor.a <= 0.0) discard;
75+
76+
#include <tonemapping_fragment>
77+
#include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
78+
}
79+
`,
80+
);
81+
82+
export type NgtsGridMaterialState = {
83+
/** Cell size, default: 0.5 */
84+
cellSize?: number;
85+
/** Cell thickness, default: 0.5 */
86+
cellThickness?: number;
87+
/** Cell color, default: black */
88+
cellColor?: THREE.ColorRepresentation;
89+
/** Section size, default: 1 */
90+
sectionSize?: number;
91+
/** Section thickness, default: 1 */
92+
sectionThickness?: number;
93+
/** Section color, default: #2080ff */
94+
sectionColor?: THREE.ColorRepresentation;
95+
/** Follow camera, default: false */
96+
followCamera?: boolean;
97+
/** Display the grid infinitely, default: false */
98+
infiniteGrid?: boolean;
99+
/** Fade distance, default: 100 */
100+
fadeDistance?: number;
101+
/** Fade strength, default: 1 */
102+
fadeStrength?: number;
103+
/** Material side, default: THREE.BackSide */
104+
side?: THREE.Side;
105+
};
106+
107+
declare global {
108+
interface HTMLElementTagNameMap {
109+
/**
110+
* @extends ngt-shader-material
111+
*/
112+
'ngt-grid-material': NgtsGridMaterialState & NgtShaderMaterial;
113+
}
114+
}

libs/soba/shaders/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './grid-material/grid-material';
2+
export * from './shader-material/shader-material';
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as THREE from 'three';
2+
3+
export function shaderMaterial<
4+
Uniforms extends {
5+
[name: string]:
6+
| THREE.CubeTexture
7+
| THREE.Texture
8+
| Int32Array
9+
| Float32Array
10+
| THREE.Matrix4
11+
| THREE.Matrix3
12+
| THREE.Quaternion
13+
| THREE.Vector4
14+
| THREE.Vector3
15+
| THREE.Vector2
16+
| THREE.Color
17+
| number
18+
| boolean
19+
| Array<any>
20+
| null;
21+
},
22+
>(
23+
uniforms: Uniforms,
24+
vertexShader: string,
25+
fragmentShader: string,
26+
onInit?: (material?: THREE.ShaderMaterial) => void,
27+
) {
28+
const material = class extends THREE.ShaderMaterial {
29+
public key: string = '';
30+
constructor(parameters = {}) {
31+
const entries = Object.entries(uniforms);
32+
// Create unforms and shaders
33+
super({
34+
uniforms: entries.reduce((acc, [name, value]) => {
35+
const uniform = THREE.UniformsUtils.clone({ [name]: { value } });
36+
return {
37+
...acc,
38+
...uniform,
39+
};
40+
}, {}),
41+
vertexShader,
42+
fragmentShader,
43+
});
44+
// Create getter/setters
45+
entries.forEach(([name]) =>
46+
Object.defineProperty(this, name, {
47+
get: () => this.uniforms[name].value,
48+
set: (v) => (this.uniforms[name].value = v),
49+
}),
50+
);
51+
52+
// Assign parameters, this might include uniforms
53+
Object.assign(this, parameters);
54+
// Call onInit
55+
if (onInit) onInit(this);
56+
}
57+
} as unknown as typeof THREE.ShaderMaterial & { key: string };
58+
material.key = THREE.MathUtils.generateUUID();
59+
return material;
60+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
2+
import { Meta, moduleMetadata } from '@storybook/angular';
3+
import { NgtsGrid } from 'angular-three-soba/abstractions';
4+
import { StorybookSetup, makeStoryFunction } from '../setup-canvas';
5+
6+
@Component({
7+
standalone: true,
8+
template: `
9+
<ngts-grid cellColor="white" [args]="[10, 10]" />
10+
<ngt-mesh [position]="[0, 0.5, 0]">
11+
<ngt-box-geometry />
12+
<ngt-mesh-standard-material />
13+
</ngt-mesh>
14+
<ngt-directional-light [position]="[10, 10, 5]" />
15+
`,
16+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
17+
imports: [NgtsGrid],
18+
})
19+
class DefaultGridStory {}
20+
21+
export default {
22+
title: 'Gizmo/Grid',
23+
decorators: [moduleMetadata({ imports: [StorybookSetup] })],
24+
} as Meta;
25+
26+
export const Default = makeStoryFunction(DefaultGridStory, { camera: { position: [-5, 5, 10] } });

0 commit comments

Comments
 (0)