UNPKG

babylonjs-editcontrol

Version:
1,302 lines (1,150 loc) 81.4 kB
import { AbstractMesh, Axis, BoundingBox, Camera, Color3, LinesMesh, Material, Matrix, Mesh, MeshBuilder, Node, PickingInfo, Quaternion, Scene, Space, StandardMaterial, Vector3, TransformNode, Engine, DeepImmutableObject, UtilityLayerRenderer } from 'babylonjs'; enum ActionType { TRANS = 0, ROT = 1, SCALE = 2 } /** * 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. * */ export class EditControl { private _mesh: TransformNode; private _canvas: HTMLCanvasElement; private _scene: Scene; private _utilLayer: UtilityLayerRenderer; private _mainCamera: Camera; //root of the edit control private _ecRoot: Mesh; private _local: boolean = true; private _snapT: boolean = false; private _snapR: boolean = false; private _transSnap: number = 1; private _rotSnap: number = Math.PI / 18; private _axesLen: number = 0.4; private _axesScale: number = 1; //how close to an axis should we get before we can pick it private _pickWidth: number = 0.02; private _redMat: StandardMaterial; private _greenMat: StandardMaterial; private _blueMat: StandardMaterial; private _whiteMat: StandardMaterial; private _yellowMat: StandardMaterial; private _redCol: Color3 = new Color3(1, 0.2, 0.2); private _greenCol: Color3 = new Color3(0.2, 1, 0.2); private _blueCol: Color3 = new Color3(0.2, 0.2, 1); private _whiteCol: Color3 = new Color3(1, 1, 1); private _yellowCol: Color3 = new Color3(1, 1, 0.2); private _actHist: ActHist; private _renderer: () => void; private _pointerdown: EventListener; private _pointerup: EventListener; private _pointermove: EventListener; //axes visibility private _visibility: number = 0.75; //lhs-rhs issue. lhs mesh in rhs or rhs mesh in lhs private _lhsRhs: boolean = false; public constructor(mesh: TransformNode, camera: Camera, canvas: HTMLCanvasElement, scale?: number, eulerian?: boolean, pickWidth?: number) { this._mesh = mesh; this._mainCamera = camera; this._canvas = canvas; if (scale != null) { this._axesScale = scale; } if (eulerian !== null) { this._eulerian = eulerian; } else { this._eulerian = false; } this._checkQuaternion(); if (pickWidth != null) { this._pickWidth = pickWidth; } this._utilLayer = 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); console.log("lhs rhs issue " + this._lhsRhs); //build the edit control axes this._ecRoot = new Mesh("", this._scene); this._ecRoot.rotationQuaternion = Quaternion.Identity(); this._ecRoot.visibility = 0; this._ecRoot.isPickable = false; this._createMaterials(this._scene); let guideAxes: Mesh = this._createCommonAxes(); guideAxes.parent = this._ecRoot; //build the pickplanes let pickPlanes: Mesh = 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); } public getRoot(): AbstractMesh { return this._ecRoot; } //make sure that if eulerian is set to false then mesh's rotation is in quaternion //throw error and exit if not so. private _checkQuaternion() { if (!this._eulerian) { if ((this._mesh.rotationQuaternion == null) || (this._mesh.rotationQuaternion == undefined)) { throw "Error: Eulerian is set to false but the mesh's rotationQuaternion is not set."; } } } /** * 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. * */ private _check_LHS_RHS(mesh: TransformNode) { let _issue: boolean = false; let root: Node = mesh.parent; if (root == null) return false; this._setLocalAxes(root); let actualZ = Vector3.Cross(this._localX, this._localY); //same direction or opposite direction of Z if (Vector3.Dot(actualZ, this._localZ) < 0) _issue = true; else _issue = false; this._setLocalAxes(mesh); return _issue; } private _ecMatrix: Matrix = new Matrix(); //edit control to camera vector private _ecTOcamera: Vector3 = new Vector3(0, 0, 0); private _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); 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, Space.LOCAL); } else { this._mainCamera.position.subtractToRef(this._ecRoot.position, this._ecTOcamera); this._pALL.lookAt(this._mainCamera.position, 0, 0, 0, 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 */ private _setECRotation() { if (this._local) { if (this._mesh.parent == null) { if (this._eulerian) { let rot: Vector3 = this._mesh.rotation; 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); Quaternion.FromRotationMatrixToRef(this._tm, this._ecRoot.rotationQuaternion); //this._ecRoot.rotationQuaternion.normalize(); } } } /** * checks if any of the mesh's ancestors has non uniform scale */ private _isScaleUnEqual(mesh: TransformNode): boolean { if (mesh.parent == null) return false; while (mesh.parent != null) { if (((<Mesh>mesh.parent).scaling.x != (<Mesh>mesh.parent).scaling.y || (<Mesh>mesh.parent).scaling.y != (<Mesh>mesh.parent).scaling.z)) { return true; } else { mesh = <Mesh>mesh.parent; } } return false; } //how far away from camera should the edit control appear to be private _distFromCamera: number = 2; //vector from camera to edit control private _cameraTOec: Vector3 = new Vector3(0, 0, 0); private _cameraNormal: Vector3 = new Vector3(0, 0, 0); private _setECScale() { this._ecRoot.position.subtractToRef(this._mainCamera.position, this._cameraTOec); Vector3.FromArrayToRef(<DeepImmutableObject<Float32Array>>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: number = Vector3.Dot(this._cameraTOec, this._cameraNormal) / this._cameraNormal.length(); let s: number = Math.abs(parentOnNormal / this._distFromCamera); 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 private _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 */ private _rotPlanarGuides(XZ: Mesh, ZY: Mesh, YX: Mesh) { let ec: Vector3 = 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; } } public switchTo(mesh: TransformNode, eulerian?: boolean) { mesh.computeWorldMatrix(true); this._mesh = mesh; if (eulerian != null) { this._eulerian = eulerian; } this._checkQuaternion(); this._setLocalAxes(mesh); this._actHist = new ActHist(mesh, 10); this._lhsRhs = this._check_LHS_RHS(mesh); this.refreshBoundingInfo(); } public switchCamera(camera: Camera) { this._mainCamera = camera; } public setUndoCount(c: number) { this._actHist.setCapacity(c); } public undo() { let at: number = this._actHist.undo(); this._mesh.computeWorldMatrix(true); this._setLocalAxes(this._mesh); this._callActionStartListener(at); this._callActionListener(at); this._callActionEndListener(at); } public redo() { let at: number = 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 */ public 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(); } private _prevState: String = ""; private _hidden: boolean = false; /** * hide the edit control. use show() to unhide the control. */ public 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(); } private _hideCommonAxes() { this._xaxis.visibility = 0; this._yaxis.visibility = 0; this._zaxis.visibility = 0; } private _showCommonAxes() { this._xaxis.visibility = this._visibility; this._yaxis.visibility = this._visibility; this._zaxis.visibility = this._visibility; } /** * unhide the editcontrol hidden using the hide() method */ public 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 */ public isHidden(): boolean { return this._hidden; } private _disposeAll() { this._ecRoot.dispose(); this._disposeMaterials(); this._actHist = null; } private _actionListener: (actionType: number) => void = null; private _actionStartListener: (actionType: number) => void = null; private _actionEndListener: (actionType: number) => void = null; public addActionListener(actionListener: (actionType: number) => void) { this._actionListener = actionListener; } public removeActionListener() { this._actionListener = null; } public addActionStartListener(actionStartListener: (actionType: number) => void) { this._actionStartListener = actionStartListener; } public removeActionStartListener() { this._actionStartListener = null; } public addActionEndListener(actionEndListener: (actionType: number) => void) { this._actionEndListener = actionEndListener; } public removeActionEndListener() { this._actionEndListener = null; } public removeAllActionListeners() { this._actionListener = null; this._actionStartListener = null; this._actionEndListener = null; } private _pDown: boolean = false; private _axisPicked: Mesh; private _onPointerDown(evt: Event) { evt.preventDefault(); this._pDown = true; if ((<PointerEvent>evt).button != 0) return; let engine: 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: PickingInfo = 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 = <Mesh>pickResult.pickedMesh; let childs: Node[] = this._axisPicked.getChildren(); if (childs.length > 0) { (<Mesh>childs[0]).visibility = this._visibility; } else { this._axisPicked.visibility = this._visibility; } let name: string = 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); } } private _setEditing(editing: boolean) { this._editing = editing; if (editing) { this._setActionType(); if (this._actionType == ActionType.ROT) { this._snapRA = 0; } this._callActionStartListener(this._actionType); } else { this._callActionEndListener(this._actionType); } } public isEditing(): boolean { return this._editing; } /** * no camera movement during edit */ private _detachCamera(cam: Object, can: Object) { let camera: Camera = <Camera>cam; let canvas: HTMLCanvasElement = <HTMLCanvasElement>can; let engine: Engine = this._scene.getEngine(); if (!engine.isPointerLock) { camera.detachControl(canvas) } } private _prevOverMesh: Mesh; private _pointerIsOver: boolean = false; public isPointerOver(): boolean { return this._pointerIsOver; } private _savedMat: Material; private _savedCol: Color3; private _onPointerOver() { //if(this.pDown) return; let engine: 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: PickingInfo = 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 (<Mesh>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 = <Mesh>pickResult.pickedMesh; if (this._rotEnabled) { this._savedCol = (<LinesMesh>this._prevOverMesh.getChildren()[0]).color; (<LinesMesh>this._prevOverMesh.getChildren()[0]).color = this._whiteCol; } else { let childs: Node[] = this._prevOverMesh.getChildren(); if (childs.length > 0) { this._savedMat = (<Mesh>childs[0]).material; (<Mesh>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._whiteCol; } else if (this._prevOverMesh.name == "Y") { this._yaxis.color = this._whiteCol; } else if (this._prevOverMesh.name == "Z") { this._zaxis.color = this._whiteCol; } } } 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 private _clearPrevOverMesh() { if (this._prevOverMesh != null) { this._prevOverMesh.visibility = 0; this._restoreColor(this._prevOverMesh); } } private _restoreColor(mesh: 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) { (<LinesMesh>mesh.getChildren()[0]).color = this._savedCol; } else { let childs: Node[] = mesh.getChildren(); if (childs.length > 0) { (<Mesh>childs[0]).material = this._savedMat; } else { mesh.material = this._savedMat; } } } private _editing: boolean = false; private _onPointerUp(evt: Event) { this._pDown = false; if (this._editing) { let engine: 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); } } private _actionType: number; private _setActionType() { if (this._transEnabled) { this._actionType = ActionType.TRANS; } else if ((this._rotEnabled)) { this._actionType = ActionType.ROT; } else if ((this._scaleEnabled)) { this._actionType = ActionType.SCALE; } } private _callActionListener(at: number) { //call actionListener if registered if (this._actionListener != null) { this._actionListener(at); } } private _callActionStartListener(at: number) { //call actionListener if registered if (this._actionStartListener != null) { this._actionStartListener(at); } } private _callActionEndListener(at: number) { //call actionListener if registered if (this._actionEndListener != null) { this._actionEndListener(at); } } private _prevPos: Vector3; private _onPointerMove(evt: Event) { if (!this._pDown) { this._onPointerOver(); return; } if (!this._editing) return; if (this._prevPos == null) return; let newPos: Vector3 = this._getPosOnPickPlane(); if (newPos == null) return; if (this._rotEnabled) { this._doRotation(this._mesh, this._axisPicked, newPos, this._prevPos); } else { let diff: Vector3 = 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); } //rotate differently if camera is too close to the rotation plane private _rotate2: boolean = false; private _getPickPlane(axis: Mesh): Mesh { let n: string = 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); 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); 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; } //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 private _transBy: Vector3 = new Vector3(0, 0, 0); private _doTranslation(diff: Vector3) { if ((this._mesh.parent != null) && this._isScaleUnEqual(this._mesh)) { this._setLocalAxes(this._ecRoot); } else { this._setLocalAxes(this._mesh); } let n: string = 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 = 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 = 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 = 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); } private _snapTV: Vector3 = new Vector3(0, 0, 0); private _transWithSnap(mesh: TransformNode, trans: Vector3, local: boolean) { if (this._snapT) { let snapit: boolean = 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; 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, Space.WORLD); this._mesh.translate(this._tv2, trans.y, Space.WORLD); this._mesh.translate(this._tv3, trans.z, Space.WORLD); } else { if (this._mesh.parent == null) { this._mesh.position.addInPlace(trans); } else { this._mesh.setAbsolutePosition(trans.addInPlace(this._mesh.absolutePosition)); } } } private _snapS: boolean = false; private _snapSV: Vector3 = new Vector3(0, 0, 0); private _scaleSnap: number = 0.25; private _scale: Vector3 = new Vector3(0, 0, 0); private _doScaling(diff: Vector3) { this._setLocalAxes(this._mesh); this._scale.x = 0; this._scale.y = 0; this._scale.z = 0; let n: string = this._axisPicked.name; if ((n == "X") || (n == "XZ") || (n == "YX")) { this._scale.x = 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 = 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 = 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: number = 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: boolean = 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: number = 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); } } private _scaleWithSnap(mesh: TransformNode, p: Vector3) { if (this._snapS) { let snapit: boolean = 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; Vector3.FromFloatsToRef(0, 0, 0, this._snapSV); snapit = false; } mesh.scaling.addInPlace(p); } private _localX: Vector3 = new Vector3(0, 0, 0); private _localY: Vector3 = new Vector3(0, 0, 0); private _localZ: Vector3 = new Vector3(0, 0, 0); /* * 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? */ private _setLocalAxes(mesh: Node) { let meshMatrix: Matrix = mesh.getWorldMatrix(); Vector3.FromArrayToRef(<DeepImmutableObject<Float32Array>>meshMatrix.m, 0, this._localX); Vector3.FromArrayToRef(<DeepImmutableObject<Float32Array>>meshMatrix.m, 4, this._localY); Vector3.FromArrayToRef(<DeepImmutableObject<Float32Array>>meshMatrix.m, 8, this._localZ); } /* * boundingDimesion is used by scaling to adjust rate at which a mesh is scaled * with respect to mouse movement. * */ private _boundingDimesion: Vector3; private _getBoundingDimension(mesh: TransformNode): Vector3 { if (mesh instanceof AbstractMesh) { { } let bb: BoundingBox = mesh.getBoundingInfo().boundingBox; let bd: Vector3 = 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 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. * */ public refreshBoundingInfo() { this._boundingDimesion = this._getBoundingDimension(this._mesh); } private _eulerian: boolean = false; private _snapRA: number = 0; private _doRotation(mesh: TransformNode, axis: Mesh, newPos: Vector3, prevPos: Vector3) { //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: number = 0; //rotation axis let rAxis: Vector3; if (axis == this._rX) rAxis = this._local ? this._localX : Axis.X; else if (axis == this._rY) rAxis = this._local ? this._localY : Axis.Y; else if (axis == this._rZ) rAxis = this._local ? this._localZ : 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, Space.WORLD); } else { if (Vector3.Dot(rAxis, this._cameraTOec) >= 0) angle = -angle; mesh.rotate(rAxis, angle, Space.WORLD); } if (this._eulerian) { mesh.rotation = mesh.rotationQuaternion.toEulerAngles(); mesh.rotationQuaternion = null; } 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, Space.WORLD); } else { this._ecRoot.rotate(rAxis, angle, Space.WORLD); } } } } } private _getPosOnPickPlane(): Vector3 { let engine: 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: PickingInfo = this._scene.pick(x, y, (mesh) => { return mesh == this._pickedPlane; }, null, this._mainCamera); if (pickinfo.hit) { return pickinfo.pickedPoint; } else { return null; } } private _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; // } // } public getRotationQuaternion(): Quaternion { return this._ecRoot.rotationQuaternion } public getPosition(): Vector3 { return this._ecRoot.position; } private _transEnabled: boolean = false; public isTranslationEnabled(): boolean { return this._transEnabled; } public 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(); } } public disableTranslation() { if (this._transEnabled) { this._setVisibility(this._all_tEnd, 0); this._transEnabled = false; } } private _rotEnabled: boolean = false; public isRotationEnabled(): boolean { return this._rotEnabled; } public returnEuler(euler: boolean) { this._eulerian = euler; } public 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(); } } public disableRotation() { if (this._rotEnabled) { this._setVisibility(this._all_rEnd, 0); this._rotEnabled = false; } } private _scaleEnabled: boolean = false; public isScalingEnabled(): boolean { return this._scaleEnabled; } public enableScaling() { if (this._hidden) return; if (this._sX == null) { this._createScaleAxes(); this._sCtl.parent = this._ecRoot; } this._clearPrevOverMesh(); if (!this._scaleEnabled) { this._setVisibility(this._all_sEnd, this._visibility); this._scaleEnabled = true; this.disableTranslation(); this.disableRotation(); } } public disableScaling() { if (this._scaleEnabled) { this._setVisibility(this._all_sEnd, 0); this._scaleEnabled