@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
text/typescript
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 {
id: string | null = null;
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();
}
}