Skip to content

Commit b68eaa0

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
feat(soba): migrate quadratic bezier line
1 parent a9214c5 commit b68eaa0

File tree

5 files changed

+207
-83
lines changed

5 files changed

+207
-83
lines changed

libs/soba/abstractions/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './billboard/billboard';
22
export * from './line/line';
3+
export * from './quadratic-bezier-line/quadratic-bezier-line';
34
export * from './text-3d/text-3d';
45
export * from './text/text';

libs/soba/abstractions/src/line/line-input.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { Directive, Input } from '@angular/core';
1+
import { Directive, Input, computed } from '@angular/core';
22
import { NgtSignalStore } from 'angular-three';
33
import type { LineMaterialParameters } from 'three-stdlib';
44

5-
export type NgtsLineState = {
5+
export interface NgtsLineState extends Omit<LineMaterialParameters, 'vertexColors' | 'color'> {
66
vertexColors?: Array<THREE.Color | [number, number, number]>;
77
lineWidth?: number;
8-
segments?: boolean;
8+
segments: boolean | number | undefined;
99
color: THREE.ColorRepresentation;
1010
points: Array<THREE.Vector3 | THREE.Vector2 | [number, number, number] | [number, number] | number>;
11-
} & Omit<LineMaterialParameters, 'vertexColors' | 'color'>;
11+
}
1212

