UNPKG

babylonjs-editcontrol

Version:
1,286 lines (1,281 loc) 79 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("babylonjs")); else if(typeof define === 'function' && define.amd) define(["babylonjs"], factory); else { var a = typeof exports === 'object' ? factory(require("babylonjs")) : factory(root["BABYLON"]); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } })(self, (__WEBPACK_EXTERNAL_MODULE_babylonjs__) => { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "babylonjs" /*!****************************************************************************************************!*\ !*** external {"commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs","root":"BABYLON"} ***! \****************************************************************************************************/ (module) { module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs__; /***/ } /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Check if module exists (development only) /******/ if (__webpack_modules__[moduleId] === undefined) { /******/ var e = new Error("Cannot find module '" + moduleId + "'"); /******/ e.code = 'MODULE_NOT_FOUND'; /******/ throw e; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ var __webpack_exports__ = {}; // This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. (() => { var exports = __webpack_exports__; /*!****************************!*\ !*** ./src/EditControl.ts ***! \****************************/ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.EditControl = void 0; const babylonjs_1 = __webpack_require__(/*! babylonjs */ "babylonjs"); var ActionType; (function (ActionType) { ActionType[ActionType["TRANS"] = 0] = "TRANS"; ActionType[ActionType["ROT"] = 1] = "ROT"; ActionType[ActionType["SCALE"] = 2] = "SCALE"; })(ActionType || (ActionType = {})); /** * Draws a transform widget at the mesh's location (its pivot location). * The widget transforms(translates,rotates and scales) the mesh based on user * interactions with the widget. * The widget shows the mesh position and rotation at any time. * The widget follows the mesh constantly. * Note: An alternate approach would have been for the mesh to follow the widget. * The problem with the alternate approach - syncing the transforms * if the mesh was being transformed by entities other than the widget say physics * or script for example. * */ class EditControl { _isEulerian(mesh) { return ((mesh.rotationQuaternion == null) || (mesh.rotationQuaternion == undefined)); } constructor(mesh, camera, canvas, scale, pickWidth) { this._local = true; this._snapT = false; this._snapR = false; this._transSnap = 1; this._rotSnap = Math.PI / 18; this._axesLen = 0.4; this._axesScale = 1; //how close to an axis should we get before we can pick it this._pickWidth = 0.02; this._redCol = new babylonjs_1.Color3(1, 0.34, 0.26); this._greenCol = new babylonjs_1.Color3(0.0, 0.8, 0.16); this._blueCol = new babylonjs_1.Color3(0.21, 0.5, 1); this._whiteCol = new babylonjs_1.Color3(0.7, 0.7, 0.7); this._yellowCol = new babylonjs_1.Color3(1, 1, 0); //axes visibility this._visibility = 0.75; //lhs-rhs issue. lhs mesh in rhs or rhs mesh in lhs this._lhsRhs = false; this._ecMatrix = new babylonjs_1.Matrix(); //edit control to camera vector this._ecTOcamera = new babylonjs_1.Vector3(0, 0, 0); //how far away from camera should the edit control appear to be this._distFromCamera = 2; //vector from camera to edit control this._cameraTOec = new babylonjs_1.Vector3(0, 0, 0); this._cameraNormal = new babylonjs_1.Vector3(0, 0, 0); this._prevState = ""; this._hidden = false; this._actionListener = null; this._actionStartListener = null; this._actionEndListener = null; this._pDown = false; this._pointerIsOver = false; this._editing = false; //rotate differently if camera is too close to the rotation plane this._rotate2 = false; //TODO when translating, the orientation of pALL keeps changing //TODo this is not so with rotation or scaling //TODO so for translation instead of pALL maybe we should use the camera view plane for picking this._transBy = new babylonjs_1.Vector3(0, 0, 0); this._snapTV = new babylonjs_1.Vector3(0, 0, 0); this._snapS = false; this._snapSV = new babylonjs_1.Vector3(0, 0, 0); this._scaleSnap = 0.25; this._scale = new babylonjs_1.Vector3(0, 0, 0); this._localX = new babylonjs_1.Vector3(0, 0, 0); this._localY = new babylonjs_1.Vector3(0, 0, 0); this._localZ = new babylonjs_1.Vector3(0, 0, 0); // private _eulerian: boolean = false; this._snapRA = 0; this._transEnabled = false; this._rotEnabled = false; this._scaleEnabled = false; this._guideSize = 180; this._tSnap = new babylonjs_1.Vector3(this._transSnap, this._transSnap, this._transSnap); //few temp vectors & matrix this._tv1 = new babylonjs_1.Vector3(0, 0, 0); this._tv2 = new babylonjs_1.Vector3(0, 0, 0); this._tv3 = new babylonjs_1.Vector3(0, 0, 0); this._tm = new babylonjs_1.Matrix(); this._mesh = mesh; this._mainCamera = camera; this._canvas = canvas; if (scale != null) { this._axesScale = scale; } if (pickWidth != null) { this._pickWidth = pickWidth; } this._utilLayer = babylonjs_1.UtilityLayerRenderer.DefaultUtilityLayer; this._utilLayer.onlyCheckPointerDownEvents = false; this._scene = this._utilLayer.utilityLayerScene; this._actHist = new ActHist(mesh, 10); mesh.computeWorldMatrix(true); this._boundingDimesion = this._getBoundingDimension(mesh); this._setLocalAxes(mesh); this._lhsRhs = this._check_LHS_RHS(mesh); if (this._lhsRhs) console.warn("we have lhs rhs issue " + this._lhsRhs); //build the edit control axes this._ecRoot = new babylonjs_1.Mesh("", this._scene); this._ecRoot.rotationQuaternion = babylonjs_1.Quaternion.Identity(); this._ecRoot.visibility = 0; this._ecRoot.isPickable = false; this._createMaterials(this._scene); let guideAxes = this._createCommonAxes(); guideAxes.parent = this._ecRoot; //build the pickplanes let pickPlanes = this._createPickPlanes(); pickPlanes.parent = this._ecRoot; this._pointerdown = (evt) => { return this._onPointerDown(evt); }; this._pointerup = (evt) => { return this._onPointerUp(evt); }; this._pointermove = (evt) => { return this._onPointerMove(evt); }; //use canvas rather than scene to handle pointer events //scene cannot have mutiple eventlisteners for an event //with canvas one will have to do ones own pickinfo generation. canvas.addEventListener("pointerdown", this._pointerdown, false); canvas.addEventListener("pointerup", this._pointerup, false); canvas.addEventListener("pointermove", this._pointermove, false); this._renderer = () => { return this._renderLoopProcess(); }; this._scene.registerBeforeRender(this._renderer); } getRoot() { return this._ecRoot; } /** * checks if a have left hand , right hand issue. * In other words if a mesh is a LHS mesh in RHS system or * a RHS mesh in LHS system * The X axis will be reversed in such cases. * thus Cross product of X and Y should be inverse of Z. * * if no parent then we are ok. * If parent and parent has issue then we have issue. * */ _check_LHS_RHS(mesh) { let _issue = false; let root = mesh.parent; if (root == null) return false; this._setLocalAxes(root); let actualZ = babylonjs_1.Vector3.Cross(this._localX, this._localY); //same direction or opposite direction of Z if (babylonjs_1.Vector3.Dot(actualZ, this._localZ) < 0) _issue = true; else _issue = false; this._setLocalAxes(mesh); return _issue; } _renderLoopProcess() { //sync the edit control position and rotation with that of mesh this._ecRoot.position = this._mesh.getAbsolutePivotPoint(); this._setECRotation(); //scale the EditControl so it seems at the same distance from camera/user this._setECScale(); //rotate the free move,rotate,scale pick plane to face the camera/user if (this._local) { this._ecRoot.getWorldMatrix().invertToRef(this._ecMatrix); babylonjs_1.Vector3.TransformCoordinatesToRef(this._mainCamera.position, this._ecMatrix, this._ecTOcamera); //note pALL is child of ecRoot hence lookAt in local space this._pALL.lookAt(this._ecTOcamera, 0, 0, 0, babylonjs_1.Space.LOCAL); } else { this._mainCamera.position.subtractToRef(this._ecRoot.position, this._ecTOcamera); this._pALL.lookAt(this._mainCamera.position, 0, 0, 0, babylonjs_1.Space.WORLD); } //rotate the rotation and planar guide to face the camera/user if (this._rotEnabled) { this._rotRotGuides(); } else if (this._transEnabled) this._rotPlanarGuides(this._tXZ, this._tZY, this._tYX); else if (this._scaleEnabled) this._rotPlanarGuides(this._sXZ, this._sZY, this._sYX); //check pointer over axes only during pointer moves //this.onPointerOver(); } /** * sets rotaion of edit control to that of the mesh */ _setECRotation() { if (this._local) { if (this._mesh.parent == null) { if (this._isEulerian(this._mesh)) { let rot = this._mesh.rotation; babylonjs_1.Quaternion.RotationYawPitchRollToRef(rot.y, rot.x, rot.z, this._ecRoot.rotationQuaternion); } else { this._ecRoot.rotationQuaternion.copyFrom(this._mesh.rotationQuaternion); } } else { if (this._isScaleUnEqual(this._mesh)) return; this._mesh.getWorldMatrix().getRotationMatrixToRef(this._tm); babylonjs_1.Quaternion.FromRotationMatrixToRef(this._tm, this._ecRoot.rotationQuaternion); //this._ecRoot.rotationQuaternion.normalize(); } } } /** * checks if any of the mesh's ancestors has non uniform scale */ _isScaleUnEqual(mesh) { if (mesh.parent == null) return false; while (mesh.parent != null) { if ((mesh.parent.scaling.x != mesh.parent.scaling.y || mesh.parent.scaling.y != mesh.parent.scaling.z)) { return true; } else { mesh = mesh.parent; } } return false; } _setECScale() { this._ecRoot.position.subtractToRef(this._mainCamera.position, this._cameraTOec); babylonjs_1.Vector3.FromArrayToRef(this._mainCamera.getWorldMatrix().asArray(), 8, this._cameraNormal); //get distance of edit control from the camera plane //project "camera to edit control" vector onto the camera normal let parentOnNormal = babylonjs_1.Vector3.Dot(this._cameraTOec, this._cameraNormal) / this._cameraNormal.length(); let s = Math.abs(parentOnNormal / this._distFromCamera); babylonjs_1.Vector3.FromFloatsToRef(s, s, s, this._ecRoot.scaling); //Vector3.FromFloatsToRef(s,s,s,this.pALL.scaling); } //rotate the rotation guides so that they are facing the camera _rotRotGuides() { let rotX = Math.atan(this._ecTOcamera.y / this._ecTOcamera.z); if (this._ecTOcamera.z >= 0) { this._rX.rotation.x = -rotX; } else { this._rX.rotation.x = -rotX - Math.PI; } let rotY = Math.atan(this._ecTOcamera.x / this._ecTOcamera.z); if (this._ecTOcamera.z >= 0) { this._rY.rotation.y = rotY; } else { this._rY.rotation.y = rotY + Math.PI; } let rotZ = Math.atan(this._ecTOcamera.x / this._ecTOcamera.y); if (this._ecTOcamera.y >= 0) { this._rZ.rotation.z = -rotZ; } else { this._rZ.rotation.z = -rotZ - Math.PI; } } /** * rotate the planar guide so that they are facing the camera */ _rotPlanarGuides(XZ, ZY, YX) { let ec = this._ecTOcamera; XZ.rotation.x = 0; XZ.rotation.y = 0; XZ.rotation.z = 0; ZY.rotation.x = 0; ZY.rotation.y = 0; ZY.rotation.z = 0; YX.rotation.x = 0; YX.rotation.y = 0; YX.rotation.z = 0; if (ec.x <= 0 && ec.y >= 0 && ec.z >= 0) { XZ.rotation.z = 3.14; YX.rotation.y = 3.14; } else if (ec.x <= 0 && ec.y >= 0 && ec.z <= 0) { XZ.rotation.y = 3.14; ZY.rotation.y = 3.14; YX.rotation.y = 3.14; } else if (ec.x >= 0 && ec.y >= 0 && ec.z <= 0) { XZ.rotation.x = 3.14; ZY.rotation.y = 3.14; } else if (ec.x >= 0 && ec.y <= 0 && ec.z >= 0) { ZY.rotation.z = 3.14; YX.rotation.x = 3.14; } else if (ec.x <= 0 && ec.y <= 0 && ec.z >= 0) { XZ.rotation.z = 3.14; ZY.rotation.z = 3.14; YX.rotation.z = 3.14; } else if (ec.x <= 0 && ec.y <= 0 && ec.z <= 0) { XZ.rotation.y = 3.14; ZY.rotation.x = 3.14; YX.rotation.z = 3.14; } else if (ec.x >= 0 && ec.y <= 0 && ec.z <= 0) { XZ.rotation.x = 3.14; ZY.rotation.x = 3.14; YX.rotation.x = 3.14; } } switchTo(mesh) { mesh.computeWorldMatrix(true); this._mesh = mesh; this._setLocalAxes(mesh); this._actHist = new ActHist(mesh, 10); this._lhsRhs = this._check_LHS_RHS(mesh); this.refreshBoundingInfo(); } switchCamera(camera) { this._mainCamera = camera; } setUndoCount(c) { this._actHist.setCapacity(c); } undo() { let at = this._actHist.undo(); this._mesh.computeWorldMatrix(true); this._setLocalAxes(this._mesh); this._callActionStartListener(at); this._callActionListener(at); this._callActionEndListener(at); } redo() { let at = this._actHist.redo(); this._mesh.computeWorldMatrix(true); this._setLocalAxes(this._mesh); this._callActionStartListener(at); this._callActionListener(at); this._callActionEndListener(at); } /** * detach the edit control from the mesh and dispose off all * resources created by the edit control */ detach() { this._canvas.removeEventListener("pointerdown", this._pointerdown, false); this._canvas.removeEventListener("pointerup", this._pointerup, false); this._canvas.removeEventListener("pointermove", this._pointermove, false); this._scene.unregisterBeforeRender(this._renderer); this.removeAllActionListeners(); this._disposeAll(); } /** * hide the edit control. use show() to unhide the control. */ hide() { this._hidden = true; if (this._transEnabled) { this._prevState = "T"; this.disableTranslation(); } else if (this._rotEnabled) { this._prevState = "R"; this.disableRotation(); } else if (this._scaleEnabled) { this._prevState = "S"; this.disableScaling(); } this._hideCommonAxes(); } _hideCommonAxes() { this._xaxis.visibility = 0; this._yaxis.visibility = 0; this._zaxis.visibility = 0; } _showCommonAxes() { this._xaxis.visibility = this._visibility; this._yaxis.visibility = this._visibility; this._zaxis.visibility = this._visibility; } /** * unhide the editcontrol hidden using the hide() method */ show() { this._hidden = false; this._showCommonAxes(); if (this._prevState == "T") this.enableTranslation(); else if (this._prevState == "R") this.enableRotation(); else if (this._prevState == "S") this.enableScaling(); } /** * check if the editcontrol was hidden using the hide() methods */ isHidden() { return this._hidden; } _disposeAll() { this._ecRoot.dispose(); this._disposeMaterials(); this._actHist = null; } addActionListener(actionListener) { this._actionListener = actionListener; } removeActionListener() { this._actionListener = null; } addActionStartListener(actionStartListener) { this._actionStartListener = actionStartListener; } removeActionStartListener() { this._actionStartListener = null; } addActionEndListener(actionEndListener) { this._actionEndListener = actionEndListener; } removeActionEndListener() { this._actionEndListener = null; } removeAllActionListeners() { this._actionListener = null; this._actionStartListener = null; this._actionEndListener = null; } _onPointerDown(evt) { evt.preventDefault(); this._pDown = true; if (evt.button != 0) return; let engine = this._scene.getEngine(); let x = (engine.isPointerLock) ? this._canvas.width * 0.5 : this._scene.pointerX; let y = (engine.isPointerLock) ? this._canvas.height * 0.5 : this._scene.pointerY; let pickResult = this._scene.pick(x, y, (mesh) => { if (this._transEnabled) { if ((mesh == this._tX) || (mesh == this._tY) || (mesh == this._tZ) || (mesh == this._tXZ) || (mesh == this._tZY) || (mesh == this._tYX) || (mesh == this._tAll)) return true; } else if ((this._rotEnabled)) { if ((mesh == this._rX) || (mesh == this._rY) || (mesh == this._rZ) || (mesh == this._rAll)) return true; } else if ((this._scaleEnabled)) { if ((mesh == this._sX) || (mesh == this._sY) || (mesh == this._sZ) || (mesh == this._sXZ) || (mesh == this._sZY) || (mesh == this._sYX) || (mesh == this._sAll)) return true; } return false; }, false, this._mainCamera); if (pickResult.hit) { //this.setAxesVisiblity(0); this._axisPicked = pickResult.pickedMesh; let childs = this._axisPicked.getChildren(); if (childs.length > 0) { childs[0].visibility = this._visibility; } else { this._axisPicked.visibility = this._visibility; } let name = this._axisPicked.name; if ((name == "X")) this._bXaxis.visibility = 1; else if ((name == "Y")) this._bYaxis.visibility = 1; else if ((name == "Z")) this._bZaxis.visibility = 1; else if ((name == "XZ")) { this._bXaxis.visibility = 1; this._bZaxis.visibility = 1; } else if ((name == "ZY")) { this._bZaxis.visibility = 1; this._bYaxis.visibility = 1; } else if ((name == "YX")) { this._bYaxis.visibility = 1; this._bXaxis.visibility = 1; } else if ((name == "ALL")) { this._bXaxis.visibility = 1; this._bYaxis.visibility = 1; this._bZaxis.visibility = 1; } this._setEditing(true); //lets find out where we are on the pickplane this._pickedPlane = this._getPickPlane(this._axisPicked); if (this._pickedPlane != null) { this._prevPos = this._getPosOnPickPlane(); } else { this._prevPos = null; } window.setTimeout(((cam, can) => { return this._detachCamera(cam, can); }), 0, this._mainCamera, this._canvas); } } _setEditing(editing) { this._editing = editing; if (editing) { this._setActionType(); if (this._actionType == ActionType.ROT) { this._snapRA = 0; } this._callActionStartListener(this._actionType); } else { this._callActionEndListener(this._actionType); } } isEditing() { return this._editing; } /** * no camera movement during edit */ _detachCamera(cam, can) { let camera = cam; let canvas = can; let engine = this._scene.getEngine(); if (!engine.isPointerLock) { camera.detachControl(canvas); } } isPointerOver() { return this._pointerIsOver; } _onPointerOver() { //if(this.pDown) return; let engine = this._scene.getEngine(); let x = (engine.isPointerLock) ? this._canvas.width * 0.5 : this._scene.pointerX; let y = (engine.isPointerLock) ? this._canvas.height * 0.5 : this._scene.pointerY; let pickResult = this._scene.pick(x, y, (mesh) => { if (this._transEnabled) { if ((mesh == this._tX) || (mesh == this._tY) || (mesh == this._tZ) || (mesh == this._tXZ) || (mesh == this._tZY) || (mesh == this._tYX) || (mesh == this._tAll)) return true; } else if ((this._rotEnabled)) { if ((mesh == this._rX) || (mesh == this._rY) || (mesh == this._rZ) || (mesh == this._rAll)) return true; } else if (this._scaleEnabled) { if ((mesh == this._sX) || (mesh == this._sY) || (mesh == this._sZ) || (mesh == this._sXZ) || (mesh == this._sZY) || (mesh == this._sYX) || (mesh == this._sAll)) return true; } return false; }, false, this._mainCamera); if (pickResult.hit) { //if we are still over the same axis mesh then don't do anything if (pickResult.pickedMesh != this._prevOverMesh) { this._pointerIsOver = true; //if we moved directly from one axis mesh to this then clean up the prev axis mesh this._clearPrevOverMesh(); this._prevOverMesh = pickResult.pickedMesh; if (this._rotEnabled) { this._savedCol = this._prevOverMesh.getChildren()[0].color; this._prevOverMesh.getChildren()[0].color = this._yellowCol; } else { let childs = this._prevOverMesh.getChildren(); if (childs.length > 0) { this._savedMat = childs[0].material; childs[0].material = this._whiteMat; } else { this._savedMat = this._prevOverMesh.material; this._prevOverMesh.material = this._whiteMat; } } if (this._prevOverMesh.name == "X") { this._xaxis.color = this._yellowCol; } else if (this._prevOverMesh.name == "Y") { this._yaxis.color = this._yellowCol; } else if (this._prevOverMesh.name == "Z") { this._zaxis.color = this._yellowCol; } } } else { this._pointerIsOver = false; if (this._prevOverMesh != null) { this._restoreColor(this._prevOverMesh); this._prevOverMesh = null; } } } //clean up any axis we might have been howering over before _clearPrevOverMesh() { if (this._prevOverMesh != null) { this._prevOverMesh.visibility = 0; this._restoreColor(this._prevOverMesh); } } _restoreColor(mesh) { switch (mesh.name) { case "X": this._xaxis.color = this._redCol; break; case "Y": this._yaxis.color = this._greenCol; break; case "Z": this._zaxis.color = this._blueCol; break; } if (this._rotEnabled) { mesh.getChildren()[0].color = this._savedCol; } else { let childs = mesh.getChildren(); if (childs.length > 0) { childs[0].material = this._savedMat; } else { mesh.material = this._savedMat; } } } _onPointerUp(evt) { this._pDown = false; if (this._editing) { let engine = this._scene.getEngine(); if (!engine.isPointerLock) { this._mainCamera.attachControl(true); } this._setEditing(false); //this.setAxesVisiblity(1); this._hideBaxis(); if (this._prevOverMesh != null) { this._restoreColor(this._prevOverMesh); this._prevOverMesh = null; } this._actHist.add(this._actionType); } } _setActionType() { if (this._transEnabled) { this._actionType = ActionType.TRANS; } else if ((this._rotEnabled)) { this._actionType = ActionType.ROT; } else if ((this._scaleEnabled)) { this._actionType = ActionType.SCALE; } } _callActionListener(at) { //call actionListener if registered if (this._actionListener != null) { this._actionListener(at); } } _callActionStartListener(at) { //call actionListener if registered if (this._actionStartListener != null) { this._actionStartListener(at); } } _callActionEndListener(at) { //call actionListener if registered if (this._actionEndListener != null) { this._actionEndListener(at); } } _onPointerMove(evt) { if (!this._pDown) { this._onPointerOver(); return; } if (!this._editing) return; if (this._prevPos == null) return; let newPos = this._getPosOnPickPlane(); if (newPos == null) return; if (this._rotEnabled) { this._doRotation(this._mesh, this._axisPicked, newPos, this._prevPos); } else { let diff = newPos.subtract(this._prevPos); if (diff.x == 0 && diff.y == 0 && diff.z == 0) return; if (this._transEnabled) { this._doTranslation(diff); } else { if (this._scaleEnabled && this._local) this._doScaling(diff); } } this._prevPos = newPos; this._callActionListener(this._actionType); } _getPickPlane(axis) { let n = axis.name; if (this._transEnabled || this._scaleEnabled) { if (n == "XZ") return this._pXZ; else if (n == "ZY") return this._pZY; else if (n == "YX") return this._pYX; else if (n == "ALL") return this._pALL; else { //get the position of camera in the edit control frame of reference this._ecRoot.getWorldMatrix().invertToRef(this._ecMatrix); babylonjs_1.Vector3.TransformCoordinatesToRef(this._mainCamera.position, this._ecMatrix, this._ecTOcamera); let c = this._ecTOcamera; if (n === "X") { if (Math.abs(c.y) > Math.abs(c.z)) { return this._pXZ; } else return this._pYX; } else if (n === "Z") { if (Math.abs(c.y) > Math.abs(c.x)) { return this._pXZ; } else return this._pZY; } else if (n === "Y") { if (Math.abs(c.z) > Math.abs(c.x)) { return this._pYX; } else return this._pZY; } } } else if (this._rotEnabled) { this._rotate2 = false; //get the position of camera in the edit control frame of reference this._ecRoot.getWorldMatrix().invertToRef(this._ecMatrix); babylonjs_1.Vector3.TransformCoordinatesToRef(this._mainCamera.position, this._ecMatrix, this._ecTOcamera); let c = this._ecTOcamera; //if camera is too close to the rotation plane then use alternate rotation process switch (n) { case "X": if (Math.abs(c.x) < 0.2) { this._rotate2 = true; return this._pALL; } else return this._pZY; case "Y": if (Math.abs(c.y) < 0.2) { this._rotate2 = true; return this._pALL; } else return this._pXZ; case "Z": if (Math.abs(c.z) < 0.2) { this._rotate2 = true; return this._pALL; } else return this._pYX; default: return this._pALL; } } else return null; } _doTranslation(diff) { if ((this._mesh.parent != null) && this._isScaleUnEqual(this._mesh)) { this._setLocalAxes(this._ecRoot); } else { this._setLocalAxes(this._mesh); } let n = this._axisPicked.name; this._transBy.x = 0; this._transBy.y = 0; this._transBy.z = 0; if ((n == "X") || (n == "XZ") || (n == "YX") || (n == "ALL")) { if (this._local) this._transBy.x = babylonjs_1.Vector3.Dot(diff, this._localX) / this._localX.length(); else this._transBy.x = diff.x; } if ((n == "Y") || (n == "ZY") || (n == "YX") || (n == "ALL")) { if (this._local) this._transBy.y = babylonjs_1.Vector3.Dot(diff, this._localY) / this._localY.length(); else this._transBy.y = diff.y; } if ((n == "Z") || (n == "XZ") || (n == "ZY") || (n == "ALL")) { if (this._local) this._transBy.z = babylonjs_1.Vector3.Dot(diff, this._localZ) / this._localZ.length(); else this._transBy.z = diff.z; } this._transWithSnap(this._mesh, this._transBy, this._local); // bound the translation if (this._transBoundsMin) { this._mesh.position.x = Math.max(this._mesh.position.x, this._transBoundsMin.x); this._mesh.position.y = Math.max(this._mesh.position.y, this._transBoundsMin.y); this._mesh.position.z = Math.max(this._mesh.position.z, this._transBoundsMin.z); } if (this._transBoundsMax) { this._mesh.position.x = Math.min(this._mesh.position.x, this._transBoundsMax.x); this._mesh.position.y = Math.min(this._mesh.position.y, this._transBoundsMax.y); this._mesh.position.z = Math.min(this._mesh.position.z, this._transBoundsMax.z); } this._mesh.computeWorldMatrix(true); } _transWithSnap(mesh, trans, local) { if (this._snapT) { let snapit = false; this._snapTV.addInPlace(trans); if (Math.abs(this._snapTV.x) > this._tSnap.x) { if (this._snapTV.x > 0) trans.x = this._tSnap.x; else trans.x = -this._tSnap.x; snapit = true; } if (Math.abs(this._snapTV.y) > this._tSnap.y) { if (this._snapTV.y > 0) trans.y = this._tSnap.y; else trans.y = -this._tSnap.y; snapit = true; } if (Math.abs(this._snapTV.z) > this._tSnap.z) { if (this._snapTV.z > 0) trans.z = this._tSnap.z; else trans.z = -this._tSnap.z; snapit = true; } if (snapit) { if (Math.abs(trans.x) !== this._tSnap.x) trans.x = 0; if (Math.abs(trans.y) !== this._tSnap.y) trans.y = 0; if (Math.abs(trans.z) !== this._tSnap.z) trans.z = 0; babylonjs_1.Vector3.FromFloatsToRef(0, 0, 0, this._snapTV); snapit = false; } else { return; } } if (local) { //locallyTranslate moves the mesh wrt the absolute location not pivotlocation :( //this.mesh.locallyTranslate(trans); // this._localX.normalizeToRef(this._tv1); this._localY.normalizeToRef(this._tv2); this._localZ.normalizeToRef(this._tv3); this._mesh.translate(this._tv1, trans.x, babylonjs_1.Space.WORLD); this._mesh.translate(this._tv2, trans.y, babylonjs_1.Space.WORLD); this._mesh.translate(this._tv3, trans.z, babylonjs_1.Space.WORLD); } else { if (this._mesh.parent == null) { this._mesh.position.addInPlace(trans); } else { this._mesh.setAbsolutePosition(trans.addInPlace(this._mesh.absolutePosition)); } } } _doScaling(diff) { this._setLocalAxes(this._mesh); this._scale.x = 0; this._scale.y = 0; this._scale.z = 0; let n = this._axisPicked.name; if ((n == "X") || (n == "XZ") || (n == "YX")) { this._scale.x = babylonjs_1.Vector3.Dot(diff, this._localX) / this._localX.length(); if (this._mesh.scaling.x < 0) this._scale.x = -this._scale.x; //if(this.lhsRhs) this.scale.x=-this.scale.x; } if ((n == "Y") || (n == "ZY") || (n == "YX")) { this._scale.y = babylonjs_1.Vector3.Dot(diff, this._localY) / this._localY.length(); if (this._mesh.scaling.y < 0) this._scale.y = -this._scale.y; } if ((n == "Z") || (n == "XZ") || (n == "ZY")) { this._scale.z = babylonjs_1.Vector3.Dot(diff, this._localZ) / this._localZ.length(); if (this._mesh.scaling.z < 0) this._scale.z = -this._scale.z; } //as the mesh becomes large reduce the amount by which we scale. let bbd = this._boundingDimesion; this._scale.x = this._scale.x / bbd.x; this._scale.y = this._scale.y / bbd.y; this._scale.z = this._scale.z / bbd.z; if (n == "ALL") { //project movement along camera up vector //if up then scale up else scale down let s = babylonjs_1.Vector3.Dot(diff, this._mainCamera.upVector); s = s / Math.max(bbd.x, bbd.y, bbd.z); this._scale.copyFromFloats(s, s, s); } else { let inPlane = false; if (n == "XZ") { inPlane = true; if (Math.abs(this._scale.x) > Math.abs(this._scale.z)) { this._scale.z = this._scale.x; } else this._scale.x = this._scale.z; } else if (n == "ZY") { inPlane = true; if (Math.abs(this._scale.z) > Math.abs(this._scale.y)) { this._scale.y = this._scale.z; } else this._scale.z = this._scale.y; } else if (n == "YX") { inPlane = true; if (Math.abs(this._scale.y) > Math.abs(this._scale.x)) { this._scale.x = this._scale.y; } else this._scale.y = this._scale.x; } if (inPlane) { //check if the mouse/pointer was moved towards camera or away from camera //if towards then scale up else scale down this._ecRoot.position.subtractToRef(this._mainCamera.position, this._cameraTOec); let s = babylonjs_1.Vector3.Dot(diff, this._cameraTOec); this._scale.x = Math.abs(this._scale.x); this._scale.y = Math.abs(this._scale.y); this._scale.z = Math.abs(this._scale.z); if (s > 0) { if (this._mesh.scaling.x > 0) this._scale.x = -this._scale.x; //if(this.lhsRhs) this.scale.y=Math.abs(this.scale.y); if (this._mesh.scaling.y > 0) this._scale.y = -this._scale.y; if (this._mesh.scaling.z > 0) this._scale.z = -this._scale.z; } else { //this.scale.x=Math.abs(this.scale.x); //if(this.lhsRhs) this.scale.y=-Math.abs(this.scale.y); //else this.scale.y=Math.abs(this.scale.y); if (this._mesh.scaling.x < 0) this._scale.x = -this._scale.x; if (this._mesh.scaling.y < 0) this._scale.y = -this._scale.y; if (this._mesh.scaling.z < 0) this._scale.z = -this._scale.z; } } } this._scaleWithSnap(this._mesh, this._scale); // bound the scale if (this._scaleBoundsMin) { this._mesh.scaling.x = Math.max(this._mesh.scaling.x, this._scaleBoundsMin.x); this._mesh.scaling.y = Math.max(this._mesh.scaling.y, this._scaleBoundsMin.y); this._mesh.scaling.z = Math.max(this._mesh.scaling.z, this._scaleBoundsMin.z); } if (this._scaleBoundsMax) { this._mesh.scaling.x = Math.min(this._mesh.scaling.x, this._scaleBoundsMax.x); this._mesh.scaling.y = Math.min(this._mesh.scaling.y, this._scaleBoundsMax.y); this._mesh.scaling.z = Math.min(this._mesh.scaling.z, this._scaleBoundsMax.z); } } _scaleWithSnap(mesh, p) { if (this._snapS) { let snapit = false; this._snapSV.addInPlace(p); if (Math.abs(this._snapSV.x) > this._scaleSnap) { if (p.x > 0) p.x = this._scaleSnap; else p.x = -this._scaleSnap; snapit = true; } if (Math.abs(this._snapSV.y) > this._scaleSnap) { if (p.y > 0) p.y = this._scaleSnap; else p.y = -this._scaleSnap; snapit = true; } if (Math.abs(this._snapSV.z) > this._scaleSnap) { if (p.z > 0) p.z = this._scaleSnap; else p.z = -this._scaleSnap; snapit = true; } if (!snapit) return; if ((Math.abs(p.x) !== this._scaleSnap) && (p.x !== 0)) p.x = 0; if ((Math.abs(p.y) !== this._scaleSnap) && (p.y !== 0)) p.y = 0; if ((Math.abs(p.z) !== this._scaleSnap) && (p.z !== 0)) p.z = 0; babylonjs_1.Vector3.FromFloatsToRef(0, 0, 0, this._snapSV); snapit = false; } mesh.scaling.addInPlace(p); } /* * This would be called after rotation or scaling as the local axes direction or length might have changed * We need to set the local axis as these are used in all three modes to figure out * direction of mouse move wrt the axes * TODO should use world pivotmatrix instead of worldmatrix - incase pivot axes were rotated? */ _setLocalAxes(mesh) { let meshMatrix = mesh.getWorldMatrix(); babylonjs_1.Vector3.FromArrayToRef(meshMatrix.m, 0, this._localX); babylonjs_1.Vector3.FromArrayToRef(meshMatrix.m, 4, this._localY); babylonjs_1.Vector3.FromArrayToRef(meshMatrix.m, 8, this._localZ); } _getBoundingDimension(mesh) { if (mesh instanceof babylonjs_1.AbstractMesh) { { } let bb = mesh.getBoundingInfo().boundingBox; let bd = bb.maximum.subtract(bb.minimum); if (bd.x == 0) bd.x = 1; if (bd.y == 0) bd.y = 1; if (bd.z == 0) bd.z = 1; return bd; } else return new babylonjs_1.Vector3(1, 1, 1); } /* * * For the sake of speed the editcontrol calculates bounding info only once. * This is in the constructor. * Now The boundingbox dimension can change if the mesh is baked. * If the editcontrol is attached to the mesh when the mesh was baked then * the scaling speed will be incorrect. * Thus client application should call refreshBoundingInfo if it bakes the mesh. * */ refreshBoundingInfo() { this._boundingDimesion = this._getBoundingDimension(this._mesh); } _doRotation(mesh, axis, newPos, prevPos) { //for now no rotation if parents have non uniform scale if (this._local && (this._mesh.parent != null) && this._isScaleUnEqual(mesh)) { this._setLocalAxes(this._ecRoot); } else { this._setLocalAxes(mesh); } let angle = 0; //rotation axis let rAxis; if (axis == this._rX) rAxis = this._local ? this._localX : babylonjs_1.Axis.X; else if (axis == this._rY) rAxis = this._local ? this._localY : babylonjs_1.Axis.Y; else if (axis == this._rZ) rAxis = this._local ? this._localZ : babylonjs_1.Axis.Z; this._ecRoot.position.subtractToRef(this._mainCamera.position, this._cameraTOec); /** * A)first find the angle and the direction (clockwise or anticlockwise) by which the user was trying to rotate * from the user(camera) perspective */ if (this._rotate2) { angle = this._getAngle2(prevPos, newPos, this._mainCamera.position, this._cameraTOec, rAxis); //TODO check why we need to handle righ hand this way if (this._scene.useRightHandedSystem) angle = -angle; } else { angle = this._getAngle(prevPos, newPos, mesh.getAbsolutePivotPoint(), this._cameraTOec); } if (this._lhsRhs) { angle = -angle; } /** * B)then rotate based on users(camera) postion and orientation in the local/world space * */ if (this._snapR) { this._snapRA += angle; angle = 0; if (Math.abs(this._snapRA) >= this._rotSnap) { if (this._snapRA > 0) angle = this._rotSnap; else angle = -this._rotSnap; this._snapRA = 0; } } if (angle !== 0) { this._cameraTOec.normalize(); if (axis == this._rAll) { mesh.rotate(this._cameraTOec, -angle, babylonjs_1.Space.WORLD); } else { if (babylonjs_1.Vector3.Dot(rAxis, this._cameraTOec) >= 0) angle = -angle; mesh.rotate(rAxis, angle, babylonjs_1.Space.WORLD); } if (this._local) { if (this._lhsRhs) { angle = -angle; } if ((this._mesh.parent != null) && this._isScaleUnEqual(mesh)) { if (axis == this._rAll) { this._ecRoot.rotate(this._cameraTOec, -angle, babylonjs_1.Space.WORLD); } else { this._ecRoot.rotate(rAxis, angle, babylonjs_1.Space.WORLD); } } } } } _getPosOnPickPlane() { let engine = this._scene.getEngine(); let x = (engine.isPointerLock) ? this._canvas.width * 0.5 : this._scene.pointerX; let y = (engine.isPointerLock) ? this._canvas.height * 0.5 : this._scene.pointerY; let pickinfo = this._scene.pick(x, y, (mesh) => { return mesh == this._pickedPlane; }, null, this._mainCamera); if (pickinfo.hit) { return pickinfo.pickedPoint; } else { return null; } } _hideBaxis() { this._bXaxis.visibility = 0; this._bYaxis.visibility = 0; this._bZaxis.visibility = 0; } // private _setAxesVisiblity(v: number) { // if (this._transEnabled) { // this._tEndX.visibility = v; // this._tEndY.visibility = v; // this._tEndZ.visibility = v; // this._tEndXZ.visibility = v; // this._tEndZY.visibility = v; // this._tEndYX.visibility = v; // this._tEndAll.visibility = v; // } // if (this._rotEnabled) { // this._rEndX.visibility = v; // this._rEndY.visibility = v; // this._rEndZ.visibility = v; // this._rEndAll.visibility = v; // } // if (this._scaleEnabled) { // this._sEndX.visibility = v; // this._sEndY.visibility = v; // this._sEndZ.visibility = v; // this._sEndXZ.visibility = v; // this._sEndZY.visibility = v; // this._sEndYX.visibility = v; // this._sEndAll.visibility = v; // } // } getRotationQuaternion() { return this._ecRoot.rotationQuaternion; } getPosition() { return this._ecRoot.position; } isTranslationEnabled() { return this._transEnabled; } enableTranslation() { if (this._hidden) return; if (this._tX == null) { this._createTransAxes(); this._tCtl.parent = this._ecRoot; } this._clearPrevOverMesh(); if (!this._transEnabled) { this._setVisibility(this._all_tEnd, this._visibility); this._transEnabled = true; this.disableRotation(); this.disableScaling(); } } disableTranslation() { if (this._transEnabled) { this._setVisibility(this._all_tEnd, 0); this._transEnabled = false; } } isRotationEnabled() { return this._rotEnabled; } enableRotation() { if (this._hidden) return; if (this._rCtl == null) { this._createRotAxes(); this._rCtl.parent = this._ecRoot; } this._clearPrevOverMesh(); if (!this._rotEnabled) { this._setVisibility(this._all_rEnd, this._visibility); this._rotEnabled = true; this.disableTranslation(); this.disableScaling(); } } disableRotation() { if (this._rotEnabled) { this._setVisibility