Skip to content

Commit f5efc4e

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
feat(soba): migrate Line
1 parent 075a1a2 commit f5efc4e

File tree

5 files changed

+487
-0
lines changed

5 files changed

+487
-0
lines changed

libs/angular-three/src/lib/renderer/renderer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { NgtStore } from '../stores/store';
1818
import type { NgtAnyRecord } from '../types';
1919
import { getLocalState, prepare } from '../utils/instance';
2020
import { is } from '../utils/is';
21+
import { safeDetectChanges } from '../utils/safe-detect-changes';
2122
import { createSignal } from '../utils/signal';
2223
import { NGT_COMPOUND_PREFIXES } from './di';
2324
import { NgtRendererClassId } from './enums';
@@ -244,6 +245,7 @@ export class NgtRenderer implements Renderer2 {
244245
if (getLocalState(newChild).parent && untracked(getLocalState(newChild).parent)) return;
245246
// attach THREE child
246247
attachThreeChild(parent, newChild);
248+
safeDetectChanges(this.cdr);
247249
// here, we handle the special case of if the parent has a compoundParent, which means this child is part of a compound parent template
248250
if (!cRS[NgtRendererClassId.compound]) return;
249251
const closestGrandparentWithCompound = this.store.getClosestParentWithCompound(parent);

libs/soba/abstractions/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './billboard/billboard';
2+
export * from './line/line';
23
export * from './text-3d/text-3d';
34
export * from './text/text';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Directive, Input } from '@angular/core';
2+
import { NgtSignalStore } from 'angular-three';
3+
import type { LineMaterialParameters } from 'three-stdlib';
4+
5+
export type NgtsLineState = {
6+
vertexColors?: Array<THREE.Color | [number, number, number]>;
7+
lineWidth?: number;
8+
segments?: boolean;
9+
color: THREE.ColorRepresentation;
10+
points: Array<THREE.Vector3 | THREE.Vector2 | [number, number, number] | [number, number] | number>;
11+
} & Omit<LineMaterialParameters, 'vertexColors' | 'color'>;
12+
13+
@Directive()
14+
export abstract class NgtsLineInputs extends NgtSignalStore<NgtsLineState> {
15+
@Input() set vertexColors(vertexColors: Array<THREE.Color | [number, number, number]>) {
16+
this.set({ vertexColors });
17+
}
18+
19+
@Input() set lineWidth(lineWidth: number) {
20+
this.set({ lineWidth });
21+
}
22+
23+
@Input() set alphaToCoverage(alphaToCoverage: boolean) {
24+
this.set({ alphaToCoverage });
25+
}
26+
27+
@Input() set color(color: THREE.ColorRepresentation) {
28+
this.set({ color });
29+
}
30+
31+
@Input() set dashed(dashed: boolean) {
32+
this.set({ dashed });
33+
}
34+
35+
@Input() set dashScale(dashScale: number) {
36+
this.set({ dashScale });
37+
}
38+
39+
@Input() set dashSize(dashSize: number) {
40+
this.set({ dashSize });
41+
}
42+
43+
@Input() set dashOffset(dashOffset: number) {
44+
this.set({ dashOffset });
45+
}
46+
47+
@Input() set gapSize(gapSize: number) {
48+
this.set({ gapSize });
49+
}
50+
51+
@Input() set resolution(resolution: THREE.Vector2) {
52+
this.set({ resolution });
53+
}
54+
55+
@Input() set wireframe(wireframe: boolean) {
56+
this.set({ wireframe });
57+
}
58+
59+
@Input() set worldUnits(worldUnits: boolean) {
60+
this.set({ worldUnits });
61+
}
62+
63+
constructor() {
64+
super({ color: 'black' });
65+
}
66+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { CUSTOM_ELEMENTS_SCHEMA, Component, Injector, Input, computed, effect, inject } from '@angular/core';
2+
import { NgtArgs, NgtStore, injectNgtRef } from 'angular-three';
3+
import * as THREE from 'three';
4+
import { Line2, LineGeometry, LineMaterial, LineSegments2, LineSegmentsGeometry } from 'three-stdlib';
5+
import { NgtsLineInputs, NgtsLineState } from './line-input';
6+
7+
declare global {
8+
interface HTMLElementTagNameMap {
9+
'ngts-line': NgtsLineState;
10+
}
11+
}
12+
13+
@Component({
14+
selector: 'ngts-line',
15+
standalone: true,
16+
template: `
17+
<ngt-primitive *args="[line()]" [ref]="lineRef" ngtCompound>
18+
<ngt-primitive *args="[lineGeometry()]" attach="geometry" />
19+
<ngt-primitive
20+
*args="[lineMaterial]"
21+
attach="material"
22+
[color]="lineMaterialParameters().color"
23+
[vertexColors]="lineMaterialParameters().vertexColors"
24+
[resolution]="lineMaterialParameters().resolution"
25+
[linewidth]="lineMaterialParameters().linewidth"
26+
[alphaToCoverage]="lineMaterialParameters().alphaToCoverage"
27+
[dashed]="lineMaterialParameters().dashed"
28+
[dashScale]="lineMaterialParameters().dashScale"
29+
[dashSize]="lineMaterialParameters().dashSize"
30+
[dashOffset]="lineMaterialParameters().dashOffset"
31+
[gapSize]="lineMaterialParameters().gapSize"
32+
[wireframe]="lineMaterialParameters().wireframe"
33+
[worldUnits]="lineMaterialParameters().worldUnits"
34+
/>
35+
</ngt-primitive>
36+
`,
37+
imports: [NgtArgs],
38+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
39+
})
40+
export class NgtsLine extends NgtsLineInputs {
41+
@Input() lineRef = injectNgtRef<LineSegments2 | Line2>();
42+
43+
@Input() set points(
44+
points: Array<THREE.Vector3 | THREE.Vector2 | [number, number, number] | [number, number] | number>
45+
) {
46+
this.set({ points });
47+
}
48+
49+
@Input() set segments(segments: boolean) {
50+
this.set({ segments });
51+
}
52+
53+
readonly #injector = inject(Injector);
54+
readonly #store = inject(NgtStore);
55+
56+
readonly #resolution = computed(() => {
57+
const size = this.#store.select('size');
58+
const resolution = this.select('resolution');
59+
60+
return resolution() ? resolution() : [size().width, size().height];
61+
});
62+
63+
readonly #pointValues = computed(() => {
64+
const points = this.select('points');
65+
return points().map((p) => {
66+
const isArray = Array.isArray(p);
67+
return p instanceof THREE.Vector3
68+
? [p.x, p.y, p.z]
69+
: p instanceof THREE.Vector2
70+
? [p.x, p.y, 0]
71+
: isArray && p.length === 3
72+
? [p[0], p[1], p[2]]
73+
: isArray && p.length === 2
74+
? [p[0], p[1], 0]
75+
: p;
76+
});
77+
});
78+
readonly #vertexColors = computed(() => {
79+
const vertexColors = this.select('vertexColors');
80+
return (vertexColors() || []).map((c) => (c instanceof THREE.Color ? c.toArray() : c));
81+
});
82+
83+
readonly lineGeometry = computed(() => {
84+
const segments = this.select('segments');
85+
const pointValues = this.#pointValues();
86+
const vertexColors = this.#vertexColors();
87+
88+
const geometry = segments() ? new LineSegmentsGeometry() : new LineGeometry();
89+
geometry.setPositions(pointValues.flat());
90+
91+
if (vertexColors.length) {
92+
geometry.setColors(vertexColors.flat());
93+
}
94+
95+
return geometry;
96+
});
97+
readonly lineMaterial = new LineMaterial();
98+
readonly line = computed(() => {
99+
const segments = this.select('segments');
100+
return segments() ? new LineSegments2() : new Line2();
101+
});
102+
103+
readonly lineMaterialParameters = computed(() => {
104+
const color = this.select('color');
105+
const vertexColors = this.select('vertexColors');
106+
const resolution = this.#resolution();
107+
const linewidth = this.select('lineWidth');
108+
const alphaToCoverage = this.select('alphaToCoverage');
109+
const dashed = this.select('dashed');
110+
const dashScale = this.select('dashScale');
111+
const dashSize = this.select('dashSize');
112+
const dashOffset = this.select('dashOffset');
113+
const gapSize = this.select('gapSize');
114+
const wireframe = this.select('wireframe');
115+
const worldUnits = this.select('worldUnits');
116+
117+
return {
118+
color: color(),
119+
vertexColors: Boolean(vertexColors()),
120+
resolution,
121+
linewidth: linewidth(),
122+
alphaToCoverage: alphaToCoverage(),
123+
dashed: dashed(),
124+
dashScale: dashScale() ?? this.lineMaterial.dashScale,
125+
dashSize: dashSize() ?? this.lineMaterial.dashSize,
126+
dashOffset: dashOffset() ?? this.lineMaterial.dashOffset,
127+
gapSize: gapSize() ?? this.lineMaterial.gapSize,
128+
wireframe: wireframe() ?? this.lineMaterial.wireframe,
129+
worldUnits: worldUnits() ?? this.lineMaterial.worldUnits,
130+
};
131+
});
132+
133+
constructor() {
134+
super();
135+
this.set({ segments: false });
136+
this.#disposeGeometry();
137+
this.#computeLineDistances();
138+
}
139+
140+
#computeLineDistances() {
141+
const trigger = computed(() => {
142+
const points = this.select('points');
143+
const lineGeometry = this.lineGeometry();
144+
const line = this.line();
145+
const children = this.lineRef.children('nonObjects');
146+
return { points: points(), lineGeometry, line, children: children() };
147+
});
148+
effect(
149+
() => {
150+
const { line, children } = trigger();
151+
if (children.length) {
152+
line.computeLineDistances();
153+
}
154+
},
155+
{ injector: this.#injector }
156+
);
157+
}
158+
159+
#disposeGeometry() {
160+
effect(
161+
(onCleanup) => {
162+
const lineGeometry = this.lineGeometry();
163+
onCleanup(() => lineGeometry.dispose());
164+
},
165+
{ injector: this.#injector }
166+
);
167+
}
168+
}

0 commit comments

Comments
 (0)