Skip to content

Commit a60e3b7

Browse files
committed
feat(soba): add NgtsMask
1 parent 2a0edd6 commit a60e3b7

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './lib/contact-shadows';
99
export * from './lib/environment';
1010
export * from './lib/float';
1111
export * from './lib/lightformer';
12+
export * from './lib/mask';
1213
export * from './lib/matcap-texture';
1314
export * from './lib/normal-texture';
1415
export * from './lib/randomized-lights';

Diff for: libs/soba/staging/src/lib/mask.ts

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
afterNextRender,
3+
ChangeDetectionStrategy,
4+
Component,
5+
computed,
6+
CUSTOM_ELEMENTS_SCHEMA,
7+
ElementRef,
8+
Injector,
9+
input,
10+
numberAttribute,
11+
viewChild,
12+
} from '@angular/core';
13+
import { extend, NgtMesh, omit, pick } from 'angular-three';
14+
import { assertInjector } from 'ngxtension/assert-injector';
15+
import { injectAutoEffect } from 'ngxtension/auto-effect';
16+
import { mergeInputs } from 'ngxtension/inject-inputs';
17+
import {
18+
AlwaysStencilFunc,
19+
BufferGeometry,
20+
EqualStencilFunc,
21+
KeepStencilOp,
22+
Material,
23+
Mesh,
24+
NotEqualStencilFunc,
25+
ReplaceStencilOp,
26+
} from 'three';
27+
28+
export interface NgtsMaskOptions extends Partial<NgtMesh> {
29+
colorWrite: boolean;
30+
depthWrite: boolean;
31+
}
32+
33+
const defaultOptions: NgtsMaskOptions = {
34+
colorWrite: false,
35+
depthWrite: false,
36+
};
37+
38+
@Component({
39+
selector: 'ngts-mask',
40+
standalone: true,
41+
template: `
42+
<ngt-mesh #mesh [renderOrder]="-id()" [parameters]="parameters()">
43+
<ng-content />
44+
</ngt-mesh>
45+
`,
46+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
47+
changeDetection: ChangeDetectionStrategy.OnPush,
48+
})
49+
export class NgtsMask {
50+
id = input(1, { transform: numberAttribute });
51+
options = input(defaultOptions, { transform: mergeInputs(defaultOptions) });
52+
53+
parameters = omit(this.options, ['colorWrite', 'depthWrite']);
54+
55+
meshRef = viewChild.required<ElementRef<Mesh<BufferGeometry, Material>>>('mesh');
56+
57+
private colorWrite = pick(this.options, 'colorWrite');
58+
private depthWrite = pick(this.options, 'depthWrite');
59+
private spread = computed(() => {
60+
const [id, colorWrite, depthWrite] = [this.id(), this.colorWrite(), this.depthWrite()];
61+
return {
62+
colorWrite,
63+
depthWrite,
64+
stencilWrite: true,
65+
stencilRef: id,
66+
stencilFunc: AlwaysStencilFunc,
67+
stencilFail: ReplaceStencilOp,
68+
stencilZFail: ReplaceStencilOp,
69+
stencilZPass: ReplaceStencilOp,
70+
};
71+
});
72+
73+
constructor() {
74+
extend({ Mesh });
75+
76+
const autoEffect = injectAutoEffect();
77+
78+
afterNextRender(() => {
79+
autoEffect(() => {
80+
const [mesh, spread] = [this.meshRef().nativeElement, this.spread()];
81+
Object.assign(mesh.material, spread);
82+
});
83+
});
84+
}
85+
}
86+
87+
export function injectMask(
88+
id: () => number,
89+
inverse: () => boolean = () => false,
90+
{ injector }: { injector?: Injector } = {},
91+
) {
92+
return assertInjector(injectMask, injector, () => {
93+
return computed(() => {
94+
const [_id, _inverse] = [id(), inverse()];
95+
return {
96+
stencilWrite: true,
97+
stencilRef: _id,
98+
stencilFunc: _inverse ? NotEqualStencilFunc : EqualStencilFunc,
99+
stencilFail: KeepStencilOp,
100+
stencilZFail: KeepStencilOp,
101+
stencilZPass: KeepStencilOp,
102+
};
103+
});
104+
});
105+
}

0 commit comments

Comments
 (0)