This commit is contained in:
James Kane 2021-11-23 13:19:46 -06:00
Родитель 3fc3379cb7
Коммит a1146c8127
5 изменённых файлов: 610 добавлений и 0 удалений

Просмотреть файл

@ -104,6 +104,7 @@
"styled-icons": "^8.4.2",
"three": "https://github.com/MozillaReality/three.js.git#0f9b0024725f0dd917caa54c2934a4ba1fc12c4f",
"three-mesh-bvh": "^0.1.4",
"troika-three-text": "^0.44.0",
"url-toolkit": "^2.1.6",
"use-debounce": "^3.4.0",
"use-http": "^0.2.4",

Просмотреть файл

@ -64,6 +64,9 @@ import ArchitectureKitSource from "./ui/assets/sources/ArchitectureKitSource";
import RockKitSource from "./ui/assets/sources/RockKitSource";
import HubsSoundPackSource from "./ui/assets/sources/HubsSoundPackSource";
import TroikaTextNode from "./editor/nodes/TroikaTextNode";
import TroikaTextNodeEditor from "./ui/properties/TroikaTextNodeEditor";
export function createEditor(api, settings) {
const editor = new Editor(api, settings);
@ -93,6 +96,7 @@ export function createEditor(api, settings) {
editor.registerNode(ScenePreviewCameraNode, ScenePreviewCameraNodeEditor);
editor.registerNode(MediaFrameNode, MediaFrameNodeEditor);
editor.registerNode(AudioZoneNode, AudioZoneNodeEditor);
editor.registerNode(TroikaTextNode, TroikaTextNodeEditor);
editor.registerSource(new ElementsSource(editor));
editor.registerSource(new MyAssetsSource(editor));

Просмотреть файл

@ -0,0 +1,217 @@
import { Color, Object3D } from "three";
import EditorNodeMixin from "../../../../src/editor/nodes/EditorNodeMixin";
import { Text } from "troika-three-text";
export default class TroikaTextNode extends EditorNodeMixin(Object3D) {
static legacyComponentName = "troika-text";
static nodeName = "Troika Text";
constructor(editor) {
super(editor);
this.editor = editor;
this.scene = editor.scene;
this.text = "Text";
this.anchorX = "left";
this.anchorY = "top";
this.color = new Color();
this.curveRadius = 0;
this.depthOffset = 0;
this.direction = "auto";
this.fillOpacity = 1;
this.font;
this.fontSize = 10;
this.textAlign = "left";
this.letterSpacing = 0;
// this.clipRect = ""; // Todo, implement later
this.lineHeight = 1;
this.outlineBlur = 0;
this.outlineColor = new Color();
this.outlineOffsetX = 0;
this.outlineOffsetY = 0;
this.outlineOpacity = 1;
this.outlineWidth = 0;
this.overflowWrap = "normal";
this.strokeColor = new Color();
this.strokeOpacity = 1;
this.strokeWidth = 0;
this.textAlign = "left";
this.textIndent = 0;
this.whiteSpace = "normal";
this.maxWidth = 9999; // The serialize method can't seem to handle a value of Infinity, just feed it ~10k meters
this.troikaText = new Text();
}
onChange(change) {
// change is undefined when serialize returns - but whole node has been updated
// change will be named if coming from a UI input
if (change === "position") this.troikaText.position.copy(this.position);
else if (change === "scale") this.troikaText.scale.copy(this.position);
else if (change === "rotation") this.troikaText.rotation.copy(this.rotation);
else if (change === "color" || change === "strokeColor" || change === "outlineColor") {
this.troikaText[change] = new Color(this[change]);
} else this.troikaText[change] = this[change];
this.troikaText.sync();
}
onRemove() {
this.scene.remove(this.troikaText);
this.troikaText.dispose();
}
onAdd() {
this.troikaText = new Text();
this.troikaText.text = this.text;
this.troikaText.anchorX = this.anchorX;
this.troikaText.anchorY = this.anchorY;
this.troikaText.color = this.color;
this.troikaText.curveRadius = this.curveRadius;
this.troikaText.depthOffset = this.depthOffset;
this.troikaText.direction = this.direction;
this.troikaText.fillOpacity = this.fillOpacity;
this.troikaText.font = this.font;
this.troikaText.fontSize = this.fontSize;
this.troikaText.textAlign = this.textAlign;
this.troikaText.letterSpacing = this.letterSpacing;
this.troikaText.lineHeight = this.lineHeight;
this.troikaText.outlineBlur = this.outlineBlur;
this.troikaText.outlineColor = this.outlineColor;
this.troikaText.outlineOffsetX = this.outlineOffsetX;
this.troikaText.outlineOffsetY = this.outlineOffsetY;
this.troikaText.outlineOpacity = this.outlineOpacity;
this.troikaText.outlineWidth = this.outlineWidth;
this.troikaText.overflowWrap = this.overflowWrap;
this.troikaText.strokeColor = this.strokeColor;
this.troikaText.strokeOpacity = this.strokeOpacity;
this.troikaText.strokeWidth = this.strokeWidth;
this.troikaText.textAlign = this.textAlign;
this.troikaText.textIndent = this.textIndent;
this.troikaText.whiteSpace = this.whiteSpace;
this.troikaText.maxWidth = this.maxWidth;
this.troikaText.position.copy(this.position);
this.troikaText.rotation.copy(this.rotation);
this.troikaText.scale.copy(this.scale);
this.scene.add(this.troikaText);
this.troikaText.sync();
}
copy(source) {
super.copy(source, false);
this.troikaText = new Text();
this.troikaText.text = source.text;
this.troikaText.anchorX = source.anchorX;
this.troikaText.anchorY = source.anchorY;
this.troikaText.color = source.color;
this.troikaText.curveRadius = source.curveRadius;
this.troikaText.depthOffset = source.depthOffset;
this.troikaText.direction = source.direction;
this.troikaText.fillOpacity = source.fillOpacity;
this.troikaText.font = source.font;
this.troikaText.fontSize = source.fontSize;
this.troikaText.textAlign = source.textAlign;
this.troikaText.letterSpacing = source.letterSpacing;
this.troikaText.lineHeight = source.lineHeight;
this.troikaText.outlineBlur = source.outlineBlur;
this.troikaText.outlineColor = source.outlineColor;
this.troikaText.outlineOffsetX = source.outlineOffsetX;
this.troikaText.outlineOffsetY = source.outlineOffsetY;
this.troikaText.outlineOpacity = source.outlineOpacity;
this.troikaText.outlineWidth = source.outlineWidth;
this.troikaText.overflowWrap = source.overflowWrap;
this.troikaText.strokeColor = source.strokeColor;
this.troikaText.strokeOpacity = source.strokeOpacity;
this.troikaText.strokeWidth = source.strokeWidth;
this.troikaText.textAlign = source.textAlign;
this.troikaText.textIndent = source.textIndent;
this.troikaText.whiteSpace = source.whiteSpace;
this.troikaText.maxWidth = source.maxWidth;
return this;
}
static async deserialize(editor, json) {
const node = await super.deserialize(editor, json);
const props = json.components.find(c => c.name === "troika-text").props;
Object.keys(props).forEach(key => {
if (key === "position") node.troikaText.position.copy(props.position);
else if (key === "scale") node.troikaText.scale.copy(props.scale);
else if (key === "rotation" || key === "quaternion") node.troikaText.rotation.copy(props.rotation);
else if (key === "color" || key === "strokeColor" || key === "outlineColor") {
node[key] = new Color(props[key]);
node.troikaText[key] = new Color(props[key]);
} else {
node[key] = props[key];
node.troikaText[key] = props[key];
}
});
node.troikaText.sync();
return node;
}
serialize() {
const serialized = super.serialize({
"troika-text": {
text: this.text,
anchorX: this.anchorX,
anchorY: this.anchorY,
color: this.color,
curveRadius: this.curveRadius,
depthOffset: this.depthOffset,
direction: this.direction,
fillOpacity: this.fillOpacity,
font: this.font,
fontSize: this.fontSize,
letterSpacing: this.letterSpacing,
lineHeight: this.lineHeight,
outlineBlur: this.outlineBlur,
outlineColor: this.outlineColor,
outlineOffsetX: this.outlineOffsetX,
outlineOffsetY: this.outlineOffsetY,
outlineOpacity: this.outlineOpacity,
outlineWidth: this.outlineWidth,
overflowWrap: this.overflowWrap,
strokeColor: this.strokeColor,
strokeOpacity: this.strokeOpacity,
strokeWidth: this.strokeWidth,
textAlign: this.textAlign,
textIndent: this.textIndent,
whiteSpace: this.whiteSpace,
maxWidth: this.maxWidth
}
});
return serialized;
}
prepareForExport() {
super.prepareForExport();
this.addGLTFComponent("troika-text", {
text: this.troikaText.text,
anchorX: this.troikaText.anchorX,
anchorY: this.troikaText.anchorY,
color: this.troikaText.color,
curveRadius: this.troikaText.curveRadius,
depthOffset: this.troikaText.depthOffset,
direction: this.troikaText.direction,
fillOpacity: this.troikaText.fillOpacity,
font: this.troikaText.font,
fontSize: this.troikaText.fontSize,
letterSpacing: this.troikaText.letterSpacing,
lineHeight: this.troikaText.lineHeight,
outlineBlur: this.troikaText.outlineBlur,
outlineColor: this.troikaText.outlineColor,
outlineOffsetX: this.troikaText.outlineOffsetX,
outlineOffsetY: this.troikaText.outlineOffsetY,
outlineOpacity: this.troikaText.outlineOpacity,
outlineWidth: this.troikaText.outlineWidth,
overflowWrap: this.troikaText.overflowWrap,
strokeColor: this.troikaText.strokeColor,
strokeOpacity: this.troikaText.strokeOpacity,
strokeWidth: this.troikaText.strokeWidth,
textAlign: this.troikaText.textAlign,
textIndent: this.troikaText.textIndent,
whiteSpace: this.troikaText.whiteSpace,
maxWidth: this.troikaText.maxWidth
});
}
}

Просмотреть файл

@ -0,0 +1,357 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import NodeEditor from "../../../../src/ui/properties/NodeEditor";
import InputGroup from "../../../../src/ui/inputs/InputGroup";
import StringInput from "../../../../src/ui/inputs/StringInput";
import { AlignCenter } from "styled-icons/fa-solid/AlignCenter";
import SelectInput from "@src/ui/inputs/SelectInput";
import NumericInputGroup from "@src/ui/inputs/NumericInputGroup";
import ColorInput from "@src/ui/inputs/ColorInput";
const textAlignments = [
{
label: "left",
value: "left"
},
{
label: "right",
value: "right"
},
{
label: "center",
value: "center"
},
{
label: "justify",
value: "justify"
}
];
const xAnchors = [
{
label: "left",
value: "left"
},
{
label: "right",
value: "right"
},
{
label: "center",
value: "center"
},
{
label: "align",
value: "align"
}
];
const yAnchors = [
{
label: "top",
value: "top"
},
{
label: "top-baseline",
value: "top-baseline"
},
{
label: "middle",
value: "middle"
},
{
label: "bottom-baseline",
value: "bottom-baseline"
},
{
label: "bottom",
value: "bottom"
}
];
const overflowWraps = [
{
label: "normal",
value: "normal"
},
{
label: "break-word",
value: "break-word"
}
];
const whiteSpaces = [
{
label: "normal",
value: "normal"
},
{
label: "nowrap",
value: "nowrap"
}
];
export default class TroikaTextNodeEditor extends Component {
static propTypes = {
editor: PropTypes.object,
node: PropTypes.object,
multiEdit: PropTypes.bool
};
static iconComponent = AlignCenter;
static description = "Creates a 3D text element using the troika-three-text library.";
constructor(props) {
super(props);
// TODO: determine proper use of UI component state
this.state = {
options: []
};
}
componentDidMount() {
const options = [];
const sceneNode = this.props.editor.scene;
sceneNode.traverse(o => {
if (o.isNode && o !== sceneNode) {
options.push({ label: o.name, value: o.uuid, nodeName: o.nodeName });
}
});
this.setState({ options });
}
onChangeProperty = (property, value) => {
this.props.editor.setPropertySelected(property, value);
};
render() {
const { node } = this.props;
return (
<NodeEditor description={TroikaTextNodeEditor.description} {...this.props}>
<InputGroup name="Text" info="The text you want to display in your scene">
<StringInput
value={node.text}
onChange={value => {
this.onChangeProperty("text", value);
}}
/>
</InputGroup>
<InputGroup
name="Font URL"
info="URL for remotely-hosted .otf, .ttf or .woff assets. WARNING: Watch for CORS errors."
>
<StringInput
value={node.font}
onChange={value => {
this.onChangeProperty("font", value);
}}
/>
</InputGroup>
<NumericInputGroup
name="Font Size"
info="The em-height at which to render the font, in local world units."
min={0}
smallStep={0.01}
mediumStep={0.1}
largeStep={1}
value={node.fontSize}
onChange={value => {
this.onChangeProperty("fontSize", value);
}}
unit="cd"
/>
<InputGroup name="Color" info="Set the color of the text's material.">
<ColorInput
value={node.color}
onChange={value => {
this.onChangeProperty("color", value);
}}
/>
</InputGroup>
<NumericInputGroup
name="Max Width"
info="The maximum width of the text block, above which text may start wrapping according to the whiteSpace and overflowWrap properties."
min={0}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
value={node.maxWidth}
onChange={value => {
this.onChangeProperty("maxWidth", value);
}}
unit="m"
/>
<NumericInputGroup
name="Letter Spacing"
info="Sets a uniform adjustment to spacing between letters after kerning is applied, in local world units. Positive numbers increase spacing and negative numbers decrease it."
min={-100}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
value={node.letterSpacing}
onChange={value => {
this.onChangeProperty("letterSpacing", value);
}}
unit="em"
/>
<NumericInputGroup
name="Line Height"
info="Sets the height of each line of text. Can either be 'normal' which chooses a reasonable height based on the chosen font's ascender/descender metrics, or a number that is interpreted as a multiple of the fontSize."
min={0}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
value={node.lineHeight}
onChange={value => {
this.onChangeProperty("lineHeight", value);
}}
unit="x"
/>
<InputGroup
name="Text Align"
info="The horizontal alignment of each line of text within the overall text bounding box."
>
<SelectInput
label="Text Align"
options={textAlignments}
value={node.textAlign}
onChange={value => {
this.onChangeProperty("textAlign", value);
}}
/>
</InputGroup>
<InputGroup
name="Anchors"
info="Defines the horizontal and vertical position in the text block that should line up with the local origin."
>
<SelectInput
label="Anchor X"
options={xAnchors}
value={node.anchorX}
onChange={value => {
this.onChangeProperty("anchorX", value);
}}
/>
<SelectInput
label="Anchor Y"
options={yAnchors}
value={node.anchorY}
onChange={value => {
this.onChangeProperty("anchorY", value);
}}
/>
</InputGroup>
<InputGroup
name="Overflow Wrap"
info="Defines how text wraps. Can be either 'normal' to break at whitespace characters, or 'break-word' to allow breaking within words."
>
<SelectInput
options={overflowWraps}
value={node.overflowWrap}
onChange={value => {
this.onChangeProperty("overflowWrap", value);
}}
/>
</InputGroup>
<NumericInputGroup
name="Text Indent"
info="An indentation applied to the first character of each hard newline. Behaves like CSS text-indent."
min={0}
smallStep={0.1}
mediumStep={0.5}
largeStep={1}
value={node.textIndent}
onChange={value => {
this.onChangeProperty("textIndent", value);
}}
unit="em"
/>
<NumericInputGroup
name="Curve Radius"
info="Defines a cylindrical radius along which the text's plane will be curved."
min={-100}
smallStep={0.1}
mediumStep={1}
largeStep={5}
value={node.curveRadius}
onChange={value => {
this.onChangeProperty("curveRadius", value);
}}
unit="degrees"
/>
<InputGroup
name="White Space"
info="Defines whether text should wrap when a line reaches the maxWidth. Can be either 'normal', to allow wrapping according to the overflowWrap property, or 'nowrap' to prevent wrapping."
>
<SelectInput
options={whiteSpaces}
value={node.whiteSpace}
onChange={value => {
this.onChangeProperty("whiteSpace", value);
}}
/>
</InputGroup>
<InputGroup
name="Stroke Color"
info="The color of the text's interior stroke lines when strokeWidth is non-zero."
>
<ColorInput
value={node.strokeColor}
onChange={value => {
this.onChangeProperty("strokeColor", value);
}}
/>
</InputGroup>
<NumericInputGroup
name="Stroke Width"
info="Sets the width of a stroke drawn inside the edge of each text glyph, using the strokeColor and strokeOpacity. The width can be specified as either an absolute number in local units, or as a percentage string e.g. '10%' which is interpreted as a percentage of the fontSize."
min={0}
max={1}
smallStep={1}
mediumStep={5}
largeStep={10}
value={node.strokeWidth}
onChange={value => {
this.onChangeProperty("strokeWidth", value);
}}
unit="0-1"
/>
<NumericInputGroup
name="Stroke Opacity"
info="The opacity of the text stroke, when strokeWidth is nonzero. Accepts a number from 0 to 1."
min={0}
max={1}
smallStep={0.01}
mediumStep={0.05}
largeStep={0.1}
value={node.strokeOpacity}
onChange={value => {
this.onChangeProperty("strokeOpacity", value);
}}
unit="0-1"
/>
</NodeEditor>
);
}
}

Просмотреть файл

@ -3156,6 +3156,13 @@ bfj@^6.1.1:
hoopy "^0.1.4"
tryer "^1.0.1"
bidi-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.2.tgz#1a497a762c2ddea377429d2649c9ce0f8a91527f"
integrity sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==
dependencies:
require-from-string "^2.0.2"
big.js@^3.1.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
@ -11575,6 +11582,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
@ -13234,6 +13246,25 @@ trim@0.0.1:
resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0=
troika-three-text@^0.44.0:
version "0.44.0"
resolved "https://registry.yarnpkg.com/troika-three-text/-/troika-three-text-0.44.0.tgz#7c1a785c1aea9acc7631651acac97f2dbed2f26f"
integrity sha512-YwqXczjXQ4yq2a2ufO9icOIjeJutE/ODS8PHmmt/WAzVFqoiqeemclp/Ewiqm0+sdI1KnWRm6lj8df/zmhU3Og==
dependencies:
bidi-js "^1.0.2"
troika-three-utils "^0.44.0"
troika-worker-utils "^0.44.0"
troika-three-utils@^0.44.0:
version "0.44.0"
resolved "https://registry.yarnpkg.com/troika-three-utils/-/troika-three-utils-0.44.0.tgz#c19bcbedb08bff96b8a38cf8b4a60da3b12bb44b"
integrity sha512-gaEpqrlWnkrVU5UgUx+YZTC8NrhsA2Tt6zEIbn3WNuom7pLtrgjuHpAM72gif7DoYdOWEyFco3Zb6rpJh9Fodg==
troika-worker-utils@^0.44.0:
version "0.44.0"
resolved "https://registry.yarnpkg.com/troika-worker-utils/-/troika-worker-utils-0.44.0.tgz#a236dc004b7a3c187ae8f14a6b497e54661e12c8"
integrity sha512-/ETcH1rUoO9hVBL6Ifea2WOoGPw90ncrk8b8SJKTLtzcQvEWRIZ4eUxlVCtU93fLechCV+DWPs1y8+Bjh1WaJg==
trough@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e"