Skip to content

Commit

Permalink
feat: add text
Browse files Browse the repository at this point in the history
  • Loading branch information
nartc committed Feb 6, 2023
1 parent 75aa0af commit 3ae76f4
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 0 deletions.
1 change: 1 addition & 0 deletions libs/angular-three-soba/abstractions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './lib/billboard/billboard';
export * from './lib/text/text';
95 changes: 95 additions & 0 deletions libs/angular-three-soba/abstractions/src/lib/text/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
EventEmitter,
inject,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { RxActionFactory } from '@rx-angular/state/actions';
import { injectNgtRef, NgtArgs, NgtRxStore, NgtStore } from 'angular-three';
// @ts-ignore
import { preloadFont, Text } from 'troika-three-text';

@Component({
selector: 'ngts-text[text]',
standalone: true,
template: `
<ngt-primitive
ngtCompound
*args="[textRef.nativeElement]"
[text]="get('text')"
[anchorX]="get('anchorX')"
[anchorY]="get('anchorY')"
[font]="get('font')"
>
<ng-content />
</ngt-primitive>
`,
imports: [NgtArgs],
providers: [RxActionFactory],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class NgtsText extends NgtRxStore implements OnInit, OnDestroy {
@Input() textRef = injectNgtRef<Text>();

@Input() set text(text: string) {
this.set({ text });
}

@Input() set characters(characters: string) {
this.set({ characters });
}

@Input() set font(font: string) {
this.set({ font });
}

@Input() set anchorX(anchorX: number | 'left' | 'center' | 'right') {
this.set({ anchorX });
}

@Input() set anchorY(anchorY: number | 'top' | 'top-baseline' | 'middle' | 'bottom-baseline' | 'bottom') {
this.set({ anchorY });
}

@Output() sync = new EventEmitter<Text>();

override initialize(): void {
super.initialize();
this.set({ anchorX: 'center', anchorY: 'middle', text: '' });
}

private readonly store = inject(NgtStore);
private readonly troikaText = new Text();

ngOnInit(): void {
if (!this.textRef.nativeElement) this.textRef.nativeElement = this.troikaText;
this.preloadFont();
this.syncText();
}

override ngOnDestroy(): void {
this.troikaText.dispose();
super.ngOnDestroy();
}

private preloadFont() {
const { font, characters } = this.get();
if (font && characters) {
preloadFont({ font, characters });
}
}

private syncText() {
this.hold(this.select(), () => {
const invalidate = this.store.get('invalidate');
this.troikaText.sync(() => {
invalidate();
if (this.sync.observed) this.sync.next(this.troikaText);
});
});
}
}
186 changes: 186 additions & 0 deletions libs/angular-three-soba/src/abstractions/billboard.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core';
import { Meta, moduleMetadata, Story } from '@storybook/angular';
import { extend, NgtArgs } from 'angular-three';
import { NgtsBillboard, NgtsText } from 'angular-three-soba/abstractions';
import { NgtsOrbitControls } from 'angular-three-soba/controls';
import { BoxGeometry, ConeGeometry, Group, Mesh, MeshStandardMaterial, PlaneGeometry } from 'three';
import { makeCanvasOptions, StorybookSetup } from '../setup-canvas';

extend({ Mesh, PlaneGeometry, BoxGeometry, ConeGeometry, MeshStandardMaterial, Group });

@Component({
selector: 'BillboardCone',
standalone: true,
template: `
<ngt-mesh>
<ngt-cone-geometry *args="args" />
<ngt-value attach="material.color" [rawValue]="color" />
<ng-content />
</ngt-mesh>
`,
imports: [NgtArgs],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
class Cone {
@Input() args: ConstructorParameters<typeof ConeGeometry> = [];
@Input() color = 'white';
}

@Component({
selector: 'BillboardBox',
standalone: true,
template: `
<ngt-mesh ngtCompound>
<ngt-box-geometry *args="args" />
<ngt-value attach="material.color" [rawValue]="color" />
<ng-content />
</ngt-mesh>
`,
imports: [NgtArgs],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
class Box {
@Input() args: ConstructorParameters<typeof BoxGeometry> = [];
@Input() color = 'white';
}

@Component({
selector: 'BillboardPlane',
standalone: true,
template: `
<ngt-mesh>
<ngt-plane-geometry *args="args" />
<ngt-value attach="material.color" [rawValue]="color" />
<ng-content />
</ngt-mesh>
`,
imports: [NgtArgs],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
class Plane {
@Input() args: ConstructorParameters<typeof PlaneGeometry> = [];
@Input() color = 'white';
}

@Component({
standalone: true,
template: `
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[0.5, 2.05, 0.5]">
<ngts-text text="box" [fontSize]="1" [outlineWidth]="'5%'" [outlineColor]="'#000'" [outlineOpacity]="1" />
</ngts-billboard>
<BillboardBox [position]="[0.5, 1, 0.5]" color="red">
<ngt-mesh-standard-material />
</BillboardBox>
<ngt-group #group name="parent-group" [position]="[-2.5, -3, -1]">
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[0, 1.05, 0]">
<ngts-text
text="cone"
[fontSize]="1"
[outlineWidth]="'5%'"
[outlineColor]="'#000'"
[outlineOpacity]="1"
/>
</ngts-billboard>
<BillboardCone color="green">
<ngt-mesh-standard-material />
</BillboardCone>
</ngt-group>
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[0, 0, -5]">
<BillboardPlane [args]="[2, 2]" color="#000066">
<ngt-mesh-standard-material />
</BillboardPlane>
</ngts-billboard>
<ngts-orbit-controls [enablePan]="true" [zoomSpeed]="0.5" />
`,
imports: [NgtsBillboard, NgtsOrbitControls, NgtsText, Cone, Box, Plane],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
class TextBillboardStory {
@Input() follow = true;
@Input() lockX = false;
@Input() lockY = false;
@Input() lockZ = false;
}

@Component({
standalone: true,
template: `
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[-4, -2, 0]">
<BillboardPlane [args]="[3, 2]" color="red" />
</ngts-billboard>
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[-4, 2, 0]">
<BillboardPlane [args]="[3, 2]" color="orange" />
</ngts-billboard>
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[0, 0, 0]">
<BillboardPlane [args]="[3, 2]" color="green" />
</ngts-billboard>
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[4, -2, 0]">
<BillboardPlane [args]="[3, 2]" color="blue" />
</ngts-billboard>
<ngts-billboard [follow]="follow" [lockX]="lockX" [lockY]="lockY" [lockZ]="lockZ" [position]="[4, 2, 0]">
<BillboardPlane [args]="[3, 2]" color="yellow" />
</ngts-billboard>
<ngts-orbit-controls [enablePan]="true" [zoomSpeed]="0.5" />
`,
imports: [NgtsBillboard, Plane, NgtsOrbitControls],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
class DefaultBillboardStory {
@Input() follow = true;
@Input() lockX = false;
@Input() lockY = false;
@Input() lockZ = false;
}

export default {
title: 'Abstractions/Billboard',
decorators: [moduleMetadata({ imports: [StorybookSetup] })],
} as Meta;

const compoundPrefixes = ['BillboardBox'];

export const Default: Story = (args) => ({
props: {
options: makeCanvasOptions({
camera: { position: [0, 0, 10] },
controls: false,
compoundPrefixes,
}),
story: DefaultBillboardStory,
inputs: args,
},
template: `
<storybook-setup [story]="story" [inputs]="inputs" [options]="options" />
`,
});

Default.args = {
follow: true,
lockX: false,
lockY: false,
lockZ: false,
};

export const Text: Story = (args) => ({
props: {
options: makeCanvasOptions({
camera: { position: [0, 0, 10] },
controls: false,
compoundPrefixes,
}),
story: TextBillboardStory,
inputs: args,
},
template: `
<storybook-setup [story]="story" [inputs]="inputs" [options]="options" />
`,
});

Text.args = {
follow: true,
lockX: false,
lockY: false,
lockZ: false,
};

0 comments on commit 3ae76f4

Please sign in to comment.