Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polish text bitecs #5933

Merged
merged 2 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const MediaFrame = defineComponent({
preview: Types.eid,
previewingNid: Types.eid
});
export const Text = defineComponent();
export const TextTag = defineComponent();
export const ReflectionProbe = defineComponent();
export const Slice9 = defineComponent({
insets: [Types.ui32, 4],
Expand Down
3 changes: 0 additions & 3 deletions src/bit-systems/camera-tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,14 @@ function updateUI(world, camera) {
if (countdownLblObj.visible) {
const timeLeftSec = Math.ceil((CameraTool.snapTime[camera] - world.time.elapsed) / 1000);
countdownLblObj.text = timeLeftSec;
countdownLblObj.sync(); // TODO this should probably happen in 1 spot per frame for all Texts
}

if (captureDurLblObj.visible) {
captureDurLblObj.text = CAPTURE_DURATIONS[CameraTool.captureDurIdx[camera]];
captureDurLblObj.sync(); // TODO this should probably happen in 1 spot per frame for all Texts
}

if (sndToggleBtnObj.visible) {
sndToggleLblObj.text = captureAudio ? "Sound ON" : "Sound OFF";
sndToggleLblObj.sync();
}
}

Expand Down
1 change: 0 additions & 1 deletion src/bit-systems/network-debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,5 @@ export function networkDebugSystem(world: HubsWorld, scene: Scene) {
textObj.matrixNeedsUpdate = true;

textObj.text = formatObjectName(networkedObj) + "\nNetworked " + formatComponentProps(eid, Networked);
textObj.sync();
});
}
33 changes: 33 additions & 0 deletions src/bit-systems/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { defineQuery } from "bitecs";
import { Text as TroikaText } from "troika-three-text";
import { HubsWorld } from "../app";
import { TextTag } from "../bit-components";

const textQuery = defineQuery([TextTag]);

