UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

1,063 lines (892 loc) 44.4 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var THREE = _interopRequireWildcard(require("three")); // This file has been added and patched after installing the NPM modules (via NPM script 'prepare') var threeExamples = {}; /** * @author arodic / https://github.com/arodic */ threeExamples.TransformControls = function (camera, domElement) { THREE.Object3D.call(this); domElement = domElement !== undefined ? domElement : document; this.visible = false; var _gizmo = new threeExamples.TransformControlsGizmo(); this.add(_gizmo); var _plane = new threeExamples.TransformControlsPlane(); this.add(_plane); var scope = this; // Define properties with getters/setter // Setting the defined property will automatically trigger change event // Defined properties are passed down to gizmo and plane defineProperty("camera", camera); defineProperty("object", undefined); defineProperty("enabled", true); defineProperty("axis", null); defineProperty("mode", "translate"); defineProperty("translationSnap", null); defineProperty("rotationSnap", null); defineProperty("space", "world"); defineProperty("size", 1); defineProperty("dragging", false); defineProperty("showX", true); defineProperty("showY", true); defineProperty("showZ", true); var changeEvent = { type: "change" }; var mouseDownEvent = { type: "mouseDown" }; var mouseUpEvent = { type: "mouseUp", mode: scope.mode }; var objectChangeEvent = { type: "objectChange" }; // Reusable utility variables var ray = new THREE.Raycaster(); var _tempVector = new THREE.Vector3(); var _tempVector2 = new THREE.Vector3(); var _tempQuaternion = new THREE.Quaternion(); var _unit = { X: new THREE.Vector3(1, 0, 0), Y: new THREE.Vector3(0, 1, 0), Z: new THREE.Vector3(0, 0, 1) }; var pointStart = new THREE.Vector3(); var pointEnd = new THREE.Vector3(); var offset = new THREE.Vector3(); var rotationAxis = new THREE.Vector3(); var startNorm = new THREE.Vector3(); var endNorm = new THREE.Vector3(); var rotationAngle = 0; var cameraPosition = new THREE.Vector3(); var cameraQuaternion = new THREE.Quaternion(); var cameraScale = new THREE.Vector3(); var parentPosition = new THREE.Vector3(); var parentQuaternion = new THREE.Quaternion(); var parentQuaternionInv = new THREE.Quaternion(); var parentScale = new THREE.Vector3(); var worldPositionStart = new THREE.Vector3(); var worldQuaternionStart = new THREE.Quaternion(); var worldScaleStart = new THREE.Vector3(); var worldPosition = new THREE.Vector3(); var worldQuaternion = new THREE.Quaternion(); var worldQuaternionInv = new THREE.Quaternion(); var worldScale = new THREE.Vector3(); var eye = new THREE.Vector3(); var positionStart = new THREE.Vector3(); var quaternionStart = new THREE.Quaternion(); var scaleStart = new THREE.Vector3(); // TODO: remove properties unused in plane and gizmo defineProperty("worldPosition", worldPosition); defineProperty("worldPositionStart", worldPositionStart); defineProperty("worldQuaternion", worldQuaternion); defineProperty("worldQuaternionStart", worldQuaternionStart); defineProperty("cameraPosition", cameraPosition); defineProperty("cameraQuaternion", cameraQuaternion); defineProperty("pointStart", pointStart); defineProperty("pointEnd", pointEnd); defineProperty("rotationAxis", rotationAxis); defineProperty("rotationAngle", rotationAngle); defineProperty("eye", eye); { domElement.addEventListener("mousedown", onPointerDown, false); domElement.addEventListener("touchstart", onPointerDown, false); domElement.addEventListener("mousemove", onPointerHover, false); domElement.addEventListener("touchmove", onPointerHover, false); domElement.addEventListener("touchmove", onPointerMove, false); document.addEventListener("mouseup", onPointerUp, false); domElement.addEventListener("touchend", onPointerUp, false); domElement.addEventListener("touchcancel", onPointerUp, false); domElement.addEventListener("touchleave", onPointerUp, false); } this.dispose = function () { domElement.removeEventListener("mousedown", onPointerDown); domElement.removeEventListener("touchstart", onPointerDown); domElement.removeEventListener("mousemove", onPointerHover); document.removeEventListener("mousemove", onPointerMove); domElement.removeEventListener("touchmove", onPointerHover); domElement.removeEventListener("touchmove", onPointerMove); document.removeEventListener("mouseup", onPointerUp); domElement.removeEventListener("touchend", onPointerUp); domElement.removeEventListener("touchcancel", onPointerUp); domElement.removeEventListener("touchleave", onPointerUp); this.traverse(function (child) { if (child.geometry) child.geometry.dispose(); if (child.material) child.material.dispose(); }); }; // Set current object this.attach = function (object) { this.object = object; this.visible = true; return this; }; // Detatch from object this.detach = function () { this.object = undefined; this.visible = false; this.axis = null; return this; }; // Defined getter, setter and store for a property function defineProperty(propName, defaultValue) { var propValue = defaultValue; Object.defineProperty(scope, propName, { get: function get() { return propValue !== undefined ? propValue : defaultValue; }, set: function set(value) { if (propValue !== value) { propValue = value; _plane[propName] = value; _gizmo[propName] = value; scope.dispatchEvent({ type: propName + "-changed", value: value }); scope.dispatchEvent(changeEvent); } } }); scope[propName] = defaultValue; _plane[propName] = defaultValue; _gizmo[propName] = defaultValue; } // updateMatrixWorld updates key transformation variables this.updateMatrixWorld = function () { if (this.object !== undefined) { this.object.updateMatrixWorld(); this.object.parent.matrixWorld.decompose(parentPosition, parentQuaternion, parentScale); this.object.matrixWorld.decompose(worldPosition, worldQuaternion, worldScale); parentQuaternionInv.copy(parentQuaternion).inverse(); worldQuaternionInv.copy(worldQuaternion).inverse(); } this.camera.updateMatrixWorld(); this.camera.matrixWorld.decompose(cameraPosition, cameraQuaternion, cameraScale); eye.copy(cameraPosition).sub(worldPosition).normalize(); THREE.Object3D.prototype.updateMatrixWorld.call(this); }; this.pointerHover = function (pointer) { if (this.object === undefined || this.dragging === true || pointer.button !== undefined && pointer.button !== 0) return; ray.setFromCamera(pointer, this.camera); var intersect = ray.intersectObjects(_gizmo.picker[this.mode].children, true)[0] || false; if (intersect) { this.axis = intersect.object.name; } else { this.axis = null; } }; this.pointerDown = function (pointer) { if (this.object === undefined || this.dragging === true || pointer.button !== undefined && pointer.button !== 0) return; if ((pointer.button === 0 || pointer.button === undefined) && this.axis !== null) { ray.setFromCamera(pointer, this.camera); var planeIntersect = ray.intersectObjects([_plane], true)[0] || false; if (planeIntersect) { var 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') { var 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(); this.object.parent.updateMatrixWorld(); positionStart.copy(this.object.position); quaternionStart.copy(this.object.quaternion); scaleStart.copy(this.object.scale); this.object.matrixWorld.decompose(worldPositionStart, worldQuaternionStart, worldScaleStart); pointStart.copy(planeIntersect.point).sub(worldPositionStart); } this.dragging = true; mouseDownEvent.mode = this.mode; this.dispatchEvent(mouseDownEvent); } }; this.pointerMove = function (pointer) { var axis = this.axis; var mode = this.mode; var object = this.object; var space = this.space; if (mode === 'scale') { space = 'local'; } else if (axis === 'E' || axis === 'XYZE' || axis === 'XYZ') { space = 'world'; } if (object === undefined || axis === null || this.dragging === false || pointer.button !== undefined && pointer.button !== 0) return; ray.setFromCamera(pointer, this.camera); var planeIntersect = ray.intersectObjects([_plane], true)[0] || false; if (planeIntersect === false) return; pointEnd.copy(planeIntersect.point).sub(worldPositionStart); if (mode === 'translate') { // Apply translate offset.copy(pointEnd).sub(pointStart); if (space === 'local' && axis !== 'XYZ') { offset.applyQuaternion(worldQuaternionInv); } if (axis.indexOf('X') === -1) offset.x = 0; if (axis.indexOf('Y') === -1) offset.y = 0; if (axis.indexOf('Z') === -1) offset.z = 0; if (space === 'local' && axis !== 'XYZ') { offset.applyQuaternion(quaternionStart).divide(parentScale); } else { offset.applyQuaternion(parentQuaternionInv).divide(parentScale); } object.position.copy(offset).add(positionStart); // Apply translation snap if (this.translationSnap) { if (space === 'local') { object.position.applyQuaternion(_tempQuaternion.copy(quaternionStart).inverse()); 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(quaternionStart); } if (space === 'world') { if (object.parent) { object.position.add(_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(_tempVector.setFromMatrixPosition(object.parent.matrixWorld)); } } } } else if (mode === 'scale') { if (axis.search('XYZ') !== -1) { var d = pointEnd.length() / pointStart.length(); if (pointEnd.dot(pointStart) < 0) d *= -1; _tempVector2.set(d, d, d); } else { _tempVector.copy(pointStart); _tempVector2.copy(pointEnd); _tempVector.applyQuaternion(worldQuaternionInv); _tempVector2.applyQuaternion(worldQuaternionInv); _tempVector2.divide(_tempVector); if (axis.search('X') === -1) { _tempVector2.x = 1; } if (axis.search('Y') === -1) { _tempVector2.y = 1; } if (axis.search('Z') === -1) { _tempVector2.z = 1; } } // Apply scale object.scale.copy(scaleStart).multiply(_tempVector2); } else if (mode === 'rotate') { offset.copy(pointEnd).sub(pointStart); var ROTATION_SPEED = 20 / worldPosition.distanceTo(_tempVector.setFromMatrixPosition(this.camera.matrixWorld)); if (axis === 'E') { rotationAxis.copy(eye); rotationAngle = pointEnd.angleTo(pointStart); startNorm.copy(pointStart).normalize(); endNorm.copy(pointEnd).normalize(); rotationAngle *= endNorm.cross(startNorm).dot(eye) < 0 ? 1 : -1; } else if (axis === 'XYZE') { rotationAxis.copy(offset).cross(eye).normalize(); rotationAngle = offset.dot(_tempVector.copy(rotationAxis).cross(this.eye)) * ROTATION_SPEED; } else if (axis === 'X' || axis === 'Y' || axis === 'Z') { rotationAxis.copy(_unit[axis]); _tempVector.copy(_unit[axis]); if (space === 'local') { _tempVector.applyQuaternion(worldQuaternion); } rotationAngle = offset.dot(_tempVector.cross(eye).normalize()) * ROTATION_SPEED; } // Apply rotation snap if (this.rotationSnap) rotationAngle = Math.round(rotationAngle / this.rotationSnap) * this.rotationSnap; this.rotationAngle = rotationAngle; // Apply rotate if (space === 'local' && axis !== 'E' && axis !== 'XYZE') { object.quaternion.copy(quaternionStart); object.quaternion.multiply(_tempQuaternion.setFromAxisAngle(rotationAxis, rotationAngle)).normalize(); } else { rotationAxis.applyQuaternion(parentQuaternionInv); object.quaternion.copy(_tempQuaternion.setFromAxisAngle(rotationAxis, rotationAngle)); object.quaternion.multiply(quaternionStart).normalize(); } } this.dispatchEvent(changeEvent); this.dispatchEvent(objectChangeEvent); }; this.pointerUp = function (pointer) { if (pointer.button !== undefined && pointer.button !== 0) return; if (this.dragging && this.axis !== null) { mouseUpEvent.mode = this.mode; this.dispatchEvent(mouseUpEvent); } this.dragging = false; if (pointer.button === undefined) this.axis = null; }; // normalize mouse / touch pointer and remap {x,y} to view space. function getPointer(event) { if (document.pointerLockElement) { return { x: 0, y: 0, button: event.button }; } else { var pointer = event.changedTouches ? event.changedTouches[0] : event; var rect = domElement.getBoundingClientRect(); return { x: (pointer.clientX - rect.left) / rect.width * 2 - 1, y: -(pointer.clientY - rect.top) / rect.height * 2 + 1, button: event.button }; } } // mouse / touch event handlers function onPointerHover(event) { if (!scope.enabled) return; scope.pointerHover(getPointer(event)); } function onPointerDown(event) { if (!scope.enabled) return; document.addEventListener("mousemove", onPointerMove, false); scope.pointerHover(getPointer(event)); scope.pointerDown(getPointer(event)); } function onPointerMove(event) { if (!scope.enabled) return; scope.pointerMove(getPointer(event)); } function onPointerUp(event) { if (!scope.enabled) return; document.removeEventListener("mousemove", onPointerMove, false); scope.pointerUp(getPointer(event)); } // TODO: deprecate this.getMode = function () { return scope.mode; }; this.setMode = function (mode) { scope.mode = mode; }; this.setTranslationSnap = function (translationSnap) { scope.translationSnap = translationSnap; }; this.setRotationSnap = function (rotationSnap) { scope.rotationSnap = rotationSnap; }; this.setSize = function (size) { scope.size = size; }; this.setSpace = function (space) { scope.space = space; }; this.update = function () { console.warn('threeExamples.TransformControls: update function has no more functionality and therefore has been deprecated.'); }; }; threeExamples.TransformControls.prototype = Object.assign(Object.create(THREE.Object3D.prototype), { constructor: threeExamples.TransformControls, isTransformControls: true }); threeExamples.TransformControlsGizmo = function () { 'use strict'; THREE.Object3D.call(this); this.type = 'TransformControlsGizmo'; // shared materials var gizmoMaterial = new THREE.MeshBasicMaterial({ depthTest: false, depthWrite: false, transparent: true, side: THREE.DoubleSide, fog: false }); var gizmoLineMaterial = new THREE.LineBasicMaterial({ depthTest: false, depthWrite: false, transparent: true, linewidth: 1, fog: false }); // Make unique material for each axis/color var matInvisible = gizmoMaterial.clone(); matInvisible.opacity = 0.15; var matHelper = gizmoMaterial.clone(); matHelper.opacity = 0.33; var matRed = gizmoMaterial.clone(); matRed.color.set(0xff0000); var matGreen = gizmoMaterial.clone(); matGreen.color.set(0x00ff00); var matBlue = gizmoMaterial.clone(); matBlue.color.set(0x0000ff); var matWhiteTransparent = gizmoMaterial.clone(); matWhiteTransparent.opacity = 0.25; var matYellowTransparent = matWhiteTransparent.clone(); matYellowTransparent.color.set(0xffff00); var matCyanTransparent = matWhiteTransparent.clone(); matCyanTransparent.color.set(0x00ffff); var matMagentaTransparent = matWhiteTransparent.clone(); matMagentaTransparent.color.set(0xff00ff); var matYellow = gizmoMaterial.clone(); matYellow.color.set(0xffff00); var matLineRed = gizmoLineMaterial.clone(); matLineRed.color.set(0xff0000); var matLineGreen = gizmoLineMaterial.clone(); matLineGreen.color.set(0x00ff00); var matLineBlue = gizmoLineMaterial.clone(); matLineBlue.color.set(0x0000ff); var matLineCyan = gizmoLineMaterial.clone(); matLineCyan.color.set(0x00ffff); var matLineMagenta = gizmoLineMaterial.clone(); matLineMagenta.color.set(0xff00ff); var matLineYellow = gizmoLineMaterial.clone(); matLineYellow.color.set(0xffff00); var matLineGray = gizmoLineMaterial.clone(); matLineGray.color.set(0x787878); var matLineYellowTransparent = matLineYellow.clone(); matLineYellowTransparent.opacity = 0.25; // reusable geometry var arrowGeometry = new THREE.CylinderBufferGeometry(0, 0.05, 0.2, 12, 1, false); var scaleHandleGeometry = new THREE.BoxBufferGeometry(0.125, 0.125, 0.125); var lineGeometry = new THREE.BufferGeometry(); lineGeometry.addAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3)); var CircleGeometry = function (radius, arc) { var geometry = new THREE.BufferGeometry(); var vertices = []; for (var i = 0; i <= 64 * arc; ++i) { vertices.push(0, Math.cos(i / 32 * Math.PI) * radius, Math.sin(i / 32 * Math.PI) * radius); } geometry.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); return geometry; }; // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position // Gizmo definitions - custom hierarchy definitions for setupGizmo() function var 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.OctahedronBufferGeometry(0.1, 0), matWhiteTransparent.clone()), [0, 0, 0], [0, 0, 0]]], XY: [[new THREE.Mesh(new THREE.PlaneBufferGeometry(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.PlaneBufferGeometry(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.PlaneBufferGeometry(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]]] }; var pickerTranslate = { X: [[new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0.6, 0, 0], [0, 0, -Math.PI / 2]]], Y: [[new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0, 0.6, 0]]], Z: [[new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0, 0, 0.6], [Math.PI / 2, 0, 0]]], XYZ: [[new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.2, 0), matInvisible)]], XY: [[new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), matInvisible), [0.2, 0.2, 0]]], YZ: [[new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), matInvisible), [0, 0.2, 0.2], [0, Math.PI / 2, 0]]], XZ: [[new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), matInvisible), [0.2, 0, 0.2], [-Math.PI / 2, 0, 0]]] }; var helperTranslate = { START: [[new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.01, 2), matHelper), null, null, null, 'helper']], END: [[new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.01, 2), matHelper), null, null, null, 'helper']], DELTA: [[new THREE.Line(function TranslateHelperGeometry() { var geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 1, 1, 1], 3)); return geometry; }(), 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']] }; var gizmoRotate = { X: [[new THREE.Line(CircleGeometry(1, 0.5), matLineRed)], [new THREE.Mesh(new THREE.OctahedronBufferGeometry(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.OctahedronBufferGeometry(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.OctahedronBufferGeometry(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.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [1.17, 0, 0], [0, 0, -Math.PI / 2], [1, 1, 0.001]], [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [-1.17, 0, 0], [0, 0, Math.PI / 2], [1, 1, 0.001]], [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [0, -1.17, 0], [Math.PI, 0, 0], [1, 1, 0.001]], [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), [0, 1.17, 0], [0, 0, 0], [1, 1, 0.001]]], XYZE: [[new THREE.Line(CircleGeometry(1, 1), matLineGray), null, [0, Math.PI / 2, 0]]] }; var helperRotate = { AXIS: [[new THREE.Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper']] }; var pickerRotate = { X: [[new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, -Math.PI / 2, -Math.PI / 2]]], Y: [[new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [Math.PI / 2, 0, 0]]], Z: [[new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, 0, -Math.PI / 2]]], E: [[new THREE.Mesh(new THREE.TorusBufferGeometry(1.25, 0.1, 2, 24), matInvisible)]], XYZE: [[new THREE.Mesh(new THREE.SphereBufferGeometry(0.7, 10, 8), matInvisible)]] }; var 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.BoxBufferGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [1.1, 0, 0]]], XYZY: [[new THREE.Mesh(new THREE.BoxBufferGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [0, 1.1, 0]]], XYZZ: [[new THREE.Mesh(new THREE.BoxBufferGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [0, 0, 1.1]]] }; var pickerScale = { X: [[new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0.5, 0, 0], [0, 0, -Math.PI / 2]]], Y: [[new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0, 0.5, 0]]], Z: [[new THREE.Mesh(new THREE.CylinderBufferGeometry(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.BoxBufferGeometry(0.2, 0.2, 0.2), matInvisible), [1.1, 0, 0]]], XYZY: [[new THREE.Mesh(new THREE.BoxBufferGeometry(0.2, 0.2, 0.2), matInvisible), [0, 1.1, 0]]], XYZZ: [[new THREE.Mesh(new THREE.BoxBufferGeometry(0.2, 0.2, 0.2), matInvisible), [0, 0, 1.1]]] }; var 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']] }; // Creates an Object3D with gizmos described in custom hierarchy definition. var setupGizmo = function (gizmoMap) { var gizmo = new THREE.Object3D(); for (var name in gizmoMap) { for (var i = gizmoMap[name].length; i--;) { var object = gizmoMap[name][i][0].clone(); var position = gizmoMap[name][i][1]; var rotation = gizmoMap[name][i][2]; var scale = gizmoMap[name][i][3]; var tag = gizmoMap[name][i][4]; // name and tag properties are essential for picking and updating logic. 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(); var tempGeometry = object.geometry.clone(); tempGeometry.applyMatrix(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; }; // Reusable utility variables var tempVector = new THREE.Vector3(0, 0, 0); var tempEuler = new THREE.Euler(); var alignVector = new THREE.Vector3(0, 1, 0); var zeroVector = new THREE.Vector3(0, 0, 0); var lookAtMatrix = new THREE.Matrix4(); var tempQuaternion = new THREE.Quaternion(); var tempQuaternion2 = new THREE.Quaternion(); var identityQuaternion = new THREE.Quaternion(); var unitX = new THREE.Vector3(1, 0, 0); var unitY = new THREE.Vector3(0, 1, 0); var unitZ = new THREE.Vector3(0, 0, 1); // Gizmo creation 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)); // Pickers should be hidden always this.picker["translate"].visible = false; this.picker["rotate"].visible = false; this.picker["scale"].visible = false; // updateMatrixWorld will update transformations and appearance of individual handles this.updateMatrixWorld = function () { var space = this.space; if (this.mode === 'scale') space = 'local'; // scale always oriented to local rotation var quaternion = space === "local" ? this.worldQuaternion : identityQuaternion; // Show only gizmos for current transform mode 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"; var 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 (var i = 0; i < handles.length; i++) { var handle = handles[i]; // hide aligned to camera handle.visible = true; handle.rotation.set(0, 0, 0); handle.position.copy(this.worldPosition); var eyeDistance = this.worldPosition.distanceTo(this.cameraPosition); handle.scale.set(1, 1, 1).multiplyScalar(eyeDistance * this.size / 7); // TODO: simplify helpers and consider decoupling from gizmo if (handle.tag === 'helper') { handle.visible = false; if (handle.name === 'AXIS') { handle.position.copy(this.worldPositionStart); handle.visible = !!this.axis; if (this.axis === 'X') { tempQuaternion.setFromEuler(tempEuler.set(0, 0, 0)); handle.quaternion.copy(quaternion).multiply(tempQuaternion); if (Math.abs(alignVector.copy(unitX).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { handle.visible = false; } } if (this.axis === 'Y') { tempQuaternion.setFromEuler(tempEuler.set(0, 0, Math.PI / 2)); handle.quaternion.copy(quaternion).multiply(tempQuaternion); if (Math.abs(alignVector.copy(unitY).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { handle.visible = false; } } if (this.axis === 'Z') { tempQuaternion.setFromEuler(tempEuler.set(0, Math.PI / 2, 0)); handle.quaternion.copy(quaternion).multiply(tempQuaternion); if (Math.abs(alignVector.copy(unitZ).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { handle.visible = false; } } if (this.axis === 'XYZE') { tempQuaternion.setFromEuler(tempEuler.set(0, Math.PI / 2, 0)); alignVector.copy(this.rotationAxis); handle.quaternion.setFromRotationMatrix(lookAtMatrix.lookAt(zeroVector, alignVector, unitY)); handle.quaternion.multiply(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); tempVector.set(1e-10, 1e-10, 1e-10).add(this.worldPositionStart).sub(this.worldPosition).multiplyScalar(-1); tempVector.applyQuaternion(this.worldQuaternionStart.clone().inverse()); handle.scale.copy(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; } } // If updating helper, skip rest of the loop continue; } // Align handles to current local or world rotation handle.quaternion.copy(quaternion); if (this.mode === 'translate' || this.mode === 'scale') { // Hide translate and scale axis facing the camera var AXIS_HIDE_TRESHOLD = 0.99; var PLANE_HIDE_TRESHOLD = 0.2; var AXIS_FLIP_TRESHOLD = 0.0; if (handle.name === 'X' || handle.name === 'XYZX') { if (Math.abs(alignVector.copy(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(alignVector.copy(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(alignVector.copy(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(alignVector.copy(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(alignVector.copy(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(alignVector.copy(unitY).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD) { handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; } } // Flip translate and scale axis ocluded behind another axis if (handle.name.search('X') !== -1) { if (alignVector.copy(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 (alignVector.copy(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 (alignVector.copy(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') { // Align handles to current local or world rotation tempQuaternion2.copy(quaternion); alignVector.copy(this.eye).applyQuaternion(tempQuaternion.copy(quaternion).inverse()); if (handle.name.search("E") !== -1) { handle.quaternion.setFromRotationMatrix(lookAtMatrix.lookAt(this.eye, zeroVector, unitY)); } if (handle.name === 'X') { tempQuaternion.setFromAxisAngle(unitX, Math.atan2(-alignVector.y, alignVector.z)); tempQuaternion.multiplyQuaternions(tempQuaternion2, tempQuaternion); handle.quaternion.copy(tempQuaternion); } if (handle.name === 'Y') { tempQuaternion.setFromAxisAngle(unitY, Math.atan2(alignVector.x, alignVector.z)); tempQuaternion.multiplyQuaternions(tempQuaternion2, tempQuaternion); handle.quaternion.copy(tempQuaternion); } if (handle.name === 'Z') { tempQuaternion.setFromAxisAngle(unitZ, Math.atan2(alignVector.y, alignVector.x)); tempQuaternion.multiplyQuaternions(tempQuaternion2, tempQuaternion); handle.quaternion.copy(tempQuaternion); } } // Hide disabled axes 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); // highlight selected axis handle.material._opacity = handle.material._opacity || handle.material.opacity; handle.material._color = handle.material._color || handle.material.color.clone(); handle.material.color.copy(handle.material._color); handle.material.opacity = handle.material._opacity; 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.0; 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.0; 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); } } } THREE.Object3D.prototype.updateMatrixWorld.call(this); }; }; threeExamples.TransformControlsGizmo.prototype = Object.assign(Object.create(THREE.Object3D.prototype), { constructor: threeExamples.TransformControlsGizmo, isTransformControlsGizmo: true }); threeExamples.TransformControlsPlane = function () { 'use strict'; THREE.Mesh.call(this, new THREE.PlaneBufferGeometry(100000, 100000, 2, 2), new THREE.MeshBasicMaterial({ visible: false, wireframe: true, side: THREE.DoubleSide, transparent: true, opacity: 0.1 })); this.type = 'TransformControlsPlane'; var unitX = new THREE.Vector3(1, 0, 0); var unitY = new THREE.Vector3(0, 1, 0); var unitZ = new THREE.Vector3(0, 0, 1); var tempVector = new THREE.Vector3(); var dirVector = new THREE.Vector3(); var alignVector = new THREE.Vector3(); var tempMatrix = new THREE.Matrix4(); var identityQuaternion = new THREE.Quaternion(); this.updateMatrixWorld = function () { var space = this.space; this.position.copy(this.worldPosition); if (this.mode === 'scale') space = 'local'; // scale always oriented to local rotation unitX.set(1, 0, 0).applyQuaternion(space === "local" ? this.worldQuaternion : identityQuaternion); unitY.set(0, 1, 0).applyQuaternion(space === "local" ? this.worldQuaternion : identityQuaternion); unitZ.set(0, 0, 1).applyQuaternion(space === "local" ? this.worldQuaternion : identityQuaternion); // Align the plane for current transform mode, axis and space. alignVector.copy(unitY); switch (this.mode) { case 'translate': case 'scale': switch (this.axis) { case 'X': alignVector.copy(this.eye).cross(unitX); dirVector.copy(unitX).cross(alignVector); break; case 'Y': alignVector.copy(this.eye).cross(unitY); dirVector.copy(unitY).cross(alignVector); break; case 'Z': alignVector.copy(this.eye).cross(unitZ); dirVector.copy(unitZ).cross(alignVector); break; case 'XY': dirVector.copy(unitZ); break; case 'YZ': dirVector.copy(unitX); break; case 'XZ': alignVector.copy(unitZ); dirVector.copy(unitY); break; case 'XYZ': case 'E': dirVector.set(0, 0, 0); break; } break; case 'rotate': default: // special case for rotate dirVector.set(0, 0, 0); } if (dirVector.length() === 0) { // If in rotate mode, make the plane parallel to camera this.quaternion.copy(this.cameraQuaternion); } else { tempMatrix.lookAt(tempVector.set(0, 0, 0), dirVector, alignVector); this.quaternion.setFromRotationMatrix(tempMatrix); } THREE.Object3D.prototype.updateMatrixWorld.call(this); }; }; threeExamples.TransformControlsPlane.prototype = Object.assign(Object.create(THREE.Mesh.prototype), { constructor: threeExamples.TransformControlsPlane, isTransformControlsPlane: true }); var _default = threeExamples.TransformControls; exports["default"] = _default;