Skip to content

Commit 98e3789

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
feat(soba): migrate text-3d
1 parent 8784ae1 commit 98e3789

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed

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 './text-3d/text-3d';
23
export * from './text/text';
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { Component, computed, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core';
2+
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
3+
import { extend, NgtArgs, NgtSignalStore, type NgtMesh } from 'angular-three';
4+
import { map, of, switchMap } from 'rxjs';
5+
import { Mesh } from 'three';
6+
import { FontLoader, TextGeometry } from 'three-stdlib';
7+
8+
declare type Glyph = { _cachedOutline: string[]; ha: number; o: string };
9+
10+
declare type FontData = {
11+
boundingBox: { yMax: number; yMin: number };
12+
familyName: string;
13+
glyphs: { [k: string]: Glyph };
14+
resolution: number;
15+
underlineThickness: number;
16+
};
17+
18+
extend({ Mesh, TextGeometry });
19+
20+
export type NgtsText3DState = {
21+
font: FontData | string;
22+
text: string;
23+
letterSpacing: number;
24+
lineHeight: number;
25+
size: number;
26+
height: number;
27+
bevelThickness: number;
28+
bevelSize: number;
29+
bevelEnabled: boolean;
30+
bevelOffset: number;
31+
bevelSegments: number;
32+
curveSegments: number;
33+
};
34+
35+
declare global {
36+
interface HTMLElementTagNameMap {
37+
'ngts-text-3d': NgtsText3DState & NgtMesh;
38+
}
39+
}
40+
41+
@Component({
42+
selector: 'ngts-text-3d',
43+
standalone: true,
44+
template: `
45+
<ngt-mesh ngtCompound>
46+
<ngt-text-geometry *args="geometryArgs()" />
47+
<ng-content />
48+
</ngt-mesh>
49+
`,
50+
imports: [NgtArgs],
51+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
52+
})
53+
export class NgtsText3D extends NgtSignalStore<NgtsText3DState> {
54+
@Input({ required: true }) set font(font: FontData | string) {
55+
this.set({ font });
56+
}
57+
58+
@Input({ required: true }) set text(text: string) {
59+
this.set({ text });
60+
}
61+
62+
@Input() set bevelEnabled(bevelEnabled: boolean) {
63+
this.set({ bevelEnabled });
64+
}
65+
66+
@Input() set bevelOffset(bevelOffset: number) {
67+
this.set({ bevelOffset });
68+
}
69+
70+
@Input() set bevelSize(bevelSize: number) {
71+
this.set({ bevelSize });
72+
}
73+
74+
@Input() set bevelThickness(bevelThickness: number) {
75+
this.set({ bevelThickness });
76+
}
77+
78+
@Input() set curveSegments(curveSegments: number) {
79+
this.set({ curveSegments });
80+
}
81+
82+
@Input() set bevelSegments(bevelSegments: number) {
83+
this.set({ bevelSegments });
84+
}
85+
86+
@Input() set height(height: number) {
87+
this.set({ height });
88+
}
89+
90+
@Input() set size(size: number) {
91+
this.set({ size });
92+
}
93+
94+
@Input() set lineHeight(lineHeight: number) {
95+
this.set({ lineHeight });
96+
}
97+
98+
@Input() set letterSpacing(letterSpacing: number) {
99+
this.set({ letterSpacing });
100+
}
101+
102+
constructor() {
103+
super({
104+
lineHeight: 1,
105+
letterSpacing: 0,
106+
size: 1,
107+
height: 0.2,
108+
bevelThickness: 0.1,
109+
bevelSize: 0.01,
110+
bevelEnabled: false,
111+
bevelOffset: 0,
112+
bevelSegments: 4,
113+
curveSegments: 8,
114+
});
115+
}
116+
117+
readonly #fontData = toSignal(
118+
toObservable(this.select('font')).pipe(
119+
switchMap((font) => {
120+
if (typeof font === 'string') return fetch(font).then((res) => res.json()) as Promise<FontData>;
121+
return of(font as FontData);
122+
}),
123+
map((fontData) => new FontLoader().parse(fontData))
124+
)
125+
);
126+
127+
readonly geometryArgs = computed(() => {
128+
const fontData = this.#fontData();
129+
if (!fontData) return null;
130+
const text = this.select('text');
131+
const size = this.select('size');
132+
const height = this.select('height');
133+
const bevelThickness = this.select('bevelThickness');
134+
const bevelSize = this.select('bevelSize');
135+
const bevelEnabled = this.select('bevelEnabled');
136+
const bevelSegments = this.select('bevelSegments');
137+
const bevelOffset = this.select('bevelOffset');
138+
const curveSegments = this.select('curveSegments');
139+
const letterSpacing = this.select('letterSpacing');
140+
const lineHeight = this.select('lineHeight');
141+
142+
return [
143+
text(),
144+
{
145+
font: fontData,
146+
size: size(),
147+
height: height(),
148+
bevelThickness: bevelThickness(),
149+
bevelSize: bevelSize(),
150+
bevelSegments: bevelSegments(),
151+
bevelEnabled: bevelEnabled(),
152+
bevelOffset: bevelOffset(),
153+
curveSegments: curveSegments(),
154+
letterSpacing: letterSpacing(),
155+
lineHeight: lineHeight(),
156+
},
157+
] as const;
158+
});
159+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core';
2+
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
3+
import { NgtsText3D } from 'angular-three-soba/abstractions';
4+
import { NgtsCenter, NgtsFloat } from 'angular-three-soba/staging';
5+
import { makeCanvasOptions, StorybookSetup } from '../setup-canvas';
6+
7+
@Component({
8+
standalone: true,
9+
template: `
10+
<ngts-center>
11+
<ngts-float [floatIntensity]="5" [speed]="2">
12+
<ngts-text-3d
13+
font="soba/helvetiker_regular.typeface.json"
14+
[bevelEnabled]="true"
15+
[bevelSize]="0.05"
16+
[text]="text"
17+
>
18+
<ngt-mesh-normal-material />
19+
</ngts-text-3d>
20+
</ngts-float>
21+
</ngts-center>
22+
`,
23+
imports: [NgtsCenter, NgtsFloat, NgtsText3D],
24+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
25+
})
26+
class DefaultText3DStory {
27+
@Input() text = 'Angular Three';
28+
}
29+
30+
export default {
31+
title: 'Abstractions/Text 3D',
32+
decorators: [moduleMetadata({ imports: [StorybookSetup] })],
33+
} as Meta;
34+
35+
export const Default: StoryObj = {
36+
render: (args) => ({
37+
props: {
38+
options: makeCanvasOptions({ camera: { position: [0, 0, 10] } }),
39+
story: DefaultText3DStory,
40+
inputs: args,
41+
},
42+
template: `
43+
<storybook-setup [options]="options" [story]="story" [inputs]="inputs" />
44+
`,
45+
}),
46+
args: { text: 'Angular Three' },
47+
};

0 commit comments

Comments
 (0)