export function textSystem(world: HubsWorld) {
textQuery(world).forEach(eid => {
const text = world.eid2obj.get(eid)! as TroikaText;

// sync() invokes async text processing in workers.
// https://github.com/protectwise/troika/tree/main/packages/troika-three-text#handling-asynchronous-updates
//
// It is safe to call sync() every frame from the
// performance and efficiency perspective because
// sync() checks whether to invoke costly processing
// inside.
//
// Ideally this system should run after any other systems
// that can update text properties and we need to be careful
// for the systems execution order. Otherwise sync() call
// can happen one frame after. (But probably it may not be
// a big deal even if it happens because what sync() invokes
// is async processing, texture properties update will be
// reflected some frames after in any case.)
//
// Assumes it is safe even if text object is
// disposed before the async processing is done
// because TroikaText properly handles
text.sync();
});
}
1 change: 0 additions & 1 deletion src/bit-systems/video-menu-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ export function videoMenuSystem(world: HubsWorld, userinput: any) {

const timeLabelRef = world.eid2obj.get(VideoMenu.timeLabelRef[eid])! as TroikaText;
timeLabelRef.text = `${timeFmt(video.currentTime)} / ${timeFmt(video.duration)}`;
timeLabelRef.sync();

if (rightMenuIndicatorCoroutine && rightMenuIndicatorCoroutine().done) {
rightMenuIndicatorCoroutine = null;
Expand Down
42 changes: 0 additions & 42 deletions src/inflators/text.js

This file was deleted.

127 changes: 127 additions & 0 deletions src/inflators/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { addComponent } from "bitecs";
import { BackSide, DoubleSide, FrontSide } from "three";
import { Text as TroikaText } from "troika-three-text";
import { HubsWorld } from "../app";
import { TextTag } from "../bit-components";
import { addObject3DComponent } from "../utils/jsx-entity";

export type TextParams = {
value: string;
anchorX?: "left" | "center" | "right";
anchorY?: "top" | "top-baseline" | "top-cap" | "top-ex" | "middle" | "bottom-baseline" | "bottom";
clipRect?: [number, number, number, number] | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to support both null and undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't "?" imply that accepts undeinfed?

color?: string;
curveRadius?: number;
depthOffset?: number;
direction?: "auto" | "ltr" | "trl";
fillOpacity?: number;
fontUrl?: string | null;
fontSize?: number;
glyphGeometryDetail?: number;
gpuAccelerateSDF?: boolean;
letterSpacing?: number;
lineHeight?: number | "normal";
maxWidth?: number;
opacity?: number;
outlineBlur?: number | `${number}%`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never seen this type definition before. What does ${number}% mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be similar to regex. (If I understand correctly) ${number}% means number string that ends with "%", like "25%" or "0.5%".

https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#handbook-content
https://stackoverflow.com/questions/51445767/how-to-define-a-regex-matched-string-type-in-typescript

Copy link
Contributor

@johnshaughnessy johnshaughnessy Feb 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know about this before. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know that was supported either. Neat!

outlineColor?: string;
outlineOffsetX?: number | `${number}%`;
outlineOffsetY?: number | `${number}%`;
outlineOpacity?: number;
outlineWidth?: number | `${number}%`;
overflowWrap?: "normal" | "break-word";
sdfGlyphSize?: number | null;
side?: "front" | "back" | "double";
strokeColor?: string;
strokeOpacity?: number;
strokeWidth?: number | `${number}%`;
textAlign?: "left" | "right" | "center" | "justify";
textIndent?: number;
whiteSpace?: "normal" | "nowrap";
};

const THREE_SIDES = {
front: FrontSide,
back: BackSide,
double: DoubleSide
};

const DEFAULTS: Required<TextParams> = {
anchorX: "center",
anchorY: "middle",
clipRect: null,
color: "#ffffff",
curveRadius: 0,
depthOffset: 0,
direction: "auto",
fillOpacity: 1.0,
fontUrl: null,
fontSize: 0.075,
glyphGeometryDetail: 1.0,
gpuAccelerateSDF: true,
letterSpacing: 0,
lineHeight: "normal",
maxWidth: Infinity,
opacity: 1.0,
outlineBlur: 0,
outlineColor: "#000000",
outlineOffsetX: 0,
outlineOffsetY: 0,
outlineOpacity: 1.0,
outlineWidth: 0,
overflowWrap: "normal",
sdfGlyphSize: null,
side: "front",
strokeColor: "grey",
strokeOpacity: 1.0,
strokeWidth: 0,
textAlign: "center",
textIndent: 0,
value: "",
whiteSpace: "normal"
};

export function inflateText(world: HubsWorld, eid: number, params: TextParams) {
const requiredParams = Object.assign({}, DEFAULTS, params) as Required<TextParams>;
const text = new TroikaText();
text.material!.toneMapped = false;

text.text = requiredParams.value;
text.material!.side = THREE_SIDES[requiredParams.side];
text.material!.opacity = requiredParams.opacity;
text.font = requiredParams.fontUrl;

text.anchorX = requiredParams.anchorX;
text.anchorY = requiredParams.anchorY;
text.clipRect = requiredParams.clipRect;
text.color = requiredParams.color;
text.curveRadius = requiredParams.curveRadius;
text.depthOffset = requiredParams.depthOffset;
text.direction = requiredParams.direction;
text.fillOpacity = requiredParams.fillOpacity;
text.fontSize = requiredParams.fontSize;
text.glyphGeometryDetail = requiredParams.glyphGeometryDetail;
text.gpuAccelerateSDF = requiredParams.gpuAccelerateSDF;
text.letterSpacing = requiredParams.letterSpacing;
text.lineHeight = requiredParams.lineHeight;
text.maxWidth = requiredParams.maxWidth;
text.outlineBlur = requiredParams.outlineBlur;
text.outlineColor = requiredParams.outlineColor;
text.outlineOffsetX = requiredParams.outlineOffsetX;
text.outlineOffsetY = requiredParams.outlineOffsetY;
text.outlineOpacity = requiredParams.outlineOpacity;
text.outlineWidth = requiredParams.outlineWidth;
text.overflowWrap = requiredParams.overflowWrap;
text.sdfGlyphSize = requiredParams.sdfGlyphSize;
text.strokeColor = requiredParams.strokeColor;
text.strokeOpacity = requiredParams.strokeOpacity;
text.strokeWidth = requiredParams.strokeWidth;
text.textAlign = requiredParams.textAlign;
text.textIndent = requiredParams.textIndent;
text.whiteSpace = requiredParams.whiteSpace;

text.sync();

addComponent(world, TextTag, eid);
addObject3DComponent(world, eid, text);
}
4 changes: 4 additions & 0 deletions src/systems/hubs-systems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import { particleEmitterSystem } from "../bit-systems/particle-emitter";
import { audioEmitterSystem } from "../bit-systems/audio-emitter-system";
import { audioZoneSystem } from "../bit-systems/audio-zone-system";
import { audioDebugSystem } from "../bit-systems/audio-debug-system";
import { textSystem } from "../bit-systems/text";

declare global {
interface Window {
Expand Down Expand Up @@ -251,6 +252,9 @@ export function mainTick(xrFrame: XRFrame, renderer: WebGLRenderer, scene: Scene
hubsSystems.nameTagSystem.tick();
simpleWaterSystem(world);

// All systems that update text properties should run before this
textSystem(world);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its very crude but we may want to add a blank line above this system to make it clear its a bit "special". Or maybe better to just add a comment like // all systems that update text properties should run before this


videoTextureSystem(world);
audioDebugSystem(world);

Expand Down
4 changes: 2 additions & 2 deletions src/systems/remove-object3D-system.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
SimpleWater,
Skybox,
Slice9,
Text,
TextTag,
VideoMenu,
ParticleEmitterTag,
NavMesh
Expand Down Expand Up @@ -50,7 +50,7 @@ const cleanupGLTFs = cleanupObjOnExit(GLTFModel, obj => {
}
});
const cleanupLights = cleanupObjOnExit(LightTag, obj => obj.dispose());
const cleanupTexts = cleanupObjOnExit(Text, obj => obj.dispose());
const cleanupTexts = cleanupObjOnExit(TextTag, obj => obj.dispose());
const cleanupMediaFrames = cleanupObjOnExit(MediaFrame, obj => obj.geometry.dispose());
const cleanupAudioEmitters = cleanupObjOnExit(AudioEmitter, obj => cleanupAudio(obj));
const cleanupImages = cleanupObjOnExit(MediaImage, obj => {
Expand Down
4 changes: 2 additions & 2 deletions src/utils/jsx-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { inflateVideoLoader, VideoLoaderParams } from "../inflators/video-loader
import { inflateImageLoader, ImageLoaderParams } from "../inflators/image-loader";
import { inflateModel, ModelParams } from "../inflators/model";
import { inflateSlice9 } from "../inflators/slice9";
import { inflateText } from "../inflators/text";
import { TextParams, inflateText } from "../inflators/text";
import {
BackgroundParams,
EnvironmentSettingsParams,
Expand Down Expand Up @@ -328,7 +328,7 @@ export interface JSXComponentData extends ComponentData {
sceneLoader?: { src: string };
mediaFrame?: any;
object3D?: any;
text?: any;
text?: TextParams;
model?: ModelParams;
networkDebug?: boolean;
waypointPreview?: boolean;
Expand Down
34 changes: 32 additions & 2 deletions types/troika-three-text.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
declare module "troika-three-text" {
import { Mesh } from "three";
import { Color, Material, Mesh } from "three";

export interface Text extends Mesh {
export class Text extends Mesh {
text: string;
anchorX: number | `${number}%` | 'left' | 'center' | 'right';
anchorY: number | `${number}%` | 'top' | 'top-baseline' | 'top-cap' | 'top-ex' | 'middle' | 'bottom-baseline' | 'bottom';
clipRect: [number, number, number, number] | null;
color: string | number | Color | null;
curveRadius: number;
depthOffset: number;
direction: string;
fillOpacity: number;
font: string | null;
fontSize: number;
glyphGeometryDetail: number;
gpuAccelerateSDF: boolean;
letterSpacing: number;
lineHeight: number | 'normal';
material: Material | null;
maxWidth: number;
outlineBlur: number | `${number}%`;
outlineColor: string | number | Color;
outlineOffsetX: number | `${number}%`;
outlineOffsetY: number | `${number}%`;
outlineOpacity: number;
outlineWidth: number | `${number}%`;
overflowWrap: 'normal' | 'break-word';
sdfGlyphSize: number | null;
strokeColor: string | number | Color;
strokeOpacity: number;
strokeWidth: number | `${number}%`;
textAlign: 'left' | 'right' | 'center' | 'justify';
textIndent: number;
whiteSpace: 'normal' | 'nowrap';
sync(callback?: () => void): void;
}

Expand Down