UNPKG

three-stdlib

Version:

stand-alone library of threejs examples

1,047 lines (1,046 loc) 51.5 kB
"use strict"; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const THREE = require("three"); class TransformControls extends THREE.Object3D { constructor(camera, domElement) { super(); __publicField(this, "isTransformControls", true); __publicField(this, "visible", false); __publicField(this, "domElement"); __publicField(this, "raycaster", new THREE.Raycaster()); __publicField(this, "gizmo"); __publicField(this, "plane"); __publicField(this, "tempVector", new THREE.Vector3()); __publicField(this, "tempVector2", new THREE.Vector3()); __publicField(this, "tempQuaternion", new THREE.Quaternion()); __publicField(this, "unit", { X: new THREE.Vector3(1, 0, 0), Y: new THREE.Vector3(0, 1, 0), Z: new THREE.Vector3(0, 0, 1) }); __publicField(this, "pointStart", new THREE.Vector3()); __publicField(this, "pointEnd", new THREE.Vector3()); __publicField(this, "offset", new THREE.Vector3()); __publicField(this, "rotationAxis", new THREE.Vector3()); __publicField(this, "startNorm", new THREE.Vector3()); __publicField(this, "endNorm", new THREE.Vector3()); __publicField(this, "rotationAngle", 0); __publicField(this, "cameraPosition", new THREE.Vector3()); __publicField(this, "cameraQuaternion", new THREE.Quaternion()); __publicField(this, "cameraScale", new THREE.Vector3()); __publicField(this, "parentPosition", new THREE.Vector3()); __publicField(this, "parentQuaternion", new THREE.Quaternion()); __publicField(this, "parentQuaternionInv", new THREE.Quaternion()); __publicField(this, "parentScale", new THREE.Vector3()); __publicField(this, "worldPositionStart", new THREE.Vector3()); __publicField(this, "worldQuaternionStart", new THREE.Quaternion()); __publicField(this, "worldScaleStart", new THREE.Vector3()); __publicField(this, "worldPosition", new THREE.Vector3()); __publicField(this, "worldQuaternion", new THREE.Quaternion()); __publicField(this, "worldQuaternionInv", new THREE.Quaternion()); __publicField(this, "worldScale", new THREE.Vector3()); __publicField(this, "eye", new THREE.Vector3()); __publicField(this, "positionStart", new THREE.Vector3()); __publicField(this, "quaternionStart", new THREE.Quaternion()); __publicField(this, "scaleStart", new THREE.Vector3()); __publicField(this, "camera"); __publicField(this, "object"); __publicField(this, "enabled", true); __publicField(this, "axis", null); __publicField(this, "mode", "translate"); __publicField(this, "translationSnap", null); __publicField(this, "rotationSnap", null); __publicField(this, "scaleSnap", null); __publicField(this, "space", "world"); __publicField(this, "size", 1); __publicField(this, "dragging", false); __publicField(this, "showX", true); __publicField(this, "showY", true); __publicField(this, "showZ", true); // events __publicField(this, "changeEvent", { type: "change" }); __publicField(this, "mouseDownEvent", { type: "mouseDown", mode: this.mode }); __publicField(this, "mouseUpEvent", { type: "mouseUp", mode: this.mode }); __publicField(this, "objectChangeEvent", { type: "objectChange" }); __publicField(this, "intersectObjectWithRay", (object, raycaster, includeInvisible) => { const allIntersections = raycaster.intersectObject(object, true); for (let i = 0; i < allIntersections.length; i++) { if (allIntersections[i].object.visible || includeInvisible) { return allIntersections[i]; } } return false; }); // Set current object __publicField(this, "attach", (object) => { this.object = object; this.visible = true; return this; }); // Detatch from object __publicField(this, "detach", () => { this.object = void 0; this.visible = false; this.axis = null; return this; }); // Reset __publicField(this, "reset", () => { if (!this.enabled) return this; if (this.dragging) { if (this.object !== void 0) { this.object.position.copy(this.positionStart); this.object.quaternion.copy(this.quaternionStart); this.object.scale.copy(this.scaleStart); this.dispatchEvent(this.changeEvent); this.dispatchEvent(this.objectChangeEvent); this.pointStart.copy(this.pointEnd); } } return this; }); __publicField(this, "updateMatrixWorld", () => { if (this.object !== void 0) { this.object.updateMatrixWorld(); if (this.object.parent === null) { console.error("TransformControls: The attached 3D object must be a part of the scene graph."); } else { this.object.parent.matrixWorld.decompose(this.parentPosition, this.parentQuaternion, this.parentScale); } this.object.matrixWorld.decompose(this.worldPosition, this.worldQuaternion, this.worldScale); this.parentQuaternionInv.copy(this.parentQuaternion).invert(); this.worldQuaternionInv.copy(this.worldQuaternion).invert(); } this.camera.updateMatrixWorld(); this.camera.matrixWorld.decompose(this.cameraPosition, this.cameraQuaternion, this.cameraScale); this.eye.copy(this.cameraPosition).sub(this.worldPosition).normalize(); super.updateMatrixWorld(); }); __publicField(this, "pointerHover", (pointer) => { if (this.object === void 0 || this.dragging === true) return; this.raycaster.setFromCamera(pointer, this.camera); const intersect = this.intersectObjectWithRay(this.gizmo.picker[this.mode], this.raycaster); if (intersect) { this.axis = intersect.object.name; } else { this.axis = null; } }); __publicField(this, "pointerDown", (pointer) => { if (this.object === void 0 || this.dragging === true || pointer.button !== 0) return; if (this.axis !== null) { this.raycaster.setFromCamera(pointer, this.camera); const planeIntersect = this.intersectObjectWithRay(this.plane, this.raycaster, true); if (planeIntersect) { let space = this.space; if (this.mode === "scale") { space = "local"; } else if (this.axis === "E" || this.axis === "XYZE" || this.axis === "XYZ") { space = "world"; } if (space === "local" && this.mode === "rotate") { const snap = this.rotationSnap; if (this.axis === "X" && snap) this.object.rotation.x = Math.round(this.object.rotation.x / snap) * snap; if (this.axis === "Y" && snap) this.object.rotation.y = Math.round(this.object.rotation.y / snap) * snap; if (this.axis === "Z" && snap) this.object.rotation.z = Math.round(this.object.rotation.z / snap) * snap; } this.object.updateMatrixWorld(); if (this.object.parent) { this.object.parent.updateMatrixWorld(); } this.positionStart.copy(this.object.position); this.quaternionStart.copy(this.object.quaternion); this.scaleStart.copy(this.object.scale); this.object.matrixWorld.decompose(this.worldPositionStart, this.worldQuaternionStart, this.worldScaleStart); this.pointStart.copy(planeIntersect.point).sub(this.worldPositionStart); } this.dragging = true; this.mouseDownEvent.mode = this.mode; this.dispatchEvent(this.mouseDownEvent); } }); __publicField(this, "pointerMove", (pointer) => { const axis = this.axis; const mode = this.mode; const object = this.object; let space = this.space; if (mode === "scale") { space = "local"; } else if (axis === "E" || axis === "XYZE" || axis === "XYZ") { space = "world"; } if (object === void 0 || axis === null || this.dragging === false || pointer.button !== -1) return; this.raycaster.setFromCamera(pointer, this.camera); const planeIntersect = this.intersectObjectWithRay(this.plane, this.raycaster, true); if (!planeIntersect) return; this.pointEnd.copy(planeIntersect.point).sub(this.worldPositionStart); if (mode === "translate") { this.offset.copy(this.pointEnd).sub(this.pointStart); if (space === "local" && axis !== "XYZ") { this.offset.applyQuaternion(this.worldQuaternionInv); } if (axis.indexOf("X") === -1) this.offset.x = 0; if (axis.indexOf("Y") === -1) this.offset.y = 0; if (axis.indexOf("Z") === -1) this.offset.z = 0; if (space === "local" && axis !== "XYZ") { this.offset.applyQuaternion(this.quaternionStart).divide(this.parentScale); } else { this.offset.applyQuaternion(this.parentQuaternionInv).divide(this.parentScale); } object.position.copy(this.offset).add(this.positionStart); if (this.translationSnap) { if (space === "local") { object.position.applyQuaternion(this.tempQuaternion.copy(this.quaternionStart).invert()); if (axis.search("X") !== -1) { object.position.x = Math.round(object.position.x / this.translationSnap) * this.translationSnap; } if (axis.search("Y") !== -1) { object.position.y = Math.round(object.position.y / this.translationSnap) * this.translationSnap; } if (axis.search("Z") !== -1) { object.position.z = Math.round(object.position.z / this.translationSnap) * this.translationSnap; } object.position.applyQuaternion(this.quaternionStart); } if (space === "world") { if (object.parent) { object.position.add(this.tempVector.setFromMatrixPosition(object.parent.matrixWorld)); } if (axis.search("X") !== -1) { object.position.x = Math.round(object.position.x / this.translationSnap) * this.translationSnap; } if (axis.search("Y") !== -1) { object.position.y = Math.round(object.position.y / this.translationSnap) * this.translationSnap; } if (axis.search("Z") !== -1) { object.position.z = Math.round(object.position.z / this.translationSnap) * this.translationSnap; } if (object.parent) { object.position.sub(this.tempVector.setFromMatrixPosition(object.parent.matrixWorld)); } } } } else if (mode === "scale") { if (axis.search("XYZ") !== -1) { let d = this.pointEnd.length() / this.pointStart.length(); if (this.pointEnd.dot(this.pointStart) < 0) d *= -1; this.tempVector2.set(d, d, d); } else { this.tempVector.copy(this.pointStart); this.tempVector2.copy(this.pointEnd); this.tempVector.applyQuaternion(this.worldQuaternionInv); this.tempVector2.applyQuaternion(this.worldQuaternionInv); this.tempVector2.divide(this.tempVector); if (axis.search("X") === -1) { this.tempVector2.x = 1; } if (axis.search("Y") === -1) { this.tempVector2.y = 1; } if (axis.search("Z") === -1) { this.tempVector2.z = 1; } } object.scale.copy(this.scaleStart).multiply(this.tempVector2); if (this.scaleSnap && this.object) { if (axis.search("X") !== -1) { this.object.scale.x = Math.round(object.scale.x / this.scaleSnap) * this.scaleSnap || this.scaleSnap; } if (axis.search("Y") !== -1) { object.scale.y = Math.round(object.scale.y / this.scaleSnap) * this.scaleSnap || this.scaleSnap; } if (axis.search("Z") !== -1) { object.scale.z = Math.round(object.scale.z / this.scaleSnap) * this.scaleSnap || this.scaleSnap; } } } else if (mode === "rotate") { this.offset.copy(this.pointEnd).sub(this.pointStart); const ROTATION_SPEED = 20 / this.worldPosition.distanceTo(this.tempVector.setFromMatrixPosition(this.camera.matrixWorld)); if (axis === "E") { this.rotationAxis.copy(this.eye); this.rotationAngle = this.pointEnd.angleTo(this.pointStart); this.startNorm.copy(this.pointStart).normalize(); this.endNorm.copy(this.pointEnd).normalize(); this.rotationAngle *= this.endNorm.cross(this.startNorm).dot(this.eye) < 0 ? 1 : -1; } else if (axis === "XYZE") { this.rotationAxis.copy(this.offset).cross(this.eye).normalize(); this.rotationAngle = this.offset.dot(this.tempVector.copy(this.rotationAxis).cross(this.eye)) * ROTATION_SPEED; } else if (axis === "X" || axis === "Y" || axis === "Z") { this.rotationAxis.copy(this.unit[axis]); this.tempVector.copy(this.unit[axis]); if (space === "local") { this.tempVector.applyQuaternion(this.worldQuaternion); } this.rotationAngle = this.offset.dot(this.tempVector.cross(this.eye).normalize()) * ROTATION_SPEED; } if (this.rotationSnap) { this.rotationAngle = Math.round(this.rotationAngle / this.rotationSnap) * this.rotationSnap; } if (space === "local" && axis !== "E" && axis !== "XYZE") { object.quaternion.copy(this.quaternionStart); object.quaternion.multiply(this.tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle)).normalize(); } else { this.rotationAxis.applyQuaternion(this.parentQuaternionInv); object.quaternion.copy(this.tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle)); object.quaternion.multiply(this.quaternionStart).normalize(); } } this.dispatchEvent(this.changeEvent); this.dispatchEvent(this.objectChangeEvent); }); __publicField(this, "pointerUp", (pointer) => { if (pointer.button !== 0) return; if (this.dragging && this.axis !== null) { this.mouseUpEvent.mode = this.mode; this.dispatchEvent(this.mouseUpEvent); } this.dragging = false; this.axis = null; }); __publicField(this, "getPointer", (event) => { var _a; if (this.domElement && ((_a = this.domElement.ownerDocument) == null ? void 0 : _a.pointerLockElement)) { return { x: 0, y: 0, button: event.button }; } else { const pointer = event.changedTouches ? event.changedTouches[0] : event; const rect = this.domElement.getBoundingClientRect(); return { x: (pointer.clientX - rect.left) / rect.width * 2 - 1, y: -(pointer.clientY - rect.top) / rect.height * 2 + 1, button: event.button }; } }); __publicField(this, "onPointerHover", (event) => { if (!this.enabled) return; switch (event.pointerType) { case "mouse": case "pen": this.pointerHover(this.getPointer(event)); break; } }); __publicField(this, "onPointerDown", (event) => { if (!this.enabled || !this.domElement) return; this.domElement.style.touchAction = "none"; this.domElement.ownerDocument.addEventListener("pointermove", this.onPointerMove); this.pointerHover(this.getPointer(event)); this.pointerDown(this.getPointer(event)); }); __publicField(this, "onPointerMove", (event) => { if (!this.enabled) return; this.pointerMove(this.getPointer(event)); }); __publicField(this, "onPointerUp", (event) => { if (!this.enabled || !this.domElement) return; this.domElement.style.touchAction = ""; this.domElement.ownerDocument.removeEventListener("pointermove", this.onPointerMove); this.pointerUp(this.getPointer(event)); }); __publicField(this, "getMode", () => this.mode); __publicField(this, "setMode", (mode) => { this.mode = mode; }); __publicField(this, "setTranslationSnap", (translationSnap) => { this.translationSnap = translationSnap; }); __publicField(this, "setRotationSnap", (rotationSnap) => { this.rotationSnap = rotationSnap; }); __publicField(this, "setScaleSnap", (scaleSnap) => { this.scaleSnap = scaleSnap; }); __publicField(this, "setSize", (size) => { this.size = size; }); __publicField(this, "setSpace", (space) => { this.space = space; }); __publicField(this, "update", () => { console.warn( "THREE.TransformControls: update function has no more functionality and therefore has been deprecated." ); }); __publicField(this, "connect", (domElement) => { if (domElement === document) { console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); } this.domElement = domElement; this.domElement.addEventListener("pointerdown", this.onPointerDown); this.domElement.addEventListener("pointermove", this.onPointerHover); this.domElement.ownerDocument.addEventListener("pointerup", this.onPointerUp); }); __publicField(this, "dispose", () => { var _a, _b, _c, _d, _e, _f; (_a = this.domElement) == null ? void 0 : _a.removeEventListener("pointerdown", this.onPointerDown); (_b = this.domElement) == null ? void 0 : _b.removeEventListener("pointermove", this.onPointerHover); (_d = (_c = this.domElement) == null ? void 0 : _c.ownerDocument) == null ? void 0 : _d.removeEventListener("pointermove", this.onPointerMove); (_f = (_e = this.domElement) == null ? void 0 : _e.ownerDocument) == null ? void 0 : _f.removeEventListener("pointerup", this.onPointerUp); this.traverse((child) => { const mesh = child; if (mesh.geometry) { mesh.geometry.dispose(); } if (mesh.material) { mesh.material.dispose(); } }); }); this.domElement = domElement; this.camera = camera; this.gizmo = new TransformControlsGizmo(); this.add(this.gizmo); this.plane = new TransformControlsPlane(); this.add(this.plane); const defineProperty = (propName, defaultValue) => { let propValue = defaultValue; Object.defineProperty(this, propName, { get: function() { return propValue !== void 0 ? propValue : defaultValue; }, set: function(value) { if (propValue !== value) { propValue = value; this.plane[propName] = value; this.gizmo[propName] = value; this.dispatchEvent({ type: propName + "-changed", value }); this.dispatchEvent(this.changeEvent); } } }); this[propName] = defaultValue; this.plane[propName] = defaultValue; this.gizmo[propName] = defaultValue; }; defineProperty("camera", this.camera); defineProperty("object", this.object); defineProperty("enabled", this.enabled); defineProperty("axis", this.axis); defineProperty("mode", this.mode); defineProperty("translationSnap", this.translationSnap); defineProperty("rotationSnap", this.rotationSnap); defineProperty("scaleSnap", this.scaleSnap); defineProperty("space", this.space); defineProperty("size", this.size); defineProperty("dragging", this.dragging); defineProperty("showX", this.showX); defineProperty("showY", this.showY); defineProperty("showZ", this.showZ); defineProperty("worldPosition", this.worldPosition); defineProperty("worldPositionStart", this.worldPositionStart); defineProperty("worldQuaternion", this.worldQuaternion); defineProperty("worldQuaternionStart", this.worldQuaternionStart); defineProperty("cameraPosition", this.cameraPosition); defineProperty("cameraQuaternion", this.cameraQuaternion); defineProperty("pointStart", this.pointStart); defineProperty("pointEnd", this.pointEnd); defineProperty("rotationAxis", this.rotationAxis); defineProperty("rotationAngle", this.rotationAngle); defineProperty("eye", this.eye); if (domElement !== void 0) this.connect(domElement); } } class TransformControlsGizmo extends THREE.Object3D { constructor() { super(); __publicField(this, "isTransformControlsGizmo", true); __publicField(this, "type", "TransformControlsGizmo"); __publicField(this, "tempVector", new THREE.Vector3(0, 0, 0)); __publicField(this, "tempEuler", new THREE.Euler()); __publicField(this, "alignVector", new THREE.Vector3(0, 1, 0)); __publicField(this, "zeroVector", new THREE.Vector3(0, 0, 0)); __publicField(this, "lookAtMatrix", new THREE.Matrix4()); __publicField(this, "tempQuaternion", new THREE.Quaternion()); __publicField(this, "tempQuaternion2", new THREE.Quaternion()); __publicField(this, "identityQuaternion", new THREE.Quaternion()); __publicField(this, "unitX", new THREE.Vector3(1, 0, 0)); __publicField(this, "unitY", new THREE.Vector3(0, 1, 0)); __publicField(this, "unitZ", new THREE.Vector3(0, 0, 1)); __publicField(this, "gizmo"); __publicField(this, "picker"); __publicField(this, "helper"); // these are set from parent class TransformControls __publicField(this, "rotationAxis", new THREE.Vector3()); __publicField(this, "cameraPosition", new THREE.Vector3()); __publicField(this, "worldPositionStart", new THREE.Vector3()); __publicField(this, "worldQuaternionStart", new THREE.Quaternion()); __publicField(this, "worldPosition", new THREE.Vector3()); __publicField(this, "worldQuaternion", new THREE.Quaternion()); __publicField(this, "eye", new THREE.Vector3()); __publicField(this, "camera", null); __publicField(this, "enabled", true); __publicField(this, "axis", null); __publicField(this, "mode", "translate"); __publicField(this, "space", "world"); __publicField(this, "size", 1); __publicField(this, "dragging", false); __publicField(this, "showX", true); __publicField(this, "showY", true); __publicField(this, "showZ", true); // updateMatrixWorld will update transformations and appearance of individual handles __publicField(this, "updateMatrixWorld", () => { let space = this.space; if (this.mode === "scale") { space = "local"; } const quaternion = space === "local" ? this.worldQuaternion : this.identityQuaternion; this.gizmo["translate"].visible = this.mode === "translate"; this.gizmo["rotate"].visible = this.mode === "rotate"; this.gizmo["scale"].visible = this.mode === "scale"; this.helper["translate"].visible = this.mode === "translate"; this.helper["rotate"].visible = this.mode === "rotate"; this.helper["scale"].visible = this.mode === "scale"; let handles = []; handles = handles.concat(this.picker[this.mode].children); handles = handles.concat(this.gizmo[this.mode].children); handles = handles.concat(this.helper[this.mode].children); for (let i = 0; i < handles.length; i++) { const handle = handles[i]; handle.visible = true; handle.rotation.set(0, 0, 0); handle.position.copy(this.worldPosition); let factor; if (this.camera.isOrthographicCamera) { factor = (this.camera.top - this.camera.bottom) / this.camera.zoom; } else { factor = this.worldPosition.distanceTo(this.cameraPosition) * Math.min(1.9 * Math.tan(Math.PI * this.camera.fov / 360) / this.camera.zoom, 7); } handle.scale.set(1, 1, 1).multiplyScalar(factor * this.size / 7); if (handle.tag === "helper") { handle.visible = false; if (handle.name === "AXIS") { handle.position.copy(this.worldPositionStart); handle.visible = !!this.axis; if (this.axis === "X") { this.tempQuaternion.setFromEuler(this.tempEuler.set(0, 0, 0)); handle.quaternion.copy(quaternion).multiply(this.tempQuaternion); if (Math.abs(this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { handle.visible = false; } } if (this.axis === "Y") { this.tempQuaternion.setFromEuler(this.tempEuler.set(0, 0, Math.PI / 2)); handle.quaternion.copy(quaternion).multiply(this.tempQuaternion); if (Math.abs(this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { handle.visible = false; } } if (this.axis === "Z") { this.tempQuaternion.setFromEuler(this.tempEuler.set(0, Math.PI / 2, 0)); handle.quaternion.copy(quaternion).multiply(this.tempQuaternion); if (Math.abs(this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { handle.visible = false; } } if (this.axis === "XYZE") { this.tempQuaternion.setFromEuler(this.tempEuler.set(0, Math.PI / 2, 0)); this.alignVector.copy(this.rotationAxis); handle.quaternion.setFromRotationMatrix( this.lookAtMatrix.lookAt(this.zeroVector, this.alignVector, this.unitY) ); handle.quaternion.multiply(this.tempQuaternion); handle.visible = this.dragging; } if (this.axis === "E") { handle.visible = false; } } else if (handle.name === "START") { handle.position.copy(this.worldPositionStart); handle.visible = this.dragging; } else if (handle.name === "END") { handle.position.copy(this.worldPosition); handle.visible = this.dragging; } else if (handle.name === "DELTA") { handle.position.copy(this.worldPositionStart); handle.quaternion.copy(this.worldQuaternionStart); this.tempVector.set(1e-10, 1e-10, 1e-10).add(this.worldPositionStart).sub(this.worldPosition).multiplyScalar(-1); this.tempVector.applyQuaternion(this.worldQuaternionStart.clone().invert()); handle.scale.copy(this.tempVector); handle.visible = this.dragging; } else { handle.quaternion.copy(quaternion); if (this.dragging) { handle.position.copy(this.worldPositionStart); } else { handle.position.copy(this.worldPosition); } if (this.axis) { handle.visible = this.axis.search(handle.name) !== -1; } } continue; } handle.quaternion.copy(quaternion); if (this.mode === "translate" || this.mode === "scale") { const AXIS_HIDE_TRESHOLD = 0.99; const PLANE_HIDE_TRESHOLD = 0.2; const AXIS_FLIP_TRESHOLD = 0; if (handle.name === "X" || handle.name === "XYZX") { if (Math.abs(this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD) { handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; } } if (handle.name === "Y" || handle.name === "XYZY") { if (Math.abs(this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD) { handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; } } if (handle.name === "Z" || handle.name === "XYZZ") { if (Math.abs(this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD) { handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; } } if (handle.name === "XY") { if (Math.abs(this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD) { handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; } } if (handle.name === "YZ") { if (Math.abs(this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD) { handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; } } if (handle.name === "XZ") { if (Math.abs(this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD) { handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; } } if (handle.name.search("X") !== -1) { if (this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) { if (handle.tag === "fwd") { handle.visible = false; } else { handle.scale.x *= -1; } } else if (handle.tag === "bwd") { handle.visible = false; } } if (handle.name.search("Y") !== -1) { if (this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) { if (handle.tag === "fwd") { handle.visible = false; } else { handle.scale.y *= -1; } } else if (handle.tag === "bwd") { handle.visible = false; } } if (handle.name.search("Z") !== -1) { if (this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) { if (handle.tag === "fwd") { handle.visible = false; } else { handle.scale.z *= -1; } } else if (handle.tag === "bwd") { handle.visible = false; } } } else if (this.mode === "rotate") { this.tempQuaternion2.copy(quaternion); this.alignVector.copy(this.eye).applyQuaternion(this.tempQuaternion.copy(quaternion).invert()); if (handle.name.search("E") !== -1) { handle.quaternion.setFromRotationMatrix(this.lookAtMatrix.lookAt(this.eye, this.zeroVector, this.unitY)); } if (handle.name === "X") { this.tempQuaternion.setFromAxisAngle(this.unitX, Math.atan2(-this.alignVector.y, this.alignVector.z)); this.tempQuaternion.multiplyQuaternions(this.tempQuaternion2, this.tempQuaternion); handle.quaternion.copy(this.tempQuaternion); } if (handle.name === "Y") { this.tempQuaternion.setFromAxisAngle(this.unitY, Math.atan2(this.alignVector.x, this.alignVector.z)); this.tempQuaternion.multiplyQuaternions(this.tempQuaternion2, this.tempQuaternion); handle.quaternion.copy(this.tempQuaternion); } if (handle.name === "Z") { this.tempQuaternion.setFromAxisAngle(this.unitZ, Math.atan2(this.alignVector.y, this.alignVector.x)); this.tempQuaternion.multiplyQuaternions(this.tempQuaternion2, this.tempQuaternion); handle.quaternion.copy(this.tempQuaternion); } } handle.visible = handle.visible && (handle.name.indexOf("X") === -1 || this.showX); handle.visible = handle.visible && (handle.name.indexOf("Y") === -1 || this.showY); handle.visible = handle.visible && (handle.name.indexOf("Z") === -1 || this.showZ); handle.visible = handle.visible && (handle.name.indexOf("E") === -1 || this.showX && this.showY && this.showZ); handle.material.tempOpacity = handle.material.tempOpacity || handle.material.opacity; handle.material.tempColor = handle.material.tempColor || handle.material.color.clone(); handle.material.color.copy(handle.material.tempColor); handle.material.opacity = handle.material.tempOpacity; if (!this.enabled) { handle.material.opacity *= 0.5; handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); } else if (this.axis) { if (handle.name === this.axis) { handle.material.opacity = 1; handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); } else if (this.axis.split("").some(function(a) { return handle.name === a; })) { handle.material.opacity = 1; handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); } else { handle.material.opacity *= 0.25; handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); } } } super.updateMatrixWorld(); }); const gizmoMaterial = new THREE.MeshBasicMaterial({ depthTest: false, depthWrite: false, transparent: true, side: THREE.DoubleSide, fog: false, toneMapped: false }); const gizmoLineMaterial = new THREE.LineBasicMaterial({ depthTest: false, depthWrite: false, transparent: true, linewidth: 1, fog: false, toneMapped: false }); const matInvisible = gizmoMaterial.clone(); matInvisible.opacity = 0.15; const matHelper = gizmoMaterial.clone(); matHelper.opacity = 0.33; const matRed = gizmoMaterial.clone(); matRed.color.set(16711680); const matGreen = gizmoMaterial.clone(); matGreen.color.set(65280); const matBlue = gizmoMaterial.clone(); matBlue.color.set(255); const matWhiteTransparent = gizmoMaterial.clone(); matWhiteTransparent.opacity = 0.25; const matYellowTransparent = matWhiteTransparent.clone(); matYellowTransparent.color.set(16776960); const matCyanTransparent = matWhiteTransparent.clone(); matCyanTransparent.color.set(65535); const matMagentaTransparent = matWhiteTransparent.clone(); matMagentaTransparent.color.set(16711935); const matYellow = gizmoMaterial.clone(); matYellow.color.set(16776960); const matLineRed = gizmoLineMaterial.clone(); matLineRed.color.set(16711680); const matLineGreen = gizmoLineMaterial.clone(); matLineGreen.color.set(65280); const matLineBlue = gizmoLineMaterial.clone(); matLineBlue.color.set(255); const matLineCyan = gizmoLineMaterial.clone(); matLineCyan.color.set(65535); const matLineMagenta = gizmoLineMaterial.clone(); matLineMagenta.color.set(16711935); const matLineYellow = gizmoLineMaterial.clone(); matLineYellow.color.set(16776960); const matLineGray = gizmoLineMaterial.clone(); matLineGray.color.set(7895160); const matLineYellowTransparent = matLineYellow.clone(); matLineYellowTransparent.opacity = 0.25; const arrowGeometry = new THREE.CylinderGeometry(0, 0.05, 0.2, 12, 1, false); const scaleHandleGeometry = new THREE.BoxGeometry(0.125, 0.125, 0.125); const lineGeometry = new THREE.BufferGeometry(); lineGeometry.setAttribute("position", new THREE.Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3)); const CircleGeometry = (radius, arc) => { const geometry = new THREE.BufferGeometry(); const vertices = []; for (let i = 0; i <= 64 * arc; ++i) { vertices.push(0, Math.cos(i / 32 * Math.PI) * radius, Math.sin(i / 32 * Math.PI) * radius); } geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3)); return geometry; }; const TranslateHelperGeometry = () => { const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.Float32BufferAttribute([0, 0, 0, 1, 1, 1], 3)); return geometry; }; const gizmoTranslate = { X: [ [new THREE.Mesh(arrowGeometry, matRed), [1, 0, 0], [0, 0, -Math.PI / 2], null, "fwd"], [new THREE.Mesh(arrowGeometry, matRed), [1, 0, 0], [0, 0, Math.PI / 2], null, "bwd"], [new THREE.Line(lineGeometry, matLineRed)] ], Y: [ [new THREE.Mesh(arrowGeometry, matGreen), [0, 1, 0], null, null, "fwd"], [new THREE.Mesh(arrowGeometry, matGreen), [0, 1, 0], [Math.PI, 0, 0], null, "bwd"], [new THREE.Line(lineGeometry, matLineGreen), null, [0, 0, Math.PI / 2]] ], Z: [ [new THREE.Mesh(arrowGeometry, matBlue), [0, 0, 1], [Math.PI / 2, 0, 0], null, "fwd"], [new THREE.Mesh(arrowGeometry, matBlue), [0, 0, 1], [-Math.PI / 2, 0, 0], null, "bwd"], [new THREE.Line(lineGeometry, matLineBlue), null, [0, -Math.PI / 2, 0]] ], XYZ: [[new THREE.Mesh(new THREE.OctahedronGeometry(0.1, 0), matWhiteTransparent.clone()), [0, 0, 0], [0, 0, 0]]], XY: [ [new THREE.Mesh(new THREE.PlaneGeometry(0.295, 0.295), matYellowTransparent.clone()), [0.15, 0.15, 0]], [new THREE.Line(lineGeometry, matLineYellow), [0.18, 0.3, 0], null, [0.125, 1, 1]], [new THREE.Line(lineGeometry, matLineYellow), [0.3, 0.18, 0], [0, 0, Math.PI / 2], [0.125, 1, 1]] ], YZ: [ [new THREE.Mesh(new THREE.PlaneGeometry(0.295, 0.295), matCyanTransparent.clone()), [0, 0.15, 0.15], [0, Math.PI / 2, 0]], [new THREE.Line(lineGeometry, matLineCyan), [0, 0.18, 0.3], [0, 0, Math.PI / 2], [0.125, 1, 1]], [new THREE.Line(lineGeometry, matLineCyan), [0, 0.3, 0.18], [0, -Math.PI / 2, 0], [0.125, 1, 1]] ], XZ: [ [ new THREE.Mesh(new THREE.PlaneGeometry(0.295, 0.295), matMagentaTransparent.clone()), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0] ], [new THREE.Line(lineGeometry, matLineMagenta), [0.18, 0, 0.3], null, [0.125, 1, 1]], [new THREE.Line(lineGeometry, matLineMagenta), [0.3, 0, 0.18], [0, -Math.PI / 2, 0], [0.125, 1, 1]] ] }; const pickerTranslate = { X: [[new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0.6, 0, 0], [0, 0, -Math.PI / 2]]], Y: [[new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0, 0.6, 0]]], Z: [[new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0, 0, 0.6], [Math.PI / 2, 0, 0]]], XYZ: [[new THREE.Mesh(new THREE.OctahedronGeometry(0.2, 0), matInvisible)]], XY: [[new THREE.Mesh(new THREE.PlaneGeometry(0.4, 0.4), matInvisible), [0.2, 0.2, 0]]], YZ: [[new THREE.Mesh(new THREE.PlaneGeometry(0.4, 0.4), matInvisible), [0, 0.2, 0.2], [0, Math.PI / 2, 0]]], XZ: [[new THREE.Mesh(new THREE.PlaneGeometry(0.4, 0.4), matInvisible), [0.2, 0, 0.2], [-Math.PI / 2, 0, 0]]] }; const helperTranslate = { START: [[new THREE.Mesh(new THREE.OctahedronGeometry(0.01, 2), matHelper), null, null, null, "helper"]], END: [[new THREE.Mesh(new THREE.OctahedronGeometry(0.01, 2), matHelper), null, null, null, "helper"]], DELTA: [[new THREE.Line(TranslateHelperGeometry(), matHelper), null, null, null, "helper"]], X: [[new THREE.Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], "helper"]], Y: [[new THREE.Line(lineGeometry, matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], "helper"]], Z: [[new THREE.Line(lineGeometry, matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], "helper"]] }; const gizmoRotate = { X: [ [new THREE.Line(CircleGeometry(1, 0.5), matLineRed)], [new THREE.Mesh(new THREE.OctahedronGeometry(0.04, 0), matRed), [0, 0, 0.99], null, [1, 3, 1]] ], Y: [ [new THREE.Line(CircleGeometry(1, 0.5), matLineGreen), null, [0, 0, -Math.PI / 2]], [new THREE.Mesh(new THREE.OctahedronGeometry(0.04, 0), matGreen), [0, 0, 0.99], null, [3, 1, 1]] ], Z: [ [new THREE.Line(CircleGeometry(1, 0.5), matLineBlue), null, [0, Math.PI / 2, 0]], [new THREE.Mesh(new THREE.OctahedronGeometry(0.04, 0), matBlue), [0.99, 0, 0], null, [1, 3, 1]] ], E: [ [new THREE.Line(CircleGeometry(1.25, 1), matLineYellowTransparent), null, [0, Math.PI / 2, 0]], [ new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [1.17, 0, 0], [0, 0, -Math.PI / 2], [1, 1, 1e-3] ], [ new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [-1.17, 0, 0], [0, 0, Math.PI / 2], [1, 1, 1e-3] ], [ new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [0, -1.17, 0], [Math.PI, 0, 0], [1, 1, 1e-3] ], [ new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [0, 1.17, 0], [0, 0, 0], [1, 1, 1e-3] ] ], XYZE: [[new THREE.Line(CircleGeometry(1, 1), matLineGray), null, [0, Math.PI / 2, 0]]] }; const helperRotate = { AXIS: [[new THREE.Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], "helper"]] }; const pickerRotate = { X: [[new THREE.Mesh(new THREE.TorusGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, -Math.PI / 2, -Math.PI / 2]]], Y: [[new THREE.Mesh(new THREE.TorusGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [Math.PI / 2, 0, 0]]], Z: [[new THREE.Mesh(new THREE.TorusGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, 0, -Math.PI / 2]]], E: [[new THREE.Mesh(new THREE.TorusGeometry(1.25, 0.1, 2, 24), matInvisible)]], XYZE: [[new THREE.Mesh(new THREE.SphereGeometry(0.7, 10, 8), matInvisible)]] }; const gizmoScale = { X: [ [new THREE.Mesh(scaleHandleGeometry, matRed), [0.8, 0, 0], [0, 0, -Math.PI / 2]], [new THREE.Line(lineGeometry, matLineRed), null, null, [0.8, 1, 1]] ], Y: [ [new THREE.Mesh(scaleHandleGeometry, matGreen), [0, 0.8, 0]], [new THREE.Line(lineGeometry, matLineGreen), null, [0, 0, Math.PI / 2], [0.8, 1, 1]] ], Z: [ [new THREE.Mesh(scaleHandleGeometry, matBlue), [0, 0, 0.8], [Math.PI / 2, 0, 0]], [new THREE.Line(lineGeometry, matLineBlue), null, [0, -Math.PI / 2, 0], [0.8, 1, 1]] ], XY: [ [new THREE.Mesh(scaleHandleGeometry, matYellowTransparent), [0.85, 0.85, 0], null, [2, 2, 0.2]], [new THREE.Line(lineGeometry, matLineYellow), [0.855, 0.98, 0], null, [0.125, 1, 1]], [new THREE.Line(lineGeometry, matLineYellow), [0.98, 0.855, 0], [0, 0, Math.PI / 2], [0.125, 1, 1]] ], YZ: [ [new THREE.Mesh(scaleHandleGeometry, matCyanTransparent), [0, 0.85, 0.85], null, [0.2, 2, 2]], [new THREE.Line(lineGeometry, matLineCyan), [0, 0.855, 0.98], [0, 0, Math.PI / 2], [0.125, 1, 1]], [new THREE.Line(lineGeometry, matLineCyan), [0, 0.98, 0.855], [0, -Math.PI / 2, 0], [0.125, 1, 1]] ], XZ: [ [new THREE.Mesh(scaleHandleGeometry, matMagentaTransparent), [0.85, 0, 0.85], null, [2, 0.2, 2]], [new THREE.Line(lineGeometry, matLineMagenta), [0.855, 0, 0.98], null, [0.125, 1, 1]], [new THREE.Line(lineGeometry, matLineMagenta), [0.98, 0, 0.855], [0, -Math.PI / 2, 0], [0.125, 1, 1]] ], XYZX: [[new THREE.Mesh(new THREE.BoxGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [1.1, 0, 0]]], XYZY: [[new THREE.Mesh(new THREE.BoxGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [0, 1.1, 0]]], XYZZ: [[new THREE.Mesh(new THREE.BoxGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [0, 0, 1.1]]] }; const pickerScale = { X: [[new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0.5, 0, 0], [0, 0, -Math.PI / 2]]], Y: [[new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0, 0.5, 0]]], Z: [[new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0, 0, 0.5], [Math.PI / 2, 0, 0]]], XY: [[new THREE.Mesh(scaleHandleGeometry, matInvisible), [0.85, 0.85, 0], null, [3, 3, 0.2]]], YZ: [[new THREE.Mesh(scaleHandleGeometry, matInvisible), [0, 0.85, 0.85], null, [0.2, 3, 3]]], XZ: [[new THREE.Mesh(scaleHandleGeometry, matInvisible), [0.85, 0, 0.85], null, [3, 0.2, 3]]], XYZX: [[new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 0.2), matInvisible), [1.1, 0, 0]]], XYZY: [[new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 0.2), matInvisible), [0, 1.1, 0]]], XYZZ: [[new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 0.2), matInvisible), [0, 0, 1.1]]] }; const helperScale = { X: [[new THREE.Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], "helper"]], Y: [[new THREE.Line(lineGeometry, matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], "helper"]], Z: [[new THREE.Line(lineGeometry, matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], "helper"]] }; const setupGizmo = (gizmoMap) => { const gizmo = new THREE.Object3D(); for (let name in gizmoMap) { for (let i = gizmoMap[name].length; i--; ) { const object = gizmoMap[name][i][0].clone(); const position = gizmoMap[name][i][1]; const rotation = gizmoMap[name][i][2]; const scale = gizmoMap[name][i][3]; const tag = gizmoMap[name][i][4]; object.name = name; object.tag = tag; if (position) { object.position.set(position[0], position[1], position[2]); } if (rotation) { object.rotation.set(rotation[0], rotation[1], rotation[2]); } if (scale) { object.scale.set(scale[0], scale[1], scale[2]); } object.updateMatrix(); const tempGeometry = object.geometry.clone(); tempGeometry.applyMatrix4(object.matrix); object.geometry = tempGeometry; object.renderOrder = Infinity; object.position.set(0, 0, 0); object.rotation.set(0, 0, 0); object.scale.set(1, 1, 1); gizmo.add(object); } } return gizmo; }; this.gizmo = {}; this.picker = {}; this.helper = {}; this.add(this.gizmo["translate"] = setupGizmo(gizmoTranslate)); this.add(this.gizmo["rotate"] = setupGizmo(gizmoRotate)); this.add(this.gizmo["scale"] = setupGizmo(gizmoScale)); this.add(this.picker["translate"] = setupGizmo(pickerTranslate)); this.add(this.picker["rotate"] = setupGizmo(pickerRotate)); this.add(this.picker["scale"] = setupGizmo(pickerScale)); this.add(this.helper["translate"] = setupGizmo(helperTranslate)); this.add(this.helper["rotate"] = setupGizmo(helperRotate)); this.add(this.helper["scale"] = setupGizmo(helperScale)); this.picker["translate"].visible = false; this.picker["rotate"].visible = false; this.picker["scale"].visible = false; } } class TransformControlsPlane extends THREE.Mesh { constructor() { super( new THREE.PlaneGeometry(1e5, 1e5, 2, 2), new THREE.MeshBasicMaterial({ visible: false, wireframe: true, side: THREE.DoubleSide, transparent: true, opacity: 0.1, toneMapped: false }) ); __publicField(this, "isTransformControlsPlane", true); __publicField(this, "type", "TransformControlsPlane"); __publicField(this, "unitX", new THREE.Vector3(1, 0, 0)); __publicField(this, "unitY", new THREE.Vector3(0, 1, 0)); __publicField(this, "unitZ", new THREE.Vector3(0, 0, 1)); __publicField(this, "tempVector", new THREE.Vector3()); __publicField(this, "dirVector", new THREE.Vector3()); __publicField(this, "alignVector", new THREE.Vector3()); __publicField(this, "tempMatrix", new THREE.Matrix4()); __publicField(this, "identityQuaternion", new THREE.Quaternion()); // these are set from parent class TransformControls __publicField(this, "cameraQuaternion", new THREE.Quaternion()); __publicField(this, "worldPosition", new THREE.Vector3()); __publicField(this, "worldQuaternion", new THREE.Quaternion()); __publicField(this, "eye", new THREE.Vector3()); __publicField(this, "axis", null); __publicField(this, "mode", "translate"); __publicField(this, "space", "world"); __publicField(this, "updateMatrixWorld", () => { let space = this.space; this.position.copy(this.worldPosition); if (this.mode === "scale") space = "local"; this.unitX.set(1, 0, 0).applyQuaternion(space === "local" ? this.worldQuaternion : this.identityQuaternion); this.unitY.set(0, 1, 0).applyQuaternion(space === "local" ? this.worldQuaternion : this.identityQuaternion); this.unitZ.set(0, 0, 1).applyQuaternion(space === "local" ? this.worldQuaternion : this.identityQuaternion); this.alignVector.copy(this.unitY); switch (this.mode) { case "translate": case "scale": switch (this.axis) { case "X": this.alignVector.copy(this.eye).cross(this.unitX);