12 Jul 2016
Rotate Components Control for the Viewer
![Default blog image](/sites/default/files/default_images/Placeholder%20-%20Blog%20images_Lifestyle%2016x9%201920x1080.jpg)
The viewer API provides access to individual components in a model and the programmer has the possibility to transform them at will by applaying matrices, translations are rather straightforward, however rotations can get a bit tricky. There is also no built-in tools directly provided by the API to transform components from the UI.
I worked in the past on a first version of the translate tool and finally got the opportunity to deal with the rotation. If the translate tool is using a transform control provided by three.js API, I wasn't really satisfied by the rotate feature, so I decided to write my own and learn a couple of things at the same time.
Here are the two main highlights of that sample:
1/ The pointerToRaycaster method that converts a 2d screenpoint from the mouse pointer into a three.js Raycaster object that lets you perform object picking selection on screen, this is used to handle user interaction with our rotate gizmo control
1 /////////////////////////////////////////////////////////////////////////// 2 // Creates Raycaster object from mouse or touch pointer 3 // 4 /////////////////////////////////////////////////////////////////////////// 5 pointerToRaycaster (pointer) { 6 7 var pointerVector = new THREE.Vector3() 8 var pointerDir = new THREE.Vector3() 9 var ray = new THREE.Raycaster() 10 11 var rect = this.domElement.getBoundingClientRect() 12 13 var x = ((pointer.clientX - rect.left) / rect.width) * 2 - 1 14 var y = -((pointer.clientY - rect.top) / rect.height) * 2 + 1 15 16 if (this.camera.isPerspective) { 17 18 pointerVector.set(x, y, 0.5) 19 20 pointerVector.unproject(this.camera) 21 22 ray.set(this.camera.position, 23 pointerVector.sub( 24 this.camera.position).normalize()) 25 26 } else { 27 28 pointerVector.set(x, y, -1) 29 30 pointerVector.unproject(this.camera) 31 32 pointerDir.set(0, 0, -1) 33 34 ray.set(pointerVector, 35 pointerDir.transformDirection( 36 this.camera.matrixWorld)) 37 } 38 39 return ray 40 }
This can later be used as follow:
1 onPointerDown (event) { 2 3 var pointer = event.pointers ? event.pointers[ 0 ] : event 4 5 if (pointer.button === 0) { 6 7 var ray = this.pointerToRaycaster(pointer) 8 9 var intersectResults = ray.intersectObjects( 10 this.gizmos, true) 11 12 if (intersectResults.length) { 13 14 this.gizmos.forEach((gizmo) => { 15 16 gizmo.visible = false 17 }) 18 19 this.selectedGizmo = intersectResults[0].object
2/ The rotateFragments method that perform rotation of the selected meshes (fragments) according to the specific input parameters
1 /////////////////////////////////////////////////////////////////////////// 2 // Rotate selected fragIds of specific model around based on specified 3 // axis, angle and center 4 // 5 /////////////////////////////////////////////////////////////////////////// 6 rotateFragments (model, fragIdsArray, axis, angle, center) { 7 8 var quaternion = new THREE.Quaternion() 9 10 quaternion.setFromAxisAngle(axis, angle) 11 12 fragIdsArray.forEach((fragId, idx) => { 13 14 var fragProxy = this.viewer.impl.getFragmentProxy( 15 model, fragId) 16 17 fragProxy.getAnimTransform() 18 19 var position = new THREE.Vector3( 20 fragProxy.position.x - center.x, 21 fragProxy.position.y - center.y, 22 fragProxy.position.z - center.z) 23 24 position.applyQuaternion(quaternion) 25 26 position.add(center) 27 28 fragProxy.position = position 29 30 fragProxy.quaternion.multiplyQuaternions( 31 quaternion, fragProxy.quaternion) 32 33 if (idx === 0) { 34 35 var euler = new THREE.Euler() 36 37 euler.setFromQuaternion( 38 fragProxy.quaternion, 0) 39 40 this.emit('transform.rotate', { 41 rotation: euler, 42 model 43 }) 44 } 45 46 fragProxy.updateAnimTransform() 47 }) 48 }
The rest of the code is pretty much self-explanatory so you can refer to the sample below. The full source code of the extension is part of my extensions library at Viewing.Extension.Transform. Like most of my latest samples, the extension requires a build step with webpack, sorry for people who prefer to deal with straigh ES5 JavaScript code to include in their html but we are in 2016 now :)
Here is a live demo where you can play with those transform tools.
import EventsEmitter from 'EventsEmitter' | |
export default class RotateTool extends EventsEmitter { | |
///////////////////////////////////////////////////////////////// | |
// Class constructor | |
// | |
///////////////////////////////////////////////////////////////// | |
constructor (viewer) { | |
super() | |
this.keys = {} | |
this.active = false | |
this.viewer = viewer | |
this.fullTransform = false | |
this.viewer.toolController.registerTool(this) | |
this.onAggregateSelectionChangedHandler = (e) => { | |
this.onAggregateSelectionChanged(e) | |
} | |
} | |
///////////////////////////////////////////////////////////////// | |
// Enable tool | |
// | |
///////////////////////////////////////////////////////////////// | |
enable (enable) { | |
var name = this.getName() | |
if (enable) { | |
this.viewer.toolController.activateTool(name) | |
} else { | |
this.viewer.toolController.deactivateTool(name) | |
} | |
} | |
///////////////////////////////////////////////////////////////// | |
// | |
// | |
///////////////////////////////////////////////////////////////// | |
getNames () { | |
return ['Viewing.Rotate.Tool'] | |
} | |
///////////////////////////////////////////////////////////////// | |
// | |
// | |
///////////////////////////////////////////////////////////////// | |
getName () { | |
return 'Viewing.Rotate.Tool' | |
} | |
/////////////////////////////////////////////////////////////////// | |
// activate tool | |
// | |
/////////////////////////////////////////////////////////////////// | |
activate () { | |
if (!this.active) { | |
this.active = true | |
this.viewer.addEventListener( | |
Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT, | |
this.onAggregateSelectionChangedHandler) | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// deactivate tool | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
deactivate () { | |
if (this.active) { | |
this.active = false | |
if (this.rotateControl) { | |
this.rotateControl.remove() | |
this.rotateControl = null | |
} | |
this.viewer.removeEventListener( | |
Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT, | |
this.onAggregateSelectionChangedHandler) | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Component Selection Handler | |
// (use Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT instead of | |
// Autodesk.Viewing.SELECTION_CHANGED_EVENT - deprecated ) | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
onAggregateSelectionChanged (event) { | |
if (this.rotateControl && this.rotateControl.engaged) { | |
this.rotateControl.engaged = false | |
this.viewer.select(this.selection.dbIdArray) | |
return | |
} | |
if (event.selections && event.selections.length) { | |
var selection = event.selections[ 0 ] | |
this.selection = selection | |
this.emit('transform.modelSelected', | |
this.selection) | |
if (this.fullTransform) { | |
this.selection.fragIdsArray = [] | |
var fragCount = selection.model.getFragmentList(). | |
fragments.fragId2dbId.length | |
for (var fragId = 0; fragId < fragCount; ++fragId) { | |
this.selection.fragIdsArray.push(fragId) | |
} | |
this.selection.dbIdArray = [] | |
var instanceTree = selection.model.getData().instanceTree | |
var rootId = instanceTree.getRootId() | |
this.selection.dbIdArray.push(rootId) | |
} | |
this.drawControl() | |
this.viewer.fitToView( | |
this.selection.dbIdArray) | |
} else { | |
this.clearSelection() | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Selection cleared | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
clearSelection () { | |
this.selection = null | |
if (this.rotateControl) { | |
this.rotateControl.remove() | |
this.rotateControl = null | |
this.viewer.impl.sceneUpdated(true) | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Draw rotate control | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
drawControl () { | |
var bBox = this.geWorldBoundingBox( | |
this.selection.fragIdsArray, | |
this.selection.model.getFragmentList()) | |
this.center = new THREE.Vector3( | |
(bBox.min.x + bBox.max.x) / 2, | |
(bBox.min.y + bBox.max.y) / 2, | |
(bBox.min.z + bBox.max.z) / 2) | |
var size = Math.max( | |
bBox.max.x - bBox.min.x, | |
bBox.max.y - bBox.min.y, | |
bBox.max.z - bBox.min.z) * 0.8 | |
if (this.rotateControl) { | |
this.rotateControl.remove() | |
} | |
this.rotateControl = new RotateControl( | |
this.viewer, this.center, size) | |
this.rotateControl.on('transform.rotate', (data) => { | |
this.rotateFragments( | |
this.selection.model, | |
this.selection.fragIdsArray, | |
data.axis, | |
data.angle, | |
this.center) | |
this.viewer.impl.sceneUpdated(true) | |
}) | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
handleButtonDown (event, button) { | |
if (this.rotateControl) { | |
if (this.rotateControl.onPointerDown(event)) { | |
return true | |
} | |
} | |
if (button === 0 && this.keys.Control) { | |
this.isDragging = true | |
this.mousePos = { | |
x: event.clientX, | |
y: event.clientY | |
} | |
return true | |
} | |
return false | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
handleButtonUp (event, button) { | |
if (this.rotateControl) { | |
this.rotateControl.onPointerUp(event) | |
} | |
if (button === 0) { | |
this.isDragging = false | |
} | |
return false | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
handleMouseMove (event) { | |
if (this.rotateControl) { | |
this.rotateControl.onPointerHover(event) | |
} | |
if (this.isDragging) { | |
if (this.selection) { | |
var offset = { | |
x: this.mousePos.x - event.clientX, | |
y: event.clientY - this.mousePos.y | |
} | |
this.mousePos = { | |
x: event.clientX, | |
y: event.clientY | |
} | |
var angle = Math.sqrt( | |
offset.x * offset.x + | |
offset.y * offset.y) | |
var sidewaysDirection = new THREE.Vector3() | |
var moveDirection = new THREE.Vector3() | |
var eyeDirection = new THREE.Vector3() | |
var upDirection = new THREE.Vector3() | |
var camera = this.viewer.getCamera() | |
var axis = new THREE.Vector3() | |
var eye = new THREE.Vector3() | |
eye.copy(camera.position).sub(camera.target) | |
eyeDirection.copy(eye).normalize() | |
upDirection.copy(camera.up).normalize() | |
sidewaysDirection.crossVectors( | |
upDirection, eyeDirection).normalize() | |
upDirection.setLength(offset.y) | |
sidewaysDirection.setLength(offset.x) | |
moveDirection.copy( | |
upDirection.add( | |
sidewaysDirection)) | |
axis.crossVectors(moveDirection, eye).normalize() | |
this.rotateFragments( | |
this.selection.model, | |
this.selection.fragIdsArray, | |
axis, angle * Math.PI / 180, | |
this.center) | |
this.viewer.impl.sceneUpdated(true) | |
} | |
return true | |
} | |
return false | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
handleKeyDown (event, keyCode) { | |
this.keys[event.key] = true | |
return false | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
handleKeyUp (event, keyCode) { | |
this.keys[event.key] = false | |
return false | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Rotate selected fragments | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
rotateFragments (model, fragIdsArray, axis, angle, center) { | |
var quaternion = new THREE.Quaternion() | |
quaternion.setFromAxisAngle(axis, angle) | |
fragIdsArray.forEach((fragId, idx) => { | |
var fragProxy = this.viewer.impl.getFragmentProxy( | |
model, fragId) | |
fragProxy.getAnimTransform() | |
var position = new THREE.Vector3( | |
fragProxy.position.x - center.x, | |
fragProxy.position.y - center.y, | |
fragProxy.position.z - center.z) | |
position.applyQuaternion(quaternion) | |
position.add(center) | |
fragProxy.position = position | |
fragProxy.quaternion.multiplyQuaternions( | |
quaternion, fragProxy.quaternion) | |
if (idx === 0) { | |
var euler = new THREE.Euler() | |
euler.setFromQuaternion( | |
fragProxy.quaternion, 0) | |
this.emit('transform.rotate', { | |
rotation: euler, | |
model | |
}) | |
} | |
fragProxy.updateAnimTransform() | |
}) | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// returns bounding box as it appears in the viewer | |
// (transformations could be applied) | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
geWorldBoundingBox (fragIds, fragList) { | |
var fragbBox = new THREE.Box3() | |
var nodebBox = new THREE.Box3() | |
fragIds.forEach((fragId) => { | |
fragList.getWorldBounds(fragId, fragbBox) | |
nodebBox.union(fragbBox) | |
}) | |
return nodebBox | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// RotateControl Class | |
// | |
/////////////////////////////////////////////////////////////////////////////// | |
class RotateControl extends EventsEmitter { | |
constructor (viewer, center, size) { | |
super() | |
this.engaged = false | |
this.overlayScene = 'rotateControlScene' | |
this.domElement = viewer.impl.canvas | |
this.camera = viewer.impl.camera | |
this.viewer = viewer | |
this.center = center | |
this.size = size | |
this.gizmos = [] | |
this.viewer.impl.createOverlayScene( | |
this.overlayScene) | |
this.createAxis( | |
center, new THREE.Vector3(1, 0, 0), | |
size * 0.85, 0xFF0000) | |
this.createAxis( | |
center, new THREE.Vector3(0, 1, 0), | |
size * 0.85, 0x00FF00) | |
this.createAxis( | |
center, new THREE.Vector3(0, 0, 1), | |
size * 0.85, 0x0000FF) | |
// World UP = Y | |
if (this.camera.worldup.y) { | |
this.gizmos.push(this.createGizmo( | |
center, | |
new THREE.Euler(0, Math.PI / 2, 0), | |
size * 0.0045, | |
size * 0.8, 0xFF0000, | |
Math.PI, | |
new THREE.Vector3(1, 0, 0))) | |
this.gizmos.push(this.createGizmo( | |
center, | |
new THREE.Euler(Math.PI / 2, 0, 0), | |
size * 0.0045, | |
size * 0.8, 0x00FF00, | |
2 * Math.PI, | |
new THREE.Vector3(0, 1, 0))) | |
this.gizmos.push(this.createGizmo( | |
center, | |
new THREE.Euler(0, 0, 0), | |
size * 0.0045, | |
size * 0.8, 0x0000FF, | |
Math.PI, | |
new THREE.Vector3(0, 0, 1))) | |
} else { | |
// World UP = Z | |
this.gizmos.push(this.createGizmo( | |
center, | |
new THREE.Euler(Math.PI / 2, Math.PI / 2, 0), | |
size * 0.0045, | |
size * 0.8, 0xFF0000, | |
Math.PI, | |
new THREE.Vector3(1, 0, 0))) | |
this.gizmos.push(this.createGizmo( | |
center, | |
new THREE.Euler(Math.PI / 2, 0, 0), | |
size * 0.0045, | |
size * 0.8, 0x00FF00, | |
Math.PI, | |
new THREE.Vector3(0, 1, 0))) | |
this.gizmos.push(this.createGizmo( | |
center, | |
new THREE.Euler(0, 0, 0), | |
size * 0.0045, | |
size * 0.8, 0x0000FF, | |
2 * Math.PI, | |
new THREE.Vector3(0, 0, 1))) | |
} | |
this.picker = this.createSphere( | |
size * 0.02) | |
var material = new THREE.LineBasicMaterial({ | |
color: 0xFFFF00, | |
linewidth: 1, | |
depthTest: false, | |
depthWrite: false, | |
transparent: true | |
}) | |
this.angleLine = | |
this.createLine( | |
this.center, | |
this.center, | |
material) | |
viewer.impl.sceneUpdated(true) | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Draw a line | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
createLine (start, end, material) { | |
var geometry = new THREE.Geometry() | |
geometry.vertices.push(new THREE.Vector3( | |
start.x, start.y, start.z)) | |
geometry.vertices.push(new THREE.Vector3( | |
end.x, end.y, end.z)) | |
var line = new THREE.Line(geometry, material) | |
this.viewer.impl.addOverlay( | |
this.overlayScene, line) | |
return line | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Draw a cone | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
createCone (start, dir, length, material) { | |
dir.normalize() | |
var end = { | |
x: start.x + dir.x * length, | |
y: start.y + dir.y * length, | |
z: start.z + dir.z * length | |
} | |
var orientation = new THREE.Matrix4() | |
orientation.lookAt( | |
start, | |
end, | |
new THREE.Object3D().up) | |
var matrix = new THREE.Matrix4() | |
matrix.set( | |
1, 0, 0, 0, | |
0, 0, 1, 0, | |
0, -1, 0, 0, | |
0, 0, 0, 1) | |
orientation.multiply(matrix) | |
var geometry = new THREE.CylinderGeometry( | |
0, length * 0.2, length, 128, 1) | |
var cone = new THREE.Mesh(geometry, material) | |
cone.applyMatrix(orientation) | |
cone.position.x = start.x + dir.x * length / 2 | |
cone.position.y = start.y + dir.y * length / 2 | |
cone.position.z = start.z + dir.z * length / 2 | |
this.viewer.impl.addOverlay( | |
this.overlayScene, cone) | |
return cone | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Draw one axis | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
createAxis (start, dir, size, color) { | |
var end = { | |
x: start.x + dir.x * size, | |
y: start.y + dir.y * size, | |
z: start.z + dir.z * size | |
} | |
var material = new THREE.LineBasicMaterial({ | |
color: color, | |
linewidth: 3, | |
depthTest: false, | |
depthWrite: false, | |
transparent: true | |
}) | |
this.createLine( | |
start, end, material) | |
this.createCone( | |
end, dir, size * 0.1, material) | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Draw a rotate gizmo | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
createGizmo (center, euler, size, radius, color, range, axis) { | |
var material = new GizmoMaterial({ | |
color: color | |
}) | |
var subMaterial = new GizmoMaterial({ | |
color: color | |
}) | |
var torusGizmo = new THREE.Mesh( | |
new THREE.TorusGeometry( | |
radius, size, 64, 64, range), | |
material) | |
var subTorus = new THREE.Mesh( | |
new THREE.TorusGeometry( | |
radius, size, 64, 64, 2 * Math.PI), | |
subMaterial) | |
subTorus.material.highlight(true) | |
var transform = new THREE.Matrix4() | |
var q = new THREE.Quaternion() | |
q.setFromEuler(euler) | |
var s = new THREE.Vector3(1, 1, 1) | |
transform.compose(center, q, s) | |
torusGizmo.applyMatrix(transform) | |
subTorus.applyMatrix(transform) | |
var plane = this.createBox( | |
this.size * 100, | |
this.size * 100, | |
0.01) | |
plane.applyMatrix(transform) | |
subTorus.visible = false | |
this.viewer.impl.addOverlay( | |
this.overlayScene, torusGizmo) | |
this.viewer.impl.addOverlay( | |
this.overlayScene, subTorus) | |
torusGizmo.subGizmo = subTorus | |
torusGizmo.plane = plane | |
torusGizmo.axis = axis | |
return torusGizmo | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Draw a box | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
createBox (w, h, d) { | |
var material = new GizmoMaterial({ | |
color: 0x000000 | |
}) | |
var geometry = new THREE.BoxGeometry(w, h, d) | |
var box = new THREE.Mesh( | |
geometry, material) | |
box.visible = false | |
this.viewer.impl.addOverlay( | |
this.overlayScene, box) | |
return box | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Draw a sphere | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
createSphere (radius) { | |
var material = new GizmoMaterial({ | |
color: 0xFFFF00 | |
}) | |
var geometry = new THREE.SphereGeometry( | |
radius, 32, 32) | |
var sphere = new THREE.Mesh( | |
geometry, material) | |
sphere.visible = false | |
this.viewer.impl.addOverlay( | |
this.overlayScene, sphere) | |
return sphere | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Creates Raycatser object from the pointer | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
pointerToRaycaster (pointer) { | |
var pointerVector = new THREE.Vector3() | |
var pointerDir = new THREE.Vector3() | |
var ray = new THREE.Raycaster() | |
var rect = this.domElement.getBoundingClientRect() | |
var x = ((pointer.clientX - rect.left) / rect.width) * 2 - 1 | |
var y = -((pointer.clientY - rect.top) / rect.height) * 2 + 1 | |
if (this.camera.isPerspective) { | |
pointerVector.set(x, y, 0.5) | |
pointerVector.unproject(this.camera) | |
ray.set(this.camera.position, | |
pointerVector.sub( | |
this.camera.position).normalize()) | |
} else { | |
pointerVector.set(x, y, -1) | |
pointerVector.unproject(this.camera) | |
pointerDir.set(0, 0, -1) | |
ray.set(pointerVector, | |
pointerDir.transformDirection( | |
this.camera.matrixWorld)) | |
} | |
return ray | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
onPointerDown (event) { | |
var pointer = event.pointers ? event.pointers[ 0 ] : event | |
if (pointer.button === 0) { | |
var ray = this.pointerToRaycaster(pointer) | |
var intersectResults = ray.intersectObjects( | |
this.gizmos, true) | |
if (intersectResults.length) { | |
this.gizmos.forEach((gizmo) => { | |
gizmo.visible = false | |
}) | |
this.selectedGizmo = intersectResults[0].object | |
this.selectedGizmo.subGizmo.visible = true | |
this.picker.position.copy( | |
intersectResults[0].point) | |
this.angleLine.geometry.vertices[1].copy( | |
intersectResults[0].point) | |
this.lastDir = intersectResults[0].point.sub( | |
this.center).normalize() | |
this.angleLine.geometry.verticesNeedUpdate = true | |
this.angleLine.visible = true | |
this.picker.visible = true | |
} else { | |
this.picker.visible = false | |
} | |
this.engaged = this.picker.visible | |
this.viewer.impl.sceneUpdated(true) | |
} | |
return this.picker.visible | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
onPointerHover (event) { | |
var pointer = event.pointers ? event.pointers[ 0 ] : event | |
if (this.engaged) { | |
var ray = this.pointerToRaycaster(pointer) | |
var intersectResults = ray.intersectObjects( | |
[this.selectedGizmo.plane], true) | |
if (intersectResults.length) { | |
var intersectPoint = intersectResults[0].point | |
var dir = intersectPoint.sub( | |
this.center).normalize() | |
var cross = new THREE.Vector3() | |
cross.crossVectors(this.lastDir, dir) | |
var sign = Math.sign( | |
cross.dot(this.selectedGizmo.axis)) | |
this.emit('transform.rotate', { | |
angle: sign * dir.angleTo(this.lastDir), | |
axis: this.selectedGizmo.axis | |
}) | |
this.lastDir = dir | |
var pickerPoint = new THREE.Vector3( | |
this.center.x + dir.x * this.size * 0.8, | |
this.center.y + dir.y * this.size * 0.8, | |
this.center.z + dir.z * this.size * 0.8) | |
this.picker.position.copy( | |
pickerPoint) | |
this.angleLine.geometry.vertices[1].copy( | |
pickerPoint) | |
} | |
this.angleLine.visible = true | |
this.angleLine.geometry.verticesNeedUpdate = true | |
} else { | |
this.angleLine.visible = false | |
var ray = this.pointerToRaycaster(pointer) | |
var intersectResults = ray.intersectObjects( | |
this.gizmos, true) | |
if (intersectResults.length) { | |
this.picker.position.set( | |
intersectResults[ 0 ].point.x, | |
intersectResults[ 0 ].point.y, | |
intersectResults[ 0 ].point.z) | |
this.picker.visible = true | |
} else { | |
this.picker.visible = false | |
} | |
} | |
this.viewer.impl.sceneUpdated(true) | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
onPointerUp (event) { | |
this.angleLine.visible = false | |
this.picker.visible = false | |
this.gizmos.forEach((gizmo) => { | |
gizmo.visible = true | |
gizmo.subGizmo.visible = false | |
}) | |
this.viewer.impl.sceneUpdated(true) | |
setTimeout(() => { | |
this.engaged = false | |
}, 100) | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
normalize(screenPoint) { | |
var viewport = this.viewer.navigation.getScreenViewport() | |
var n = { | |
x: (screenPoint.x - viewport.left) / viewport.width, | |
y: (screenPoint.y - viewport.top) / viewport.height | |
} | |
return n | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
projectOntoPlane (worldPoint, normal) { | |
var dist = normal.dot(worldPoint) | |
return new THREE.Vector3( | |
worldPoint.x - dist * normal.x, | |
worldPoint.y - dist * normal.y, | |
worldPoint.z - dist * normal.z) | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
remove () { | |
this.viewer.impl.removeOverlayScene( | |
this.overlayScene) | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// Highlightable Gizmo Material | |
// | |
/////////////////////////////////////////////////////////////////////////////// | |
class GizmoMaterial extends THREE.MeshBasicMaterial { | |
constructor (parameters) { | |
super() | |
this.setValues(parameters) | |
this.colorInit = this.color.clone() | |
this.opacityInit = this.opacity | |
this.side = THREE.FrontSide | |
this.depthWrite = false | |
this.transparent = true | |
this.depthTest = false | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// | |
// | |
/////////////////////////////////////////////////////////////////////////// | |
highlight (highlighted) { | |
if (highlighted) { | |
this.color.setRGB(1, 230 / 255, 3 / 255) | |
this.opacity = 1 | |
} else { | |
this.color.copy(this.colorInit) | |
this.opacity = this.opacityInit | |
} | |
} | |
} |