@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
text/typescript
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 {
public isGizmo: boolean = false;
public translationSnap: number = 1;
public rotationSnapAngle: number = 15;
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;
}
}
}