Skip to content

Commit 197104d

Browse files
committed
decal and sampler
1 parent d0b8cb5 commit 197104d

File tree

16 files changed

+580
-11
lines changed

16 files changed

+580
-11
lines changed

Diff for: libs/core/src/lib/directives/parent.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Directive, Input } from '@angular/core';
2-
import type { NgtInjectedRef } from '../ref';
2+
import type { NgtRef } from '../ref';
33
import { NgtCommonDirective } from './common';
44

55
@Directive({ selector: 'ng-template[parent]', standalone: true })
66
export class NgtParent extends NgtCommonDirective {
7-
private injectedParent: string | THREE.Object3D | NgtInjectedRef<THREE.Object3D> = null!;
7+
private injectedParent: string | NgtRef<THREE.Object3D> = null!;
88

9-
@Input() set parent(parent: string | THREE.Object3D | NgtInjectedRef<THREE.Object3D>) {
9+
@Input() set parent(parent: string | NgtRef<THREE.Object3D>) {
1010
if (!parent) return;
1111
this.injected = false;
1212
this.injectedParent = parent;

Diff for: libs/core/src/lib/ref.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export type NgtInjectedRef<TElement> = ElementRef<TElement> & {
2020
untracked: TElement;
2121
};
2222

23+
export type NgtRef<TElement> = TElement | NgtInjectedRef<TElement>;
24+
2325
export function injectNgtRef<TElement>(
2426
initial: ElementRef<TElement> | TElement = null!,
2527
injector?: Injector,

Diff for: libs/core/src/lib/renderer/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,10 @@ class NgtRenderer implements Renderer2 {
287287
((pRS[NgtRendererClassId.type] === 'dom' ||
288288
(pRS[NgtRendererClassId.type] === 'compound' && !pRS[NgtRendererClassId.compounded])) &&
289289
(cRS[NgtRendererClassId.type] === 'dom' ||
290-
(cRS[NgtRendererClassId.type] === 'compound' && !cRS[NgtRendererClassId.compounded])));
290+
(cRS[NgtRendererClassId.type] === 'compound' && !cRS[NgtRendererClassId.compounded]))) ||
291+
(pRS[NgtRendererClassId.type] === 'dom' &&
292+
cRS[NgtRendererClassId.type] === 'compound' &&
293+
!!cRS[NgtRendererClassId.compounded]);
291294

292295
if (shouldFindGrandparentInstance) {
293296
// we'll try to get the grandparent instance here so that we can run appendChild with both instances

Diff for: libs/core/src/lib/renderer/store.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NgtArgs } from '../directives/args';
33
import type { NgtCommonDirective } from '../directives/common';
44
import { NgtParent } from '../directives/parent';
55
import { getLocalState, type NgtInstanceNode } from '../instance';
6-
import type { NgtInjectedRef } from '../ref';
6+
import type { NgtRef } from '../ref';
77
import { NGT_STORE, type NgtState } from '../store';
88
import type { NgtAnyRecord } from '../types';
99
import { applyProps } from '../utils/apply-props';
@@ -26,7 +26,7 @@ type NgtQueueOp = [type: 'op' | 'cleanUp', op: () => void, done?: true];
2626
export type NgtRendererState = [
2727
type: 'three' | 'compound' | 'portal' | 'comment' | 'dom',
2828
parent: NgtRendererNode | null,
29-
injectedParent: NgtRendererNode | NgtInjectedRef<NgtRendererNode> | null,
29+
injectedParent: NgtRef<NgtRendererNode> | null,
3030
children: NgtRendererNode[],
3131
destroyed: boolean,
3232
compound: [applyFirst: boolean, props: NgtAnyRecord],

Diff for: libs/soba/.storybook/public/soba/decals/angular.png

2.33 KB
Loading

Diff for: libs/soba/.storybook/public/soba/decals/ngt-logo.png

8.81 KB
Loading

Diff for: libs/soba/.storybook/public/soba/decals/react.png

24.6 KB
Loading

Diff for: libs/soba/.storybook/public/soba/decals/three.png

18.7 KB
Loading

Diff for: libs/soba/misc/src/animations/animations.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DestroyRef, Injector, computed, effect, inject, runInInjectionContext } from '@angular/core';
2-
import { assertInjectionContext, injectBeforeRender, injectNgtRef, type NgtInjectedRef } from 'angular-three';
2+
import { assertInjectionContext, injectBeforeRender, injectNgtRef, type NgtRef } from 'angular-three';
33
import * as THREE from 'three';
44

55
export function injectNgtsAnimations(
@@ -8,7 +8,7 @@ export function injectNgtsAnimations(
88
ref,
99
injector,
1010
playFirstClip = true,
11-
}: { ref?: NgtInjectedRef<THREE.Object3D> | THREE.Object3D; playFirstClip?: boolean; injector?: Injector } = {},
11+
}: { ref?: NgtRef<THREE.Object3D>; playFirstClip?: boolean; injector?: Injector } = {},
1212
) {
1313
injector = assertInjectionContext(injectNgtsAnimations, injector);
1414
return runInInjectionContext(injector, () => {

Diff for: libs/soba/misc/src/decal/decal.ts

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { NgIf } from '@angular/common';
2+
import { Component, computed, CUSTOM_ELEMENTS_SCHEMA, effect, Input } from '@angular/core';
3+
import {
4+
applyProps,
5+
extend,
6+
injectNgtRef,
7+
is,
8+
signalStore,
9+
type NgtEuler,
10+
type NgtMesh,
11+
type NgtRef,
12+
type NgtVector3,
13+
} from 'angular-three';
14+
import * as THREE from 'three';
15+
import { AxesHelper, BoxGeometry, Mesh, MeshNormalMaterial } from 'three';
16+
import { DecalGeometry } from 'three-stdlib';
17+
18+
extend({ Mesh, BoxGeometry, MeshNormalMaterial, AxesHelper });
19+
20+
export type NgtsDecalState = {
21+
debug: boolean;
22+
mesh?: NgtRef<THREE.Mesh>;
23+
position: NgtVector3;
24+
rotation: NgtEuler | number;
25+
scale: NgtVector3;
26+
map?: THREE.Texture;
27+
polygonOffsetFactor: number;
28+
depthTest: boolean;
29+
};
30+
31+
declare global {
32+
interface HTMLElementTagNameMap {
33+
/**
34+
* @extends ngt-mesh
35+
*/
36+
'ngts-decal': NgtsDecalState & NgtMesh;
37+
}
38+
}
39+
40+
function vecToArray(vec: number[] | NgtVector3 | NgtEuler | number = [0, 0, 0]) {
41+
if (Array.isArray(vec)) {
42+
return vec;
43+
}
44+
45+
if (vec instanceof THREE.Vector3 || vec instanceof THREE.Euler) {
46+
return [vec.x, vec.y, vec.z];
47+
}
48+
49+
return [vec, vec, vec];
50+
}
51+
52+
@Component({
53+
selector: 'ngts-decal',
54+
standalone: true,
55+
template: `
56+
<ngt-mesh [ref]="decalRef" ngtCompound>
57+
<ngt-value [rawValue]="true" attach="material.transparent" />
58+
<ngt-value [rawValue]="true" attach="material.polygonOffset" />
59+
<ngt-value [rawValue]="polygonOffsetFactor()" attach="material.polygonOffsetFactor" />
60+
<ngt-value [rawValue]="depthTest()" attach="material.depthTest" />
61+
<ngt-value [rawValue]="map()" attach="material.map" />
62+
<ng-content />
63+
64+
<ngt-mesh *ngIf="debug()" [ref]="helperRef">
65+
<ngt-box-geometry />
66+
<ngt-mesh-normal-material [wireframe]="true" />
67+
<ngt-axes-helper />
68+
</ngt-mesh>
69+
</ngt-mesh>
70+
`,
71+
imports: [NgIf],
72+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
73+
})
74+
export class NgtsDecal {
75+
private inputs = signalStore<NgtsDecalState>({
76+
debug: false,
77+
depthTest: false,
78+
polygonOffsetFactor: -10,
79+
position: [0, 0, 0],
80+
rotation: [0, 0, 0],
81+
scale: 1,
82+
});
83+
84+
@Input() decalRef = injectNgtRef<Mesh>();
85+
86+
@Input({ alias: 'debug' }) set _debug(debug: boolean) {
87+
this.inputs.set({ debug });
88+
}
89+
90+
@Input({ alias: 'mesh' }) set _mesh(mesh: NgtRef<THREE.Mesh>) {
91+
this.inputs.set({ mesh });
92+
}
93+
94+
@Input({ alias: 'position' }) set _position(position: NgtVector3) {
95+
this.inputs.set({ position });
96+
}
97+
98+
@Input({ alias: 'rotation' }) set _rotation(rotation: NgtEuler | number) {
99+
this.inputs.set({ rotation });
100+
}
101+
102+
@Input({ alias: 'scale' }) set _scale(scale: NgtVector3) {
103+
this.inputs.set({ scale });
104+
}
105+
106+
@Input({ alias: 'map' }) set _map(map: THREE.Texture) {
107+
this.inputs.set({ map });
108+
}
109+
110+
@Input({ alias: 'polygonOffsetFactor' }) set _polygonOffsetFactor(polygonOffsetFactor: number) {
111+
this.inputs.set({ polygonOffsetFactor });
112+
}
113+
114+
@Input({ alias: 'depthTest' }) set _depthTest(depthTest: boolean) {
115+
this.inputs.set({ depthTest });
116+
}
117+
118+
private mesh = this.inputs.select('mesh');
119+
private __position = this.inputs.select('position');
120+
private __rotation = this.inputs.select('rotation');
121+
private __scale = this.inputs.select('scale');
122+
private position = computed(() => vecToArray(this.__position()));
123+
private rotation = computed(() => vecToArray(this.__rotation()));
124+
private scale = computed(() => vecToArray(this.__scale()));
125+
126+
helperRef = injectNgtRef<Mesh>();
127+
128+
debug = this.inputs.select('debug');
129+
depthTest = this.inputs.select('depthTest');
130+
polygonOffsetFactor = this.inputs.select('polygonOffsetFactor');
131+
map = this.inputs.select('map');
132+
133+
constructor() {
134+
this.processDecal();
135+
}
136+
137+
private processDecal() {
138+
effect((onCleanup) => {
139+
const decal = this.decalRef.nativeElement;
140+
if (!decal) return;
141+
142+
const [mesh, position, rotation, scale, helper] = [
143+
this.mesh(),
144+
this.position(),
145+
this.rotation(),
146+
this.scale(),
147+
this.helperRef.untracked,
148+
];
149+
150+
const parent = mesh ? (is.ref(mesh) ? mesh.nativeElement : mesh) : decal.parent;
151+
if (!(parent instanceof Mesh)) {
152+
throw new Error('[NGT] ngts-decal must have a ngt-mesh as parent or specify its "mesh" input');
153+
}
154+
155+
const state = {
156+
position: new THREE.Vector3(),
157+
rotation: new THREE.Euler(),
158+
scale: new THREE.Vector3(1, 1, 1),
159+
};
160+
161+
if (parent) {
162+
applyProps(state, { position, scale });
163+
164+
// Zero out the parents matrix world for this operation
165+
const matrixWorld = parent.matrixWorld.clone();
166+
parent.matrixWorld.identity();
167+
168+
if (!rotation || typeof rotation === 'number') {
169+
const o = new THREE.Object3D();
170+
171+
o.position.copy(state.position);
172+
o.lookAt(parent.position);
173+
if (typeof rotation === 'number') o.rotateZ(rotation);
174+
applyProps(state, { rotation: o.rotation });
175+
} else {
176+
applyProps(state, { rotation });
177+
}
178+
179+
decal.geometry = new DecalGeometry(parent, state.position, state.rotation, state.scale);
180+
if (helper) {
181+
applyProps(helper, state);
182+
// Prevent the helpers from blocking rays
183+
helper.traverse((child) => (child.raycast = () => null));
184+
}
185+
// Reset parents matix-world
186+
parent.matrixWorld = matrixWorld;
187+
188+
onCleanup(() => {
189+
decal.geometry.dispose();
190+
});
191+
}
192+
});
193+
}
194+
}

Diff for: libs/soba/misc/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export * from './animations/animations';
22
export * from './bake-shadows/bake-shadows';
3+
export * from './decal/decal';
34
export * from './depth-buffer/depth-buffer';
45
export * from './fbo/fbo';
6+
export * from './sampler/sampler';

0 commit comments

Comments
 (0)