Spaces:
Running
Running
import { | |
CylinderGeometry, | |
CanvasTexture, | |
Color, | |
Euler, | |
Mesh, | |
MeshBasicMaterial, | |
Object3D, | |
OrthographicCamera, | |
Quaternion, | |
Raycaster, | |
Sprite, | |
SpriteMaterial, | |
SRGBColorSpace, | |
Vector2, | |
Vector3, | |
Vector4 | |
} from 'three'; | |
class ViewHelper extends Object3D { | |
constructor( camera, domElement ) { | |
super(); | |
this.isViewHelper = true; | |
this.animating = false; | |
this.center = new Vector3(); | |
const color1 = new Color( '#ff4466' ); | |
const color2 = new Color( '#88ff44' ); | |
const color3 = new Color( '#4488ff' ); | |
const color4 = new Color( '#000000' ); | |
const options = {}; | |
const interactiveObjects = []; | |
const raycaster = new Raycaster(); | |
const mouse = new Vector2(); | |
const dummy = new Object3D(); | |
const orthoCamera = new OrthographicCamera( - 2, 2, 2, - 2, 0, 4 ); | |
orthoCamera.position.set( 0, 0, 2 ); | |
const geometry = new CylinderGeometry( 0.04, 0.04, 0.8, 5 ).rotateZ( - Math.PI / 2 ).translate( 0.4, 0, 0 ); | |
const xAxis = new Mesh( geometry, getAxisMaterial( color1 ) ); | |
const yAxis = new Mesh( geometry, getAxisMaterial( color2 ) ); | |
const zAxis = new Mesh( geometry, getAxisMaterial( color3 ) ); | |
yAxis.rotation.z = Math.PI / 2; | |
zAxis.rotation.y = - Math.PI / 2; | |
this.add( xAxis ); | |
this.add( zAxis ); | |
this.add( yAxis ); | |
const spriteMaterial1 = getSpriteMaterial( color1 ); | |
const spriteMaterial2 = getSpriteMaterial( color2 ); | |
const spriteMaterial3 = getSpriteMaterial( color3 ); | |
const spriteMaterial4 = getSpriteMaterial( color4 ); | |
const posXAxisHelper = new Sprite( spriteMaterial1 ); | |
const posYAxisHelper = new Sprite( spriteMaterial2 ); | |
const posZAxisHelper = new Sprite( spriteMaterial3 ); | |
const negXAxisHelper = new Sprite( spriteMaterial4 ); | |
const negYAxisHelper = new Sprite( spriteMaterial4 ); | |
const negZAxisHelper = new Sprite( spriteMaterial4 ); | |
posXAxisHelper.position.x = 1; | |
posYAxisHelper.position.y = 1; | |
posZAxisHelper.position.z = 1; | |
negXAxisHelper.position.x = - 1; | |
negYAxisHelper.position.y = - 1; | |
negZAxisHelper.position.z = - 1; | |
negXAxisHelper.material.opacity = 0.2; | |
negYAxisHelper.material.opacity = 0.2; | |
negZAxisHelper.material.opacity = 0.2; | |
posXAxisHelper.userData.type = 'posX'; | |
posYAxisHelper.userData.type = 'posY'; | |
posZAxisHelper.userData.type = 'posZ'; | |
negXAxisHelper.userData.type = 'negX'; | |
negYAxisHelper.userData.type = 'negY'; | |
negZAxisHelper.userData.type = 'negZ'; | |
this.add( posXAxisHelper ); | |
this.add( posYAxisHelper ); | |
this.add( posZAxisHelper ); | |
this.add( negXAxisHelper ); | |
this.add( negYAxisHelper ); | |
this.add( negZAxisHelper ); | |
interactiveObjects.push( posXAxisHelper ); | |
interactiveObjects.push( posYAxisHelper ); | |
interactiveObjects.push( posZAxisHelper ); | |
interactiveObjects.push( negXAxisHelper ); | |
interactiveObjects.push( negYAxisHelper ); | |
interactiveObjects.push( negZAxisHelper ); | |
const point = new Vector3(); | |
const dim = 128; | |
const turnRate = 2 * Math.PI; // turn rate in angles per second | |
this.render = function ( renderer ) { | |
this.quaternion.copy( camera.quaternion ).invert(); | |
this.updateMatrixWorld(); | |
point.set( 0, 0, 1 ); | |
point.applyQuaternion( camera.quaternion ); | |
// | |
const x = domElement.offsetWidth - dim; | |
renderer.clearDepth(); | |
renderer.getViewport( viewport ); | |
renderer.setViewport( x, 0, dim, dim ); | |
renderer.render( this, orthoCamera ); | |
renderer.setViewport( viewport.x, viewport.y, viewport.z, viewport.w ); | |
}; | |
const targetPosition = new Vector3(); | |
const targetQuaternion = new Quaternion(); | |
const q1 = new Quaternion(); | |
const q2 = new Quaternion(); | |
const viewport = new Vector4(); | |
let radius = 0; | |
this.handleClick = function ( event ) { | |
if ( this.animating === true ) return false; | |
const rect = domElement.getBoundingClientRect(); | |
const offsetX = rect.left + ( domElement.offsetWidth - dim ); | |
const offsetY = rect.top + ( domElement.offsetHeight - dim ); | |
mouse.x = ( ( event.clientX - offsetX ) / ( rect.right - offsetX ) ) * 2 - 1; | |
mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1; | |
raycaster.setFromCamera( mouse, orthoCamera ); | |
const intersects = raycaster.intersectObjects( interactiveObjects ); | |
if ( intersects.length > 0 ) { | |
const intersection = intersects[ 0 ]; | |
const object = intersection.object; | |
prepareAnimationData( object, this.center ); | |
this.animating = true; | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
this.setLabels = function ( labelX, labelY, labelZ ) { | |
options.labelX = labelX; | |
options.labelY = labelY; | |
options.labelZ = labelZ; | |
updateLabels(); | |
}; | |
this.setLabelStyle = function ( font, color, radius ) { | |
options.font = font; | |
options.color = color; | |
options.radius = radius; | |
updateLabels(); | |
}; | |
this.update = function ( delta ) { | |
const step = delta * turnRate; | |
// animate position by doing a slerp and then scaling the position on the unit sphere | |
q1.rotateTowards( q2, step ); | |
camera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( this.center ); | |
// animate orientation | |
camera.quaternion.rotateTowards( targetQuaternion, step ); | |
if ( q1.angleTo( q2 ) === 0 ) { | |
this.animating = false; | |
} | |
}; | |
this.dispose = function () { | |
geometry.dispose(); | |
xAxis.material.dispose(); | |
yAxis.material.dispose(); | |
zAxis.material.dispose(); | |
posXAxisHelper.material.map.dispose(); | |
posYAxisHelper.material.map.dispose(); | |
posZAxisHelper.material.map.dispose(); | |
negXAxisHelper.material.map.dispose(); | |
negYAxisHelper.material.map.dispose(); | |
negZAxisHelper.material.map.dispose(); | |
posXAxisHelper.material.dispose(); | |
posYAxisHelper.material.dispose(); | |
posZAxisHelper.material.dispose(); | |
negXAxisHelper.material.dispose(); | |
negYAxisHelper.material.dispose(); | |
negZAxisHelper.material.dispose(); | |
}; | |
function prepareAnimationData( object, focusPoint ) { | |
switch ( object.userData.type ) { | |
case 'posX': | |
targetPosition.set( 1, 0, 0 ); | |
targetQuaternion.setFromEuler( new Euler( 0, Math.PI * 0.5, 0 ) ); | |
break; | |
case 'posY': | |
targetPosition.set( 0, 1, 0 ); | |
targetQuaternion.setFromEuler( new Euler( - Math.PI * 0.5, 0, 0 ) ); | |
break; | |
case 'posZ': | |
targetPosition.set( 0, 0, 1 ); | |
targetQuaternion.setFromEuler( new Euler() ); | |
break; | |
case 'negX': | |
targetPosition.set( - 1, 0, 0 ); | |
targetQuaternion.setFromEuler( new Euler( 0, - Math.PI * 0.5, 0 ) ); | |
break; | |
case 'negY': | |
targetPosition.set( 0, - 1, 0 ); | |
targetQuaternion.setFromEuler( new Euler( Math.PI * 0.5, 0, 0 ) ); | |
break; | |
case 'negZ': | |
targetPosition.set( 0, 0, - 1 ); | |
targetQuaternion.setFromEuler( new Euler( 0, Math.PI, 0 ) ); | |
break; | |
default: | |
console.error( 'ViewHelper: Invalid axis.' ); | |
} | |
// | |
radius = camera.position.distanceTo( focusPoint ); | |
targetPosition.multiplyScalar( radius ).add( focusPoint ); | |
dummy.position.copy( focusPoint ); | |
dummy.lookAt( camera.position ); | |
q1.copy( dummy.quaternion ); | |
dummy.lookAt( targetPosition ); | |
q2.copy( dummy.quaternion ); | |
} | |
function getAxisMaterial( color ) { | |
return new MeshBasicMaterial( { color: color, toneMapped: false } ); | |
} | |
function getSpriteMaterial( color, text ) { | |
const { font = '24px Arial', color: labelColor = '#000000', radius = 14 } = options; | |
const canvas = document.createElement( 'canvas' ); | |
canvas.width = 64; | |
canvas.height = 64; | |
const context = canvas.getContext( '2d' ); | |
context.beginPath(); | |
context.arc( 32, 32, radius, 0, 2 * Math.PI ); | |
context.closePath(); | |
context.fillStyle = color.getStyle(); | |
context.fill(); | |
if ( text ) { | |
context.font = font; | |
context.textAlign = 'center'; | |
context.fillStyle = labelColor; | |
context.fillText( text, 32, 41 ); | |
} | |
const texture = new CanvasTexture( canvas ); | |
texture.colorSpace = SRGBColorSpace; | |
return new SpriteMaterial( { map: texture, toneMapped: false } ); | |
} | |
function updateLabels() { | |
posXAxisHelper.material.map.dispose(); | |
posYAxisHelper.material.map.dispose(); | |
posZAxisHelper.material.map.dispose(); | |
posXAxisHelper.material.dispose(); | |
posYAxisHelper.material.dispose(); | |
posZAxisHelper.material.dispose(); | |
posXAxisHelper.material = getSpriteMaterial( color1, options.labelX ); | |
posYAxisHelper.material = getSpriteMaterial( color2, options.labelY ); | |
posZAxisHelper.material = getSpriteMaterial( color3, options.labelZ ); | |
} | |
} | |
} | |
export { ViewHelper }; | |