Autodesk Forge is now Autodesk Platform Services

12 Jul 2016

Rotate Components Control for the Viewer

Default blog image

    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.

Screen Shot 2016-07-12 at 16.04.29

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
}
}
}

Related Article