UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

80 lines (68 loc) 2.83 kB
import { Box3, MeshBasicMaterial } from 'three'; import { HTMLMesh } from 'three/examples/jsm/interactive/HTMLMesh.js'; import { InteractiveGroup } from 'three/examples/jsm/interactive/InteractiveGroup.js'; import { serializable } from '../../engine/engine_serialization.js'; import { getWorldRotation, setWorldRotationXYZ } from '../../engine/engine_three_utils.js'; import { Behaviour } from '../Component.js'; export class SpatialHtml extends Behaviour { @serializable() id: string | null = null; @serializable() keepAspect: boolean = false; private _object: InteractiveGroup | null = null; onEnable() { if (this._object) { this.gameObject.add(this._object); return; } if (!this.id || !this.context.mainCamera) return; const div = document.getElementById(this.id); if (!div) { console.warn("Could not find element with id \"" + this.id + "\""); return; } div.style.display = "block"; div.style.visibility = "hidden"; const group = new InteractiveGroup(); group.listenToPointerEvents(this.context.renderer, this.context.mainCamera!); // TODO listen to controller events? this.gameObject.add(group); const mesh = new HTMLMesh(div); group.add(mesh); mesh.visible = false; const mat = mesh.material as MeshBasicMaterial; mat.transparent = true; // need to wait one frame for it to render to get bounds setTimeout(() => { mesh.visible = true; // align box to get bounding box const rot = getWorldRotation(this.gameObject).clone(); setWorldRotationXYZ(this.gameObject, 0, 0, 0); this.gameObject.updateMatrixWorld(); const aabb = new Box3(); aabb.setFromObject(group); this.setWorldRotation(rot.x, rot.y, rot.z); // apply bounds const width = aabb.max.x - aabb.min.x; const height = aabb.max.y - aabb.min.y; if (this.keepAspect) { const aspect = width / height; if (width > height) { mesh.scale.set(1 / width, 1 / height / aspect, 1); } else { mesh.scale.set(1 / width * aspect, 1 / height, 1); } } else { mesh.scale.set(1 / width, 1 / height, 1); } // TODO: replace with world scale once we have that const factor = this.gameObject.scale; mesh.scale.multiply(factor); }, 1); } onDisable(): void { this._object?.removeFromParent(); } }