diff --git a/package-lock.json b/package-lock.json index 9c1c818..4f9ac40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@types/react": "^18.2.72", "@types/react-dom": "^18.2.22", "axios": "^1.6.8", - "babylon-html": "0.0.3", + "canvas-hypertxt": "1.0.3", "dom-to-image-more": "^3.3.0", "earcut": "^2.2.4", "events": "^3.3.0", @@ -1127,15 +1127,6 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/babylon-html": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/babylon-html/-/babylon-html-0.0.3.tgz", - "integrity": "sha512-QBIDVM6wbKOl94lFa0kJ3TxSBBvlGS6hzgtOaZ6QSAHgjkwsHtTBxliCRehaf2sh5IGmHuQNF8GlHHMOOXvqZg==", - "dependencies": { - "@babylonjs/core": "^7.1.0", - "dom-to-image-more": "^3.3.0" - } - }, "node_modules/babylonjs-gltf2interface": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-7.5.0.tgz", @@ -1215,6 +1206,11 @@ "node": ">=8" } }, + "node_modules/canvas-hypertxt": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/canvas-hypertxt/-/canvas-hypertxt-1.0.3.tgz", + "integrity": "sha512-+VsMpRr64jYgKq2IeFUNel3vCZH/IzS+iXSHxmUV3IUH5dXlC9xHz4AwtPZisDxZ5MWcuK0V+TXgPKFPiZnxzg==" + }, "node_modules/chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", diff --git a/package.json b/package.json index 74fe262..9fe94e8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@types/react": "^18.2.72", "@types/react-dom": "^18.2.22", "axios": "^1.6.8", - "babylon-html": "0.0.3", + "canvas-hypertxt": "1.0.3", "dom-to-image-more": "^3.3.0", "earcut": "^2.2.4", "events": "^3.3.0", diff --git a/src/diagram/diagramMenuManager.ts b/src/diagram/diagramMenuManager.ts index d212e4d..c845cda 100644 --- a/src/diagram/diagramMenuManager.ts +++ b/src/diagram/diagramMenuManager.ts @@ -38,8 +38,7 @@ export class DiagramMenuManager { }); this.toolbox = new Toolbox(); this.scaleMenu = new ScaleMenu2(this._notifier); - const clickMenu = this.createClickMenu(this.toolbox.handleMesh, null); - clickMenu.dispose(); + controllers.controllerObservable.add((event: ControllerEvent) => { if (event.type == ControllerEventType.B_BUTTON) { if (event.value > .8) { diff --git a/src/menus/clickMenu.ts b/src/menus/clickMenu.ts index 69da67f..d89a70b 100644 --- a/src/menus/clickMenu.ts +++ b/src/menus/clickMenu.ts @@ -1,5 +1,5 @@ import {AbstractMesh, ActionEvent, Observable, Scene, TransformNode, Vector3} from "@babylonjs/core"; -import {HtmlButton} from "babylon-html"; +import {Button} from "../objects/Button"; const POINTER_UP = "pointerup"; @@ -74,11 +74,13 @@ export class ClickMenu { this._transformNode.dispose(false, true); } - private makeNewButton(name: string, id: string, scene: Scene, x: number): HtmlButton { - const button = new HtmlButton(name, id, scene, null, {html: null, image: {width: 268, height: 268}}); + private makeNewButton(name: string, id: string, scene: Scene, x: number): Button { + const button = new Button(name, id, scene) + button.transform.scaling = new Vector3(.2, .2, .2); + button.transform.rotate(Vector3.Up(), Math.PI); const transform = button.transform; transform.parent = this._transformNode; - transform.rotation.y = Math.PI; + //transform.rotation.y = Math.PI; transform.position.x = x; return button; } diff --git a/src/menus/scaleMenu.ts b/src/menus/scaleMenu.ts index 39fc47e..9662843 100644 --- a/src/menus/scaleMenu.ts +++ b/src/menus/scaleMenu.ts @@ -1,6 +1,6 @@ import {DefaultScene} from "../defaultScene"; -import {HtmlButton, HtmlMeshBuilder} from "babylon-html"; import {AbstractMesh, Observable, TransformNode, Vector3} from "@babylonjs/core"; +import {Button} from "../objects/Button"; export class ScaleMenu { @@ -17,21 +17,6 @@ export class ScaleMenu { this.build(); } - public changePosition(position: Vector3) { - this.transform.position = position.clone(); - } - - public show(mesh: AbstractMesh) { - this.transform.position = mesh.absolutePosition.clone(); - this.transform.position.y = mesh.getBoundingInfo().boundingBox.maximumWorld.y + .1; - this.transform.setEnabled(true); - this._mesh = mesh; - } - - public hide() { - this.transform.setEnabled(false); - this._mesh = null; - } private async build() { let x = .12; @@ -78,7 +63,7 @@ export class ScaleMenu { } private makeButton(name: string, x: number, y: number, parent: TransformNode = null) { - const button = new HtmlButton(name, name, DefaultScene.Scene); + const button = new Button(name, name, DefaultScene.Scene); button.transform.parent = parent; button.transform.position.x = x; //button.transform.position.y = y; @@ -113,18 +98,4 @@ export class ScaleMenu { this.onScaleChangeObservable.notifyObservers(this._mesh); } } - - private async createLabel(name: string, y: number) { - const label = await HtmlMeshBuilder.CreatePlane(`${name}-label`, - { - html: `
${name}
`, - height: .1, image: {width: 128, height: 128} - }, - DefaultScene.Scene); - - label.parent = this.transform; - label.position.y = y; - label.position.x = -.42; - return label; - } } diff --git a/src/objects/Button.ts b/src/objects/Button.ts new file mode 100644 index 0000000..36a0afd --- /dev/null +++ b/src/objects/Button.ts @@ -0,0 +1,186 @@ +import { + AbstractMesh, + ActionEvent, + ActionManager, + Color3, + DynamicTexture, + ExecuteCodeAction, + ICanvasRenderingContext, + MeshBuilder, + Observable, + Scene, + StandardMaterial, + TransformNode, + Vector3 +} from "@babylonjs/core"; +import {split} from "canvas-hypertxt"; + +export type ButtonOptions = { + width?: number, + height?: number, + background?: Color3, + color?: Color3, + hoverBackground?: Color3, + hoverColor?: Color3, + clickBackground?: Color3, + clickColor?: Color3, + fontSize?: number +} + +enum states { + NORMAL, + HOVER, + CLICK +} + +export class Button { + public onPointerObservable: Observable = new Observable(); + private _scene: Scene; + private _mesh: AbstractMesh; + private _width: number; + private _height: number; + private _background: Color3; + private _color: Color3; + private _hoverBackground: Color3; + private _hoverColor: Color3; + private _clickBackground?: Color3; + private _clickColor?: Color3; + private _textures: Map = new Map(); + private _fontSize: number; + private readonly _density: number = 512; + + constructor(name: string, id: string, scene: Scene, options?: ButtonOptions) { + this._scene = scene; + const opts = defaultOptions(options); + this.mapColors(options); + this._width = opts.width; + this._height = opts.height; + this._mesh = MeshBuilder.CreatePlane(name, opts, scene); + this._mesh.id = id; + this._transform = new TransformNode(id, scene); + this._mesh.parent = this._transform; + this._mesh.rotate(Vector3.Up(), Math.PI); + this._mesh.material = this.buildMaterial(); + this.registerActions(); + + } + + private _transform: TransformNode; + + public get transform(): TransformNode { + return this._transform; + } + + static CreateButton(name: string, id: string, scene: Scene, options?: ButtonOptions): Button { + const button = new Button(name, id, scene, options); + return button; + } + + public dispose() { + this._mesh.dispose(false, true); + this._transform.dispose(false, true); + this.onPointerObservable.clear(); + this.onPointerObservable = null; + this._transform = null; + this._scene = null; + this._mesh = null; + this._textures.forEach((value) => { + value.dispose(); + }); + this._textures.clear(); + } + + private mapColors(options?: ButtonOptions) { + this._background = options?.background || Color3.Black(); + this._color = options?.color || Color3.White(); + this._hoverBackground = options?.hoverBackground || Color3.Gray(); + this._hoverColor = options?.hoverColor || Color3.White(); + this._clickBackground = options?.clickBackground || Color3.White(); + this._clickColor = options?.clickColor || Color3.Black(); + this._fontSize = options?.fontSize || 512; + + } + + private buildMaterial(): StandardMaterial { + const mat = new StandardMaterial('buttonMat', this._scene); + //mat.diffuseColor.set(.5, .5, .5); + mat.backFaceCulling = false; + this._textures.set(states.NORMAL, this.drawText(this._mesh.name, this._color, this._background)); + this._textures.set(states.HOVER, this.drawText(this._mesh.name, this._hoverColor, this._hoverBackground)); + this._textures.set(states.CLICK, this.drawText(this._mesh.name, this._clickColor, this._clickBackground)); + mat.emissiveTexture = this._textures.get(states.NORMAL); + mat.disableLighting = true; + return mat; + } + + private drawText(name: string, foreground: Color3, background: Color3): DynamicTexture { + const opts = {width: this._width * this._density, height: this._height * this._density}; + const texture = new DynamicTexture('buttonTexture', opts, this._scene); + + const ctx: ICanvasRenderingContext = texture.getContext(); + const ctx2d: CanvasRenderingContext2D = (ctx.canvas.getContext('2d') as CanvasRenderingContext2D); + const font = `900 ${this._fontSize / 10}px Arial`; + ctx2d.font = font; + ctx2d.textBaseline = 'middle'; + ctx2d.textAlign = 'center'; + ctx2d.fillStyle = background.toHexString(); + ctx2d.fillRect(0, 0, this._width * this._density, this._height * this._density); + ctx2d.fillStyle = foreground.toHexString(); + const lines = split(ctx2d, name, font, this._width * this._density, true); + const x = this._width * this._density / 2; + let y = this._height * this._density / 2 - (lines.length - 1) * 50 / 2; + for (const line of lines) { + ctx2d.fillText(line, x, y); + y += 50; + } + texture.update(); + return texture; + } + + private registerActions() { + const button = this._mesh; + button.actionManager = new ActionManager(this._scene); + button.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPointerOverTrigger, (evt) => { + this.setMaterial(states.HOVER); + this.onPointerObservable.notifyObservers(evt); + })); + button.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPointerOutTrigger, (evt) => { + this.setMaterial(states.NORMAL); + this.onPointerObservable.notifyObservers(evt); + })); + button.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPickDownTrigger, (evt) => { + this.setMaterial(states.CLICK); + this.onPointerObservable.notifyObservers(evt); + })); + button.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPickUpTrigger, (evt) => { + this.setMaterial(states.HOVER); + this.onPointerObservable.notifyObservers(evt); + })); + button.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPointerOutTrigger, (evt) => { + this.setMaterial(states.NORMAL); + this.onPointerObservable.notifyObservers(evt); + })); + button.actionManager.registerAction(new ExecuteCodeAction(ActionManager.OnPickTrigger, (evt) => { + this.onPointerObservable.notifyObservers(evt); + })); + } + + private setMaterial(state: states) { + if (this._mesh && this._mesh.material && this._textures.has(state)) { + (this._mesh.material as StandardMaterial).emissiveTexture = this._textures.get(state); + } + } +} + +function defaultOptions(options: ButtonOptions): ButtonOptions { + if (!options) { + options = {width: .5, height: .5}; + } + if (!options.width) { + options.width = .5; + } + if (!options.height) { + options.height = .5; + } + return options; +} \ No newline at end of file diff --git a/src/objects/handle.ts b/src/objects/handle.ts index 1b8c370..be905a1 100644 --- a/src/objects/handle.ts +++ b/src/objects/handle.ts @@ -1,9 +1,18 @@ -import {AbstractMesh, Scene, TransformNode, Vector3} from "@babylonjs/core"; -import {HtmlMeshBuilder} from "babylon-html"; +import { + Color3, + DynamicTexture, + ICanvasRenderingContext, + MeshBuilder, + Scene, + StandardMaterial, + TransformNode, + Vector3 +} from "@babylonjs/core"; import log, {Logger} from "loglevel"; +import {split} from "canvas-hypertxt"; export class Handle { - public mesh: AbstractMesh; + public mesh: TransformNode; private readonly _menuItem: TransformNode; private _isStored: boolean = false; private _offset: Vector3; @@ -30,15 +39,21 @@ export class Handle { private buildHandle() { const scene: Scene = this._menuItem.getScene(); - const handle = HtmlMeshBuilder.CreatePlaneSync('handle-' + this._menuItem.id, { - html: - `
${this._label}
- `, width: .5, height: .1, image: {width: 256, height: 51} - }, scene); + + + const handle = MeshBuilder.CreatePlane('handle-' + this._menuItem.id, {width: .4, height: .4 / 8}, scene); + //button.transform.scaling.set(.1,.1,.1); + const texture = this.drawText(this._label, Color3.White(), Color3.Black()); + const material = new StandardMaterial('handleMaterial', scene); + material.emissiveTexture = texture; + material.disableLighting = true; + handle.material = material; + //handle.rotate(Vector3.Up(), Math.PI); handle.id = 'handle-' + this._menuItem.id; if (this._menuItem) { this._menuItem.setParent(handle); } + const stored = localStorage.getItem(handle.id); if (stored) { this._logger.debug('Stored location found for ' + handle.id); @@ -60,6 +75,30 @@ export class Handle { } handle.metadata = {handle: true}; this.mesh = handle; + } + private drawText(name: string, foreground: Color3, background: Color3): DynamicTexture { + const texture = new DynamicTexture('handleTexture', {width: 512, height: 64}, this._menuItem.getScene()); + const ctx: ICanvasRenderingContext = texture.getContext(); + const ctx2d: CanvasRenderingContext2D = (ctx.canvas.getContext('2d') as CanvasRenderingContext2D); + const font = `900 24px Arial`; + ctx2d.font = font; + ctx2d.textBaseline = 'middle'; + ctx2d.textAlign = 'center'; + ctx2d.fillStyle = background.toHexString(); + ctx2d.fillRect(0, 0, 512, 64); + ctx2d.fillStyle = foreground.toHexString(); + const lines = split(ctx2d, name, font, 512, true); + const x = 256; + let y = 32; + for (const line of lines) { + ctx2d.fillText(line, x, y); + y += 50; + } + texture.update(); + return texture; + } + + } \ No newline at end of file diff --git a/src/tutorial/introduction.ts b/src/tutorial/introduction.ts index 8dca068..9ca6dbe 100644 --- a/src/tutorial/introduction.ts +++ b/src/tutorial/introduction.ts @@ -10,8 +10,8 @@ import { VideoTexture } from "@babylonjs/core"; import {DefaultScene} from "../defaultScene"; -import {HtmlButton} from "babylon-html"; import Hls from "hls.js"; +import {Button} from "../objects/Button"; type Step = { name: string; @@ -82,9 +82,13 @@ export class Introduction { hls.attachMedia(vid); hls.on(Hls.Events.MANIFEST_PARSED, function () { vid.loop = false; + vid.play().then(() => { - this.logger.debug("Video Playing"); + //this.logger.debug("Video Playing"); + }).catch((err) => { + console.error(err); }); + }); } else if (vid.canPlayType('application/vnd.apple.mpegurl')) { vid.src = src; @@ -92,6 +96,8 @@ export class Introduction { vid.addEventListener('loadedmetadata', function () { vid.play().then(() => { + }).catch((err) => { + console.error(err); }); }); } @@ -109,15 +115,14 @@ export class Introduction { }, -1, true, this, false); } - private buildButton(name: string): HtmlButton { - const button = new HtmlButton(name, name, this._scene, null, - {html: null, image: {width: 512, height: 512}, width: .5, height: .5}); + private buildButton(name: string): Button { + const button = new Button(name, name, this._scene, {width: .5, height: .5, fontSize: 400}); button.transform.position.y = .4; return button; } - private step(index: number, prev?: HtmlButton, prevVideo?: AbstractMesh) { + private step(index: number, prev?: Button, prevVideo?: AbstractMesh) { if (prevVideo && prevVideo?.metadata) { prevVideo.metadata.video.pause(); prevVideo.metadata.video.remove(); diff --git a/src/util/displayDebug.ts b/src/util/displayDebug.ts index 7a70d9c..88add5d 100644 --- a/src/util/displayDebug.ts +++ b/src/util/displayDebug.ts @@ -1,5 +1,5 @@ import {TransformNode} from "@babylonjs/core"; -import {HtmlMeshBuilder} from "babylon-html"; + const debug = true; @@ -15,14 +15,8 @@ function drawDebugText(text: string[]) { transform.getScene().onActiveCameraChanged.add(() => { transform.parent = transform.getScene().activeCamera; }); - + console.log(text); transform.position.z = 1; transform.position.y = .2; - const plane = HtmlMeshBuilder.CreatePlaneSync('debugMesh', { - html: - `
${text.join('
')}
- `, width: .3, height: .05 * text.length, image: {width: 256, height: 32 * text.length} - }, transform.getScene()); - plane.parent = transform; } \ No newline at end of file