1313
@Directive()
1414
export abstract class NgtsLineInputs extends NgtSignalStore<NgtsLineState> {
@@ -60,6 +60,36 @@ export abstract class NgtsLineInputs extends NgtSignalStore<NgtsLineState> {
6060
this.set({ worldUnits });
6161
}
6262

63+
readonly lineParameters = computed(() => {
64+
const color = this.select('color');
65+
const vertexColors = this.select('vertexColors');
66+
const resolution = this.select('resolution');
67+
const linewidth = this.select('lineWidth');
68+
const alphaToCoverage = this.select('alphaToCoverage');
69+
const dashed = this.select('dashed');
70+
const dashScale = this.select('dashScale');
71+
const dashSize = this.select('dashSize');
72+
const dashOffset = this.select('dashOffset');
73+
const gapSize = this.select('gapSize');
74+
const wireframe = this.select('wireframe');
75+
const worldUnits = this.select('worldUnits');
76+
77+
return {
78+
color: color(),
79+
vertexColors: vertexColors(),
80+
resolution: resolution(),
81+
linewidth: linewidth(),
82+
alphaToCoverage: alphaToCoverage(),
83+
dashed: dashed(),
84+
dashScale: dashScale(),
85+
dashSize: dashSize(),
86+
dashOffset: dashOffset(),
87+
gapSize: gapSize(),
88+
wireframe: wireframe(),
89+
worldUnits: worldUnits(),
90+
};
91+
});
92+
6393
constructor() {
6494
super({ color: 'black' });
6595
}

libs/soba/abstractions/src/line/line.ts

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, Component, Injector, Input, computed, effect, i
22
import { NgtArgs, NgtStore, injectNgtRef } from 'angular-three';
33
import * as THREE from 'three';
44
import { Line2, LineGeometry, LineMaterial, LineSegments2, LineSegmentsGeometry } from 'three-stdlib';
5-
import { NgtsLineInputs, NgtsLineState } from './line-input';
5+
import { NgtsLineInputs, type NgtsLineState } from './line-input';
66

77
declare global {
88
interface HTMLElementTagNameMap {
@@ -19,9 +19,9 @@ declare global {
1919
<ngt-primitive
2020
*args="[lineMaterial]"
2121
attach="material"
22+
[resolution]="lineMaterialParameters().resolution"
2223
[color]="lineMaterialParameters().color"
2324
[vertexColors]="lineMaterialParameters().vertexColors"
24-
[resolution]="lineMaterialParameters().resolution"
2525
[linewidth]="lineMaterialParameters().linewidth"
2626
[alphaToCoverage]="lineMaterialParameters().alphaToCoverage"
2727
[dashed]="lineMaterialParameters().dashed"
@@ -101,32 +101,19 @@ export class NgtsLine extends NgtsLineInputs {
101101
});
102102

103103
readonly lineMaterialParameters = computed(() => {
104-
const color = this.select('color');
105-
const vertexColors = this.select('vertexColors');
104+
const parameters = this.lineParameters();
106105
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');
116106

117107
return {
118-
color: color(),
119-
vertexColors: Boolean(vertexColors()),
108+
...parameters,
109+
vertexColors: Boolean(parameters.vertexColors),
120110
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,
111+
dashScale: parameters.dashScale ?? this.lineMaterial.dashScale,
112+
dashSize: parameters.dashSize ?? this.lineMaterial.dashSize,
113+
dashOffset: parameters.dashOffset ?? this.lineMaterial.dashOffset,
114+
gapSize: parameters.gapSize ?? this.lineMaterial.gapSize,
115+
wireframe: parameters.wireframe ?? this.lineMaterial.wireframe,
116+
worldUnits: parameters.worldUnits ?? this.lineMaterial.worldUnits,
130117
};
131118
});
132119

@@ -141,19 +128,15 @@ export class NgtsLine extends NgtsLineInputs {
141128
const trigger = computed(() => {
142129
const points = this.select('points');
143130
const lineGeometry = this.lineGeometry();
144-
const line = this.line();
131+
const line = this.lineRef.nativeElement;
145132
const children = this.lineRef.children('nonObjects');
146133
return { points: points(), lineGeometry, line, children: children() };
147134
});
148-
effect(
149-
() => {
150-
const { line, children } = trigger();
151-
if (children.length) {
152-
line.computeLineDistances();
153-
}
154-
},
155-
{ injector: this.#injector }
156-
);
135+
effect(() => {
136+
const { line } = trigger();
137+
if (!line) return;
138+
line.computeLineDistances();
139+
});
157140
}
158141

159142
#disposeGeometry() {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { Component, Input, computed, effect } from '@angular/core';
2+
import { injectNgtRef } from 'angular-three';
3+
import * as THREE from 'three';
4+
import { Line2 } from 'three-stdlib';
5+
import { NgtsLine } from '../line/line';
6+
import { NgtsLineInputs } from '../line/line-input';
7+
8+
declare module '../line/line-input' {
9+
interface NgtsLineState {
10+
start: THREE.Vector3 | [number, number, number];
11+
end: THREE.Vector3 | [number, number, number];
12+
mid?: THREE.Vector3 | [number, number, number];
13+
}
14+
}
15+
16+
const v = new THREE.Vector3();
17+
18+
@Component({
19+
selector: 'ngts-quadratic-bezier-line',
20+
standalone: true,
21+
template: `
22+
<ngts-line
23+
[lineRef]="lineRef"
24+
[points]="points()"
25+
[color]="lineParameters().color"
26+
[vertexColors]="lineParameters().vertexColors"
27+
[resolution]="lineParameters().resolution"
28+
[lineWidth]="lineParameters().linewidth"
29+
[alphaToCoverage]="lineParameters().alphaToCoverage"
30+
[dashed]="lineParameters().dashed"
31+
[dashScale]="lineParameters().dashScale"
32+
[dashSize]="lineParameters().dashSize"
33+
[dashOffset]="lineParameters().dashOffset"
34+
[gapSize]="lineParameters().gapSize"
35+
[wireframe]="lineParameters().wireframe"
36+
[worldUnits]="lineParameters().worldUnits"
37+
/>
38+
`,
39+
imports: [NgtsLine],
40+
})
41+
export class NgtsQuadraticBezierLine extends NgtsLineInputs {
42+
readonly curve = new THREE.QuadraticBezierCurve3(undefined!, undefined!, undefined!);
43+
44+
@Input() lineRef = injectNgtRef<Line2>();
45+
46+
@Input() set start(start: THREE.Vector3 | [number, number, number]) {
47+
this.set({ start });
48+
}
49+
50+
@Input() set end(end: THREE.Vector3 | [number, number, number]) {
51+
this.set({ end });
52+
}
53+
54+
@Input() set mid(mid: THREE.Vector3 | [number, number, number]) {
55+
this.set({ mid });
56+
}
57+
58+
@Input() set segments(segments: number) {
59+
this.set({ segments });
60+
}
61+
62+
readonly points = computed(() => {
63+
const start = this.select('start');
64+
const end = this.select('end');
65+
const mid = this.select('mid');
66+
const segments = this.select('segments');
67+
return this.#getPoints(start(), end(), mid(), segments() as number);
68+
});
69+
70+
constructor() {
71+
super();
72+
this.set({ start: [0, 0, 0], end: [0, 0, 0], segments: 20 });
73+
this.#replaceSetPoints();
74+
}
75+
76+
#replaceSetPoints() {
77+
effect(() => {
78+
const line = this.lineRef.nativeElement;
79+
if (!line) return;
80+
(
81+
line as unknown as {
82+
setPoints: (
83+
start: THREE.Vector3 | [number, number, number],
84+
end: THREE.Vector3 | [number, number, number],
85+
mid: THREE.Vector3 | [number, number, number]
86+
) => void;
87+
}
88+
).setPoints = (start, end, mid) => {
89+
const points = this.#getPoints(start, end, mid);
90+
if (line.geometry) {
91+
line.geometry.setPositions(points.map((p) => p.toArray()).flat());
92+
}
93+
};
94+
});
95+
}
96+
97+
#getPoints(
98+
start: THREE.Vector3 | [number, number, number],
99+
end: THREE.Vector3 | [number, number, number],
100+
mid?: THREE.Vector3 | [number, number, number],
101+
segments = 20
102+
) {
103+
if (start instanceof THREE.Vector3) this.curve.v0.copy(start);
104+
else this.curve.v0.set(...(start as [number, number, number]));
105+
if (end instanceof THREE.Vector3) this.curve.v2.copy(end);
106+
else this.curve.v2.set(...(end as [number, number, number]));
107+
if (mid instanceof THREE.Vector3) {
108+
this.curve.v1.copy(mid);
109+
} else {
110+
this.curve.v1.copy(
111+
this.curve.v0
112+
.clone()
113+
.add(this.curve.v2.clone().sub(this.curve.v0))
114+
.add(v.set(0, this.curve.v0.y - this.curve.v2.y, 0))
115+
);
116+
}
117+
return this.curve.getPoints(segments);
118+
}
119+
}

libs/soba/src/abstractions/line.stories.ts

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core';
22
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
3-
import { NgtsLine } from 'angular-three-soba/abstractions';
3+
import { NgtsLine, NgtsQuadraticBezierLine } from 'angular-three-soba/abstractions';
44
import { NgtsOrbitControls } from 'angular-three-soba/controls';
55
import * as THREE from 'three';
66
import { GeometryUtils } from 'three-stdlib';
@@ -77,32 +77,32 @@ const defaultCatmullRom = {
7777
// @Input() tension = defaultCatmullRom.tension;
7878
// }
7979
//
80-
// @Component({
81-
// standalone: true,
82-
// template: `
83-
// <ngts-quadratic-bezier-line
84-
// [start]="start"
85-
// [end]="end"
86-
// [midA]="midA"
87-
// [midB]="midB"
88-
// [segments]="segments"
89-
// [color]="color"
90-
// [lineWidth]="lineWidth"
91-
// [dashed]="dashed"
92-
// />
93-
// <ngts-orbit-controls [zoomSpeed]="0.5" />
94-
// `,
95-
// imports: [NgtsQuadraticBezierLine, NgtsOrbitControls],
96-
// schemas: [CUSTOM_ELEMENTS_SCHEMA],
97-
// })
98-
// class QuadraticBezierLineStory {
99-
// @Input() color = 'red';
100-
// @Input() lineWidth = 3;
101-
// @Input() dashed = false;
102-
// @Input() start = defaultQuadraticBezier.start;
103-
// @Input() end = defaultQuadraticBezier.end;
104-
// @Input() segments = defaultQuadraticBezier.segments;
105-
// }
80+
@Component({
81+
standalone: true,
82+
template: `
83+
<ngts-quadratic-bezier-line
84+
[start]="start"
85+
[end]="end"
86+
[midA]="midA"
87+
[midB]="midB"
88+
[segments]="segments"
89+
[color]="color"
90+
[lineWidth]="lineWidth"
91+
[dashed]="dashed"
92+
/>
93+
<ngts-orbit-controls [zoomSpeed]="0.5" />
94+
`,
95+
imports: [NgtsQuadraticBezierLine, NgtsOrbitControls],
96+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
97+
})
98+
class QuadraticBezierLineStory {
99+
@Input() color = 'red';
100+
@Input() lineWidth = 3;
101+
@Input() dashed = false;
102+
@Input() start = defaultQuadraticBezier.start;
103+
@Input() end = defaultQuadraticBezier.end;
104+
@Input() segments = defaultQuadraticBezier.segments;
105+
}
106106
//
107107
// @Component({
108108
// standalone: true,
@@ -208,24 +208,15 @@ export const VertexColors: StoryObj = {
208208
// },
209209
// };
210210
//
211-
// export const QuadraticBezierLine: StoryObj = {
212-
// render: (args) => ({
213-
// props: {
214-
// story: QuadraticBezierLineStory,
215-
// options: makeCanvasOptions({ camera: { position: [0, 0, 17] }, controls: false }),
216-
// inputs: args,
217-
// },
218-
// template: `
219-
// <storybook-setup [story]="story" [options]="options" [inputs]="inputs" />
220-
// `,
221-
// }),
222-
// args: {
223-
// ...defaultQuadraticBezier,
224-
// color: 'red',
225-
// lineWidth: 3,
226-
// dashed: false,
227-
// },
228-
// };
211+
export const QuadraticBezierLine: StoryObj = {
212+
render: makeRenderFunction(QuadraticBezierLineStory, { camera: { position: [0, 0, 17] }, controls: false }),
213+
args: {
214+
...defaultQuadraticBezier,
215+
color: 'red',
216+
lineWidth: 3,
217+
dashed: false,
218+
},
219+
};
229220
//
230221
// export const CatmullRomLine: StoryObj = {
231222
// render: (args) => ({

0 commit comments

Comments
 (0)