Fixed up menu system.
This commit is contained in:
parent
16f9f7f92c
commit
e43a9e9d41
@ -96,11 +96,13 @@ export class Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public disable() {
|
public disable() {
|
||||||
|
this.scene.preventDefaultOnPointerDown = true;
|
||||||
this.controller.motionController.rootMesh.setEnabled(false)
|
this.controller.motionController.rootMesh.setEnabled(false)
|
||||||
this.controller.pointer.setEnabled(false);
|
this.controller.pointer.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enable() {
|
public enable() {
|
||||||
|
this.scene.preventDefaultOnPointerDown = false;
|
||||||
this.controller.motionController.rootMesh.setEnabled(true);
|
this.controller.motionController.rootMesh.setEnabled(true);
|
||||||
this.controller.pointer.setEnabled(true)
|
this.controller.pointer.setEnabled(true)
|
||||||
}
|
}
|
||||||
@ -112,7 +114,7 @@ export class Base {
|
|||||||
}
|
}
|
||||||
const template = mesh?.metadata?.template;
|
const template = mesh?.metadata?.template;
|
||||||
if (!template) {
|
if (!template) {
|
||||||
if (mesh?.id == "handle") {
|
if (mesh?.metadata?.handle == true) {
|
||||||
mesh && mesh.setParent(this.controller.motionController.rootMesh);
|
mesh && mesh.setParent(this.controller.motionController.rootMesh);
|
||||||
this.grabbedMesh = mesh;
|
this.grabbedMesh = mesh;
|
||||||
} else {
|
} else {
|
||||||
@ -163,7 +165,7 @@ export class Base {
|
|||||||
|
|
||||||
private toolboxHandleWasGrabbed(mesh: AbstractMesh): boolean {
|
private toolboxHandleWasGrabbed(mesh: AbstractMesh): boolean {
|
||||||
if (!mesh?.metadata?.template
|
if (!mesh?.metadata?.template
|
||||||
&& mesh?.id == "handle") {
|
&& mesh?.metadata?.handle == true) {
|
||||||
this.grabbedMesh = null;
|
this.grabbedMesh = null;
|
||||||
this.previousParentId = null;
|
this.previousParentId = null;
|
||||||
mesh.setParent(null);
|
mesh.setParent(null);
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import {Observable, Scene, WebXRDefaultExperience} from "@babylonjs/core";
|
import {MeshBuilder, Observable, Scene, Vector3, WebXRDefaultExperience} from "@babylonjs/core";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import {AdvancedDynamicTexture, InputText} from "@babylonjs/gui";
|
import {AdvancedDynamicTexture, Control, InputText, VirtualKeyboard} from "@babylonjs/gui";
|
||||||
import {ControllerEventType, Controllers} from "../controllers/controllers";
|
import {ControllerEventType, Controllers} from "../controllers/controllers";
|
||||||
|
import {setMenuPosition} from "../util/functions/setMenuPosition";
|
||||||
|
|
||||||
export type TextEvent = {
|
export type TextEvent = {
|
||||||
text: string;
|
text: string;
|
||||||
@ -15,33 +16,71 @@ export type InputTextViewOptions = {
|
|||||||
|
|
||||||
export class InputTextView {
|
export class InputTextView {
|
||||||
public readonly onTextObservable: Observable<TextEvent> = new Observable<TextEvent>();
|
public readonly onTextObservable: Observable<TextEvent> = new Observable<TextEvent>();
|
||||||
private readonly text: string;
|
private readonly text: string = "";
|
||||||
private readonly scene: Scene;
|
private readonly scene: Scene;
|
||||||
private readonly controllers: Controllers;
|
private readonly controllers: Controllers;
|
||||||
private readonly xr: WebXRDefaultExperience;
|
private readonly xr: WebXRDefaultExperience;
|
||||||
|
|
||||||
constructor(options: InputTextViewOptions) {
|
constructor(text: string, xr: WebXRDefaultExperience, scene: Scene) {
|
||||||
if (options.text) {
|
this.text = text ? text : "";
|
||||||
this.text = options.text;
|
this.xr = xr;
|
||||||
}
|
this.scene = scene;
|
||||||
if (options.xr) {
|
|
||||||
this.xr = options.xr;
|
|
||||||
}
|
|
||||||
if (options.scene) {
|
|
||||||
this.scene = options.scene;
|
|
||||||
}
|
|
||||||
if (options.controllers) {
|
|
||||||
this.controllers = options.controllers;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public show() {
|
public show() {
|
||||||
|
this.showVirtualKeyboard();
|
||||||
if ((this.xr as WebXRDefaultExperience).baseExperience?.sessionManager?.inXRSession) {
|
/*if ((this.xr as WebXRDefaultExperience).baseExperience?.sessionManager?.inXRSession) {
|
||||||
this.showXr();
|
this.showXr();
|
||||||
} else {
|
} else {
|
||||||
this.showWeb();
|
this.showWeb();
|
||||||
}
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public showVirtualKeyboard() {
|
||||||
|
|
||||||
|
|
||||||
|
const inputMesh = MeshBuilder.CreatePlane("input", {width: 1, height: .5}, this.scene);
|
||||||
|
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(inputMesh, 2048, 1024, false);
|
||||||
|
|
||||||
|
const input = new InputText();
|
||||||
|
|
||||||
|
input.width = 0.5;
|
||||||
|
input.maxWidth = 0.5;
|
||||||
|
input.height = "64px";
|
||||||
|
input.text = this.text;
|
||||||
|
|
||||||
|
input.fontSize = "32px";
|
||||||
|
input.color = "white";
|
||||||
|
input.background = "black";
|
||||||
|
input.thickness = 3;
|
||||||
|
input.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
|
||||||
|
advancedTexture.addControl(input);
|
||||||
|
|
||||||
|
const keyboard = VirtualKeyboard.CreateDefaultLayout();
|
||||||
|
keyboard.scaleY = 2;
|
||||||
|
keyboard.scaleX = 2;
|
||||||
|
keyboard.transformCenterY = 0;
|
||||||
|
keyboard.transformCenterX = .5;
|
||||||
|
keyboard.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
|
||||||
|
keyboard.paddingTop = "70px"
|
||||||
|
keyboard.height = "768px";
|
||||||
|
keyboard.fontSizeInPixels = 24;
|
||||||
|
advancedTexture.addControl(keyboard);
|
||||||
|
keyboard.connect(input);
|
||||||
|
keyboard.isVisible = true;
|
||||||
|
keyboard.isEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
|
keyboard.onKeyPressObservable.add((key) => {
|
||||||
|
if (key === '↵') {
|
||||||
|
this.onTextObservable.notifyObservers({text: input.text});
|
||||||
|
input.dispose();
|
||||||
|
keyboard.dispose();
|
||||||
|
advancedTexture.dispose();
|
||||||
|
inputMesh.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setMenuPosition(inputMesh, this.scene, new Vector3(0, .4, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public showWeb() {
|
public showWeb() {
|
||||||
@ -59,6 +98,7 @@ export class InputTextView {
|
|||||||
this.onTextObservable.notifyObservers({text: textInput.text});
|
this.onTextObservable.notifyObservers({text: textInput.text});
|
||||||
textInput.dispose();
|
textInput.dispose();
|
||||||
advancedTexture.dispose();
|
advancedTexture.dispose();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import {AdvancedDynamicTexture, RadioGroup, SelectionPanel} from "@babylonjs/gui";
|
import {AdvancedDynamicTexture, RadioGroup, SelectionPanel, StackPanel} from "@babylonjs/gui";
|
||||||
import {AbstractMesh, MeshBuilder, Scene, WebXRDefaultExperience} from "@babylonjs/core";
|
import {MeshBuilder, Scene, Vector3, WebXRDefaultExperience} from "@babylonjs/core";
|
||||||
import {AppConfig} from "../util/appConfig";
|
import {AppConfig} from "../util/appConfig";
|
||||||
import {ControllerEventType, Controllers} from "../controllers/controllers";
|
import {ControllerEventType, Controllers} from "../controllers/controllers";
|
||||||
import {DiaSounds} from "../util/diaSounds";
|
import {DiaSounds} from "../util/diaSounds";
|
||||||
import {AbstractMenu} from "./abstractMenu";
|
import {AbstractMenu} from "./abstractMenu";
|
||||||
import {setMenuPosition} from "../util/functions/setMenuPosition";
|
import {setMenuPosition} from "../util/functions/setMenuPosition";
|
||||||
|
import {MenuHandle} from "./menuHandle";
|
||||||
|
|
||||||
export class ConfigMenu extends AbstractMenu {
|
export class ConfigMenu extends AbstractMenu {
|
||||||
private sounds: DiaSounds;
|
private sounds: DiaSounds;
|
||||||
private configPlane: AbstractMesh = null;
|
|
||||||
|
|
||||||
private yObserver;
|
private yObserver;
|
||||||
private config: AppConfig;
|
private config: AppConfig;
|
||||||
@ -42,44 +42,68 @@ export class ConfigMenu extends AbstractMenu {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handle: MenuHandle;
|
||||||
|
|
||||||
public toggle() {
|
public toggle() {
|
||||||
if (this.configPlane) {
|
if (this.handle) {
|
||||||
|
this.handle.mesh.dispose(false, true);
|
||||||
this.sounds.exit.play();
|
this.sounds.exit.play();
|
||||||
this.configPlane.dispose();
|
this.handle = null;
|
||||||
this.configPlane = null;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.sounds.enter.play();
|
this.sounds.enter.play();
|
||||||
const width = .25;
|
const configPlane = MeshBuilder
|
||||||
const height = .75;
|
|
||||||
const res = 256;
|
|
||||||
const heightPixels = Math.round((height / width) * res);
|
|
||||||
this.configPlane = MeshBuilder
|
|
||||||
.CreatePlane("gridSizePlane",
|
.CreatePlane("gridSizePlane",
|
||||||
{
|
{
|
||||||
width: .25,
|
width: .6,
|
||||||
height: .75
|
height: .3
|
||||||
}, this.scene);
|
}, this.scene);
|
||||||
const configTexture = AdvancedDynamicTexture.CreateForMesh(this.configPlane, res, heightPixels);
|
this.handle = new MenuHandle(configPlane);
|
||||||
configTexture.background = "white";
|
const configTexture = AdvancedDynamicTexture.CreateForMesh(configPlane, 2048, 1024);
|
||||||
const selectionPanel = new SelectionPanel("selectionPanel");
|
|
||||||
configTexture.addControl(selectionPanel)
|
|
||||||
this.buildGridSizeControl(selectionPanel);
|
|
||||||
this.buildCreateScaleControl(selectionPanel);
|
|
||||||
this.buildRotationSnapControl(selectionPanel);
|
|
||||||
this.buildTurnSnapControl(selectionPanel);
|
|
||||||
|
|
||||||
setMenuPosition(this.configPlane, this.scene);
|
configTexture.background = "white";
|
||||||
|
const columnPanel = new StackPanel('columns');
|
||||||
|
columnPanel.fontSize = "48px";
|
||||||
|
columnPanel.isVertical = false;
|
||||||
|
configTexture.addControl(columnPanel);
|
||||||
|
const selectionPanel1 = new SelectionPanel("selectionPanel1");
|
||||||
|
selectionPanel1.width = .5;
|
||||||
|
columnPanel.addControl(selectionPanel1);
|
||||||
|
this.buildGridSizeControl(selectionPanel1);
|
||||||
|
this.buildCreateScaleControl(selectionPanel1);
|
||||||
|
const selectionPanel2 = new SelectionPanel("selectionPanel2");
|
||||||
|
selectionPanel2.width = .5;
|
||||||
|
columnPanel.addControl(selectionPanel2);
|
||||||
|
this.buildRotationSnapControl(selectionPanel2);
|
||||||
|
this.buildTurnSnapControl(selectionPanel2);
|
||||||
|
configPlane.position.set(0, .2, 0);
|
||||||
|
setMenuPosition(this.handle.mesh, this.scene, new Vector3(0, .4, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private adjustRadio(radio: RadioGroup) {
|
||||||
|
radio.groupPanel.height = "512px";
|
||||||
|
radio.groupPanel.fontSize = "64px";
|
||||||
|
radio.groupPanel.children[0].height = "70px";
|
||||||
|
radio.groupPanel.paddingLeft = "16px";
|
||||||
|
radio.selectors.forEach((panel) => {
|
||||||
|
panel.children[0].height = "64px";
|
||||||
|
panel.children[0].width = "64px";
|
||||||
|
panel.children[1].paddingLeft = "32px";
|
||||||
|
panel.paddingTop = "16px";
|
||||||
|
panel.fontSize = "60px";
|
||||||
|
panel.adaptHeightToChildren = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCreateScaleControl(selectionPanel: SelectionPanel): RadioGroup {
|
private buildCreateScaleControl(selectionPanel: SelectionPanel): RadioGroup {
|
||||||
const radio = new RadioGroup("Create Scale");
|
const radio = new RadioGroup("Create Scale");
|
||||||
selectionPanel.addGroup(radio);
|
|
||||||
|
|
||||||
|
selectionPanel.addGroup(radio);
|
||||||
for (const [index, snap] of this.gridSnaps.entries()) {
|
for (const [index, snap] of this.gridSnaps.entries()) {
|
||||||
const selected = (this.config.current.createSnap == snap.value);
|
const selected = (this.config.current.createSnap == snap.value);
|
||||||
radio.addRadio(snap.label, this.createVal.bind(this), selected);
|
radio.addRadio(snap.label, this.createVal.bind(this), selected);
|
||||||
}
|
}
|
||||||
|
this.adjustRadio(radio);
|
||||||
return radio;
|
return radio;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +114,7 @@ export class ConfigMenu extends AbstractMenu {
|
|||||||
const selected = (this.config.current.rotateSnap == snap.value);
|
const selected = (this.config.current.rotateSnap == snap.value);
|
||||||
radio.addRadio(snap.label, this.rotateVal.bind(this), selected);
|
radio.addRadio(snap.label, this.rotateVal.bind(this), selected);
|
||||||
}
|
}
|
||||||
|
this.adjustRadio(radio);
|
||||||
return radio;
|
return radio;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +128,7 @@ export class ConfigMenu extends AbstractMenu {
|
|||||||
|
|
||||||
radio.addRadio(snap.label, this.gridVal.bind(this), selected);
|
radio.addRadio(snap.label, this.gridVal.bind(this), selected);
|
||||||
}
|
}
|
||||||
|
this.adjustRadio(radio);
|
||||||
return radio;
|
return radio;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +139,7 @@ export class ConfigMenu extends AbstractMenu {
|
|||||||
const selected = (this.config.current.turnSnap == snap.value);
|
const selected = (this.config.current.turnSnap == snap.value);
|
||||||
radio.addRadio(snap.label, this.turnVal.bind(this), selected);
|
radio.addRadio(snap.label, this.turnVal.bind(this), selected);
|
||||||
}
|
}
|
||||||
|
this.adjustRadio(radio);
|
||||||
return radio;
|
return radio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,7 +129,7 @@ export class EditMenu extends AbstractMenu {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.sounds.enter.play();
|
this.sounds.enter.play();
|
||||||
setMenuPosition(this.manager.rootContainer.children[0].node, this.scene);
|
setMenuPosition(this.manager.rootContainer.children[0].node, this.scene, new Vector3(0, .4, 0));
|
||||||
this.isVisible = true;
|
this.isVisible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -252,7 +252,7 @@ export class EditMenu extends AbstractMenu {
|
|||||||
if (mesh?.metadata?.text) {
|
if (mesh?.metadata?.text) {
|
||||||
text = mesh.metadata.text;
|
text = mesh.metadata.text;
|
||||||
}
|
}
|
||||||
const textInput = new InputTextView({xr: this.xr, text: text, controllers: this.controllers});
|
const textInput = new InputTextView(text, this.xr, this.scene);
|
||||||
|
|
||||||
textInput.show();
|
textInput.show();
|
||||||
textInput.onTextObservable.addOnce((value) => {
|
textInput.onTextObservable.addOnce((value) => {
|
||||||
@ -264,7 +264,7 @@ export class EditMenu extends AbstractMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showNewRelic() {
|
private showNewRelic() {
|
||||||
const inputTextView = new InputTextView({xr: this.xr, scene: this.scene, text: "New Relic"});
|
const inputTextView = new InputTextView('test', this.xr, this.scene);
|
||||||
inputTextView.show();
|
inputTextView.show();
|
||||||
inputTextView.onTextObservable.addOnce((value) => {
|
inputTextView.onTextObservable.addOnce((value) => {
|
||||||
const config = this.diagramManager.config.current;
|
const config = this.diagramManager.config.current;
|
||||||
|
|||||||
30
src/menus/menuHandle.ts
Normal file
30
src/menus/menuHandle.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import {AbstractMesh, Color3, MeshBuilder, Scene, StandardMaterial, TransformNode, Vector3} from "@babylonjs/core";
|
||||||
|
|
||||||
|
export class MenuHandle {
|
||||||
|
public mesh: AbstractMesh;
|
||||||
|
private menuTransformNode: TransformNode;
|
||||||
|
|
||||||
|
constructor(mesh: TransformNode) {
|
||||||
|
this.menuTransformNode = mesh;
|
||||||
|
this.buildHandle(mesh.getScene());
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildHandle(scene: Scene) {
|
||||||
|
const handle = MeshBuilder.CreateCapsule("handle", {
|
||||||
|
radius: .05,
|
||||||
|
orientation: Vector3.Right(),
|
||||||
|
height: .4
|
||||||
|
}, scene);
|
||||||
|
handle.id = "handle-" + this.menuTransformNode.id + "-mesh";
|
||||||
|
const handleMaterial = new StandardMaterial("handle-" + this.menuTransformNode.id, scene);
|
||||||
|
handleMaterial.diffuseColor = Color3.FromHexString("#EEEEFF");
|
||||||
|
handleMaterial.alpha = .8;
|
||||||
|
handle.material = handleMaterial;
|
||||||
|
handle.position = Vector3.Zero();
|
||||||
|
handle.metadata = {handle: true};
|
||||||
|
if (this.menuTransformNode) {
|
||||||
|
this.menuTransformNode.setParent(handle);
|
||||||
|
}
|
||||||
|
this.mesh = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import {Color3, Mesh, MeshBuilder, Observable, Scene, StandardMaterial, TransformNode, Vector3} from "@babylonjs/core";
|
import {Color3, Mesh, Observable, Scene, TransformNode, Vector3} from "@babylonjs/core";
|
||||||
|
|
||||||
import {Button3D, GUI3DManager, StackPanel3D, TextBlock} from "@babylonjs/gui";
|
import {Button3D, GUI3DManager, StackPanel3D, TextBlock} from "@babylonjs/gui";
|
||||||
import {ControllerEventType, Controllers} from "../controllers/controllers";
|
import {ControllerEventType, Controllers} from "../controllers/controllers";
|
||||||
import {setMenuPosition} from "../util/functions/setMenuPosition";
|
import {setMenuPosition} from "../util/functions/setMenuPosition";
|
||||||
import {buildColor} from "./functions/buildColor";
|
import {buildColor} from "./functions/buildColor";
|
||||||
|
import {MenuHandle} from "../menus/menuHandle";
|
||||||
|
|
||||||
export class Toolbox {
|
export class Toolbox {
|
||||||
private index = 0;
|
private index = 0;
|
||||||
@ -15,7 +16,7 @@ export class Toolbox {
|
|||||||
private readonly xObserver;
|
private readonly xObserver;
|
||||||
public readonly colorChangeObservable: Observable<{ oldColor: string, newColor: string }> =
|
public readonly colorChangeObservable: Observable<{ oldColor: string, newColor: string }> =
|
||||||
new Observable<{ oldColor: string; newColor: string }>()
|
new Observable<{ oldColor: string; newColor: string }>()
|
||||||
|
private handle: MenuHandle;
|
||||||
constructor(scene: Scene, controllers: Controllers) {
|
constructor(scene: Scene, controllers: Controllers) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.controllers = controllers;
|
this.controllers = controllers;
|
||||||
@ -23,20 +24,9 @@ export class Toolbox {
|
|||||||
this.manager = new GUI3DManager(scene);
|
this.manager = new GUI3DManager(scene);
|
||||||
this.manager.addControl(this.addPanel);
|
this.manager.addControl(this.addPanel);
|
||||||
this.node = new TransformNode("toolbox", this.scene);
|
this.node = new TransformNode("toolbox", this.scene);
|
||||||
const handle = MeshBuilder.CreateCapsule("handle", {
|
this.handle = new MenuHandle(this.node);
|
||||||
radius: .05,
|
|
||||||
orientation: Vector3.Right(),
|
|
||||||
height: .4
|
|
||||||
}, this.scene);
|
|
||||||
handle.id = "handle";
|
|
||||||
const handleMaterial = new StandardMaterial("handle-material", this.scene);
|
|
||||||
handleMaterial.diffuseColor = Color3.FromHexString("#EEEEFF");
|
|
||||||
handleMaterial.alpha = .8;
|
|
||||||
handle.material = handleMaterial;
|
|
||||||
handle.position = Vector3.Zero();
|
|
||||||
|
|
||||||
this.node.parent = handle;
|
|
||||||
this.node.position.y = .1;
|
this.node.position.y = .1;
|
||||||
|
this.node.position.z = .2;
|
||||||
this.node.scaling = new Vector3(0.6, 0.6, 0.6);
|
this.node.scaling = new Vector3(0.6, 0.6, 0.6);
|
||||||
this.buildToolbox();
|
this.buildToolbox();
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import {getFrontPosition} from "./getFrontPosition";
|
|||||||
|
|
||||||
export function setMenuPosition(node: TransformNode, scene: Scene, offset: Vector3 = Vector3.Zero()) {
|
export function setMenuPosition(node: TransformNode, scene: Scene, offset: Vector3 = Vector3.Zero()) {
|
||||||
const front = getFrontPosition(.8, scene);
|
const front = getFrontPosition(.8, scene);
|
||||||
front.y = scene.activeCamera.globalPosition.y;
|
//front.y = scene.activeCamera.globalPosition.y;
|
||||||
node.position = front;
|
node.position = front;
|
||||||
node.position.addInPlace(offset);
|
node.position.addInPlace(offset);
|
||||||
node.lookAt(scene.activeCamera.globalPosition);
|
|
||||||
node.rotation.y = node.rotation.y + Math.PI;
|
|
||||||
node.position.y -= .5;
|
node.position.y -= .5;
|
||||||
|
|
||||||
|
node.lookAt(scene.activeCamera.globalPosition);
|
||||||
|
node.rotation.y = node.rotation.y + Math.PI;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user