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

166 lines (135 loc) 5.33 kB
import { MathUtils,Mesh, MeshBasicMaterial } from "three"; import { TransformControls } from "three/examples/jsm/controls/TransformControls.js"; import * as params from "../engine/engine_default_parameters.js"; import { serializable } from "../engine/engine_serialization_decorator.js"; import { Behaviour, GameObject } from "./Component.js"; import { OrbitControls } from "./OrbitControls.js"; import { SyncedTransform } from "./SyncedTransform.js"; /** * TransformGizmo is a component that displays a gizmo for transforming the object in the scene. * @category Helpers * @group Components */ export class TransformGizmo extends Behaviour { @serializable() public isGizmo: boolean = false; @serializable() public translationSnap: number = 1; @serializable() public rotationSnapAngle: number = 15; @serializable() public scaleSnap: number = .25; private control?: TransformControls; private orbit?: OrbitControls; /** @internal */ onEnable() { if (this.isGizmo && !params.showGizmos) return; if (!this.context.mainCamera) return; if (!this.control) { this.control = new TransformControls(this.context.mainCamera, this.context.renderer.domElement); this.control.visible = true; this.control.enabled = true; this.control.getRaycaster().layers.set(2); this.control.size = 1; this.control.traverse(x => { const mesh = x as Mesh; mesh.layers.set(2); if (mesh) { const gizmoMat = mesh.material as MeshBasicMaterial; if (gizmoMat) { gizmoMat.opacity = 0.3; } } }); this.orbit = GameObject.getComponentInParent(this.context.mainCamera, OrbitControls) ?? undefined; } if (this.control) { this.context.scene.add(this.control); this.control.attach(this.gameObject); this.control?.addEventListener('dragging-changed', this.onControlChangedEvent); window.addEventListener('keydown', this.windowKeyDownListener); window.addEventListener('keyup', this.windowKeyUpListener); } } /** @internal */ onDisable() { this.control?.removeFromParent(); this.control?.removeEventListener('dragging-changed', this.onControlChangedEvent); window.removeEventListener('keydown', this.windowKeyDownListener); window.removeEventListener('keyup', this.windowKeyUpListener); } enableSnapping() { if (this.control) { this.control.setTranslationSnap(this.translationSnap); this.control.setRotationSnap(MathUtils.degToRad(this.rotationSnapAngle)); this.control.setScaleSnap(this.scaleSnap); } } disableSnapping() { if (this.control) { this.control.setTranslationSnap(null); this.control.setRotationSnap(null); this.control.setScaleSnap(null); } } private onControlChangedEvent = (event) => { const orbit = this.orbit; if (orbit) orbit.enabled = !event.value; if (event.value) { // request ownership on drag start const sync = GameObject.getComponentInParent(this.gameObject, SyncedTransform); if (sync) { sync.requestOwnership(); } } } private windowKeyDownListener = (event) => { if (!this.enabled) return; if (!this.control) return; switch (event.keyCode) { case 81: // Q this.control.setSpace(this.control.space === 'local' ? 'world' : 'local'); break; case 16: // Shift this.enableSnapping(); break; case 87: // W this.control.setMode('translate'); break; case 69: // E this.control.setMode('rotate'); break; case 82: // R this.control.setMode('scale'); break; case 187: case 107: // +, =, num+ this.control.setSize(this.control.size + 0.1); break; case 189: case 109: // -, _, num- this.control.setSize(Math.max(this.control.size - 0.1, 0.1)); break; case 88: // X this.control.showX = !this.control.showX; break; case 89: // Y this.control.showY = !this.control.showY; break; case 90: // Z this.control.showZ = !this.control.showZ; break; case 32: // Spacebar this.control.enabled = !this.control.enabled; break; } } private windowKeyUpListener = (event) => { if (!this.enabled) return; switch (event.keyCode) { case 16: // Shift this.disableSnapping(); break; } } }