最新代码

This commit is contained in:
Teo
2025-07-29 11:22:30 +08:00
parent d503e64098
commit b01d143ea6
1490 changed files with 680232 additions and 28 deletions

View File

@ -0,0 +1,130 @@
import {
Mesh,
ShaderMaterial,
SphereGeometry
} from 'three';
class LightProbeHelper extends Mesh {
constructor( lightProbe, size ) {
const material = new ShaderMaterial( {
type: 'LightProbeHelperMaterial',
uniforms: {
sh: { value: lightProbe.sh.coefficients }, // by reference
intensity: { value: lightProbe.intensity }
},
vertexShader: [
'varying vec3 vNormal;',
'void main() {',
' vNormal = normalize( normalMatrix * normal );',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}',
].join( '\n' ),
fragmentShader: [
'#define RECIPROCAL_PI 0.318309886',
'vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {',
' // matrix is assumed to be orthogonal',
' return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );',
'}',
'// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf',
'vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {',
' // normal is assumed to have unit length',
' float x = normal.x, y = normal.y, z = normal.z;',
' // band 0',
' vec3 result = shCoefficients[ 0 ] * 0.886227;',
' // band 1',
' result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;',
' result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;',
' result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;',
' // band 2',
' result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;',
' result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;',
' result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );',
' result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;',
' result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );',
' return result;',
'}',
'uniform vec3 sh[ 9 ]; // sh coefficients',
'uniform float intensity; // light probe intensity',
'varying vec3 vNormal;',
'void main() {',
' vec3 normal = normalize( vNormal );',
' vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );',
' vec3 irradiance = shGetIrradianceAt( worldNormal, sh );',
' vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;',
' gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) );',
'}'
].join( '\n' )
} );
const geometry = new SphereGeometry( 1, 32, 16 );
super( geometry, material );
this.lightProbe = lightProbe;
this.size = size;
this.type = 'LightProbeHelper';
this.onBeforeRender();
}
dispose() {
this.geometry.dispose();
this.material.dispose();
}
onBeforeRender() {
this.position.copy( this.lightProbe.position );
this.scale.set( 1, 1, 1 ).multiplyScalar( this.size );
this.material.uniforms.intensity.value = this.lightProbe.intensity;
}
}
export { LightProbeHelper };

View File

@ -0,0 +1,73 @@
import {
LineSegments,
BufferGeometry,
Float32BufferAttribute,
LineBasicMaterial
} from 'three';
class OctreeHelper extends LineSegments {
constructor( octree, color = 0xffff00 ) {
super( new BufferGeometry(), new LineBasicMaterial( { color: color, toneMapped: false } ) );
this.octree = octree;
this.color = color;
this.type = 'OctreeHelper';
this.update();
}
update() {
const vertices = [];
function traverse( tree ) {
for ( let i = 0; i < tree.length; i ++ ) {
const min = tree[ i ].box.min;
const max = tree[ i ].box.max;
vertices.push( max.x, max.y, max.z ); vertices.push( min.x, max.y, max.z ); // 0, 1
vertices.push( min.x, max.y, max.z ); vertices.push( min.x, min.y, max.z ); // 1, 2
vertices.push( min.x, min.y, max.z ); vertices.push( max.x, min.y, max.z ); // 2, 3
vertices.push( max.x, min.y, max.z ); vertices.push( max.x, max.y, max.z ); // 3, 0
vertices.push( max.x, max.y, min.z ); vertices.push( min.x, max.y, min.z ); // 4, 5
vertices.push( min.x, max.y, min.z ); vertices.push( min.x, min.y, min.z ); // 5, 6
vertices.push( min.x, min.y, min.z ); vertices.push( max.x, min.y, min.z ); // 6, 7
vertices.push( max.x, min.y, min.z ); vertices.push( max.x, max.y, min.z ); // 7, 4
vertices.push( max.x, max.y, max.z ); vertices.push( max.x, max.y, min.z ); // 0, 4
vertices.push( min.x, max.y, max.z ); vertices.push( min.x, max.y, min.z ); // 1, 5
vertices.push( min.x, min.y, max.z ); vertices.push( min.x, min.y, min.z ); // 2, 6
vertices.push( max.x, min.y, max.z ); vertices.push( max.x, min.y, min.z ); // 3, 7
traverse( tree[ i ].subTrees );
}
}
traverse( this.octree.subTrees );
this.geometry.dispose();
this.geometry = new BufferGeometry();
this.geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
}
dispose() {
this.geometry.dispose();
this.material.dispose();
}
}
export { OctreeHelper };

View File

@ -0,0 +1,109 @@
import {
BufferGeometry,
BufferAttribute,
LineBasicMaterial,
Line,
MathUtils
} from 'three';
class PositionalAudioHelper extends Line {
constructor( audio, range = 1, divisionsInnerAngle = 16, divisionsOuterAngle = 2 ) {
const geometry = new BufferGeometry();
const divisions = divisionsInnerAngle + divisionsOuterAngle * 2;
const positions = new Float32Array( ( divisions * 3 + 3 ) * 3 );
geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
const materialInnerAngle = new LineBasicMaterial( { color: 0x00ff00 } );
const materialOuterAngle = new LineBasicMaterial( { color: 0xffff00 } );
super( geometry, [ materialOuterAngle, materialInnerAngle ] );
this.audio = audio;
this.range = range;
this.divisionsInnerAngle = divisionsInnerAngle;
this.divisionsOuterAngle = divisionsOuterAngle;
this.type = 'PositionalAudioHelper';
this.update();
}
update() {
const audio = this.audio;
const range = this.range;
const divisionsInnerAngle = this.divisionsInnerAngle;
const divisionsOuterAngle = this.divisionsOuterAngle;
const coneInnerAngle = MathUtils.degToRad( audio.panner.coneInnerAngle );
const coneOuterAngle = MathUtils.degToRad( audio.panner.coneOuterAngle );
const halfConeInnerAngle = coneInnerAngle / 2;
const halfConeOuterAngle = coneOuterAngle / 2;
let start = 0;
let count = 0;
let i;
let stride;
const geometry = this.geometry;
const positionAttribute = geometry.attributes.position;
geometry.clearGroups();
//
function generateSegment( from, to, divisions, materialIndex ) {
const step = ( to - from ) / divisions;
positionAttribute.setXYZ( start, 0, 0, 0 );
count ++;
for ( i = from; i < to; i += step ) {
stride = start + count;
positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range );
positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range );
positionAttribute.setXYZ( stride + 2, 0, 0, 0 );
count += 3;
}
geometry.addGroup( start, count, materialIndex );
start += count;
count = 0;
}
//
generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 );
generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 );
generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 );
//
positionAttribute.needsUpdate = true;
if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false;
}
dispose() {
this.geometry.dispose();
this.material[ 0 ].dispose();
this.material[ 1 ].dispose();
}
}
export { PositionalAudioHelper };

View File

@ -0,0 +1,85 @@
import {
BackSide,
BufferGeometry,
Float32BufferAttribute,
Line,
LineBasicMaterial,
Mesh,
MeshBasicMaterial
} from 'three';
/**
* This helper must be added as a child of the light
*/
class RectAreaLightHelper extends Line {
constructor( light, color ) {
const positions = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ];
const geometry = new BufferGeometry();
geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
geometry.computeBoundingSphere();
const material = new LineBasicMaterial( { fog: false } );
super( geometry, material );
this.light = light;
this.color = color; // optional hardwired color for the helper
this.type = 'RectAreaLightHelper';
//
const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ];
const geometry2 = new BufferGeometry();
geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) );
geometry2.computeBoundingSphere();
this.add( new Mesh( geometry2, new MeshBasicMaterial( { side: BackSide, fog: false } ) ) );
}
updateMatrixWorld() {
this.scale.set( 0.5 * this.light.width, 0.5 * this.light.height, 1 );
if ( this.color !== undefined ) {
this.material.color.set( this.color );
this.children[ 0 ].material.color.set( this.color );
} else {
this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
// prevent hue shift
const c = this.material.color;
const max = Math.max( c.r, c.g, c.b );
if ( max > 1 ) c.multiplyScalar( 1 / max );
this.children[ 0 ].material.color.copy( this.material.color );
}
// ignore world scale on light
this.matrixWorld.extractRotation( this.light.matrixWorld ).scale( this.scale ).copyPosition( this.light.matrixWorld );
this.children[ 0 ].matrixWorld.copy( this.matrixWorld );
}
dispose() {
this.geometry.dispose();
this.material.dispose();
this.children[ 0 ].geometry.dispose();
this.children[ 0 ].material.dispose();
}
}
export { RectAreaLightHelper };

View File

@ -0,0 +1,237 @@
import {
BoxGeometry,
BufferAttribute,
DoubleSide,
Mesh,
PlaneGeometry,
ShaderMaterial,
Vector3,
} from 'three';
import { mergeGeometries } from '../utils/BufferGeometryUtils.js';
class TextureHelper extends Mesh {
constructor( texture, width = 1, height = 1, depth = 1 ) {
const material = new ShaderMaterial( {
type: 'TextureHelperMaterial',
side: DoubleSide,
transparent: true,
uniforms: {
map: { value: texture },
alpha: { value: getAlpha( texture ) },
},
vertexShader: [
'attribute vec3 uvw;',
'varying vec3 vUvw;',
'void main() {',
' vUvw = uvw;',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}',
].join( '\n' ),
fragmentShader: [
'precision highp float;',
'precision highp sampler2DArray;',
'precision highp sampler3D;',
'uniform {samplerType} map;',
'uniform float alpha;',
'varying vec3 vUvw;',
'vec4 textureHelper( in sampler2D map ) { return texture( map, vUvw.xy ); }',
'vec4 textureHelper( in sampler2DArray map ) { return texture( map, vUvw ); }',
'vec4 textureHelper( in sampler3D map ) { return texture( map, vUvw ); }',
'vec4 textureHelper( in samplerCube map ) { return texture( map, vUvw ); }',
'void main() {',
' gl_FragColor = linearToOutputTexel( vec4( textureHelper( map ).xyz, alpha ) );',
'}'
].join( '\n' ).replace( '{samplerType}', getSamplerType( texture ) )
} );
const geometry = texture.isCubeTexture
? createCubeGeometry( width, height, depth )
: createSliceGeometry( texture, width, height, depth );
super( geometry, material );
this.texture = texture;
this.type = 'TextureHelper';
}
dispose() {
this.geometry.dispose();
this.material.dispose();
}
}
function getSamplerType( texture ) {
if ( texture.isCubeTexture ) {
return 'samplerCube';
} else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {
return 'sampler2DArray';
} else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) {
return 'sampler3D';
} else {
return 'sampler2D';
}
}
function getImageCount( texture ) {
if ( texture.isCubeTexture ) {
return 6;
} else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {
return texture.image.depth;
} else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) {
return texture.image.depth;
} else {
return 1;
}
}
function getAlpha( texture ) {
if ( texture.isCubeTexture ) {
return 1;
} else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {
return Math.max( 1 / texture.image.depth, 0.25 );
} else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) {
return Math.max( 1 / texture.image.depth, 0.25 );
} else {
return 1;
}
}
function createCubeGeometry( width, height, depth ) {
const geometry = new BoxGeometry( width, height, depth );
const position = geometry.attributes.position;
const uv = geometry.attributes.uv;
const uvw = new BufferAttribute( new Float32Array( uv.count * 3 ), 3 );
const _direction = new Vector3();
for ( let j = 0, jl = uv.count; j < jl; ++ j ) {
_direction.fromBufferAttribute( position, j ).normalize();
const u = _direction.x;
const v = _direction.y;
const w = _direction.z;
uvw.setXYZ( j, u, v, w );
}
geometry.deleteAttribute( 'uv' );
geometry.setAttribute( 'uvw', uvw );
return geometry;
}
function createSliceGeometry( texture, width, height, depth ) {
const sliceCount = getImageCount( texture );
const geometries = [];
for ( let i = 0; i < sliceCount; ++ i ) {
const geometry = new PlaneGeometry( width, height );
if ( sliceCount > 1 ) {
geometry.translate( 0, 0, depth * ( i / ( sliceCount - 1 ) - 0.5 ) );
}
const uv = geometry.attributes.uv;
const uvw = new BufferAttribute( new Float32Array( uv.count * 3 ), 3 );
for ( let j = 0, jl = uv.count; j < jl; ++ j ) {
const u = uv.getX( j );
const v = texture.flipY ? uv.getY( j ) : 1 - uv.getY( j );
const w = sliceCount === 1
? 1
: texture.isDataArrayTexture || texture.isCompressedArrayTexture
? i
: i / ( sliceCount - 1 );
uvw.setXYZ( j, u, v, w );
}
geometry.deleteAttribute( 'uv' );
geometry.setAttribute( 'uvw', uvw );
geometries.push( geometry );
}
return mergeGeometries( geometries );
}
export { TextureHelper };

View File

@ -0,0 +1,96 @@
import {
BufferGeometry,
Float32BufferAttribute,
LineSegments,
LineBasicMaterial,
Matrix3,
Vector3
} from 'three';
const _v1 = new Vector3();
const _v2 = new Vector3();
const _normalMatrix = new Matrix3();
class VertexNormalsHelper extends LineSegments {
constructor( object, size = 1, color = 0xff0000 ) {
const geometry = new BufferGeometry();
const nNormals = object.geometry.attributes.normal.count;
const positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 );
geometry.setAttribute( 'position', positions );
super( geometry, new LineBasicMaterial( { color, toneMapped: false } ) );
this.object = object;
this.size = size;
this.type = 'VertexNormalsHelper';
//
this.matrixAutoUpdate = false;
this.update();
}
update() {
this.object.updateMatrixWorld( true );
_normalMatrix.getNormalMatrix( this.object.matrixWorld );
const matrixWorld = this.object.matrixWorld;
const position = this.geometry.attributes.position;
//
const objGeometry = this.object.geometry;
if ( objGeometry ) {
const objPos = objGeometry.attributes.position;
const objNorm = objGeometry.attributes.normal;
let idx = 0;
// for simplicity, ignore index and drawcalls, and render every normal
for ( let j = 0, jl = objPos.count; j < jl; j ++ ) {
_v1.fromBufferAttribute( objPos, j ).applyMatrix4( matrixWorld );
_v2.fromBufferAttribute( objNorm, j );
_v2.applyMatrix3( _normalMatrix ).normalize().multiplyScalar( this.size ).add( _v1 );
position.setXYZ( idx, _v1.x, _v1.y, _v1.z );
idx = idx + 1;
position.setXYZ( idx, _v2.x, _v2.y, _v2.z );
idx = idx + 1;
}
}
position.needsUpdate = true;
}
dispose() {
this.geometry.dispose();
this.material.dispose();
}
}
export { VertexNormalsHelper };

View File

@ -0,0 +1,88 @@
import {
BufferGeometry,
Float32BufferAttribute,
LineSegments,
LineBasicMaterial,
Vector3
} from 'three';
const _v1 = new Vector3();
const _v2 = new Vector3();
class VertexTangentsHelper extends LineSegments {
constructor( object, size = 1, color = 0x00ffff ) {
const geometry = new BufferGeometry();
const nTangents = object.geometry.attributes.tangent.count;
const positions = new Float32BufferAttribute( nTangents * 2 * 3, 3 );
geometry.setAttribute( 'position', positions );
super( geometry, new LineBasicMaterial( { color, toneMapped: false } ) );
this.object = object;
this.size = size;
this.type = 'VertexTangentsHelper';
//
this.matrixAutoUpdate = false;
this.update();
}
update() {
this.object.updateMatrixWorld( true );
const matrixWorld = this.object.matrixWorld;
const position = this.geometry.attributes.position;
//
const objGeometry = this.object.geometry;
const objPos = objGeometry.attributes.position;
const objTan = objGeometry.attributes.tangent;
let idx = 0;
// for simplicity, ignore index and drawcalls, and render every tangent
for ( let j = 0, jl = objPos.count; j < jl; j ++ ) {
_v1.fromBufferAttribute( objPos, j ).applyMatrix4( matrixWorld );
_v2.fromBufferAttribute( objTan, j );
_v2.transformDirection( matrixWorld ).multiplyScalar( this.size ).add( _v1 );
position.setXYZ( idx, _v1.x, _v1.y, _v1.z );
idx = idx + 1;
position.setXYZ( idx, _v2.x, _v2.y, _v2.z );
idx = idx + 1;
}
position.needsUpdate = true;
}
dispose() {
this.geometry.dispose();
this.material.dispose();
}
}
export { VertexTangentsHelper };

View File

@ -0,0 +1,333 @@
import {
BoxGeometry,
CanvasTexture,
Color,
Euler,
Mesh,
MeshBasicMaterial,
Object3D,
OrthographicCamera,
Quaternion,
Raycaster,
Sprite,
SpriteMaterial,
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( '#ff3653' );
const color2 = new Color( '#8adb00' );
const color3 = new Color( '#2c8fff' );
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 BoxGeometry( 0.8, 0.05, 0.05 ).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 posXAxisHelper = new Sprite( getSpriteMaterial( color1, 'X' ) );
posXAxisHelper.userData.type = 'posX';
const posYAxisHelper = new Sprite( getSpriteMaterial( color2, 'Y' ) );
posYAxisHelper.userData.type = 'posY';
const posZAxisHelper = new Sprite( getSpriteMaterial( color3, 'Z' ) );
posZAxisHelper.userData.type = 'posZ';
const negXAxisHelper = new Sprite( getSpriteMaterial( color1 ) );
negXAxisHelper.userData.type = 'negX';
const negYAxisHelper = new Sprite( getSpriteMaterial( color2 ) );
negYAxisHelper.userData.type = 'negY';
const negZAxisHelper = new Sprite( getSpriteMaterial( color3 ) );
negZAxisHelper.userData.type = 'negZ';
posXAxisHelper.position.x = 1;
posYAxisHelper.position.y = 1;
posZAxisHelper.position.z = 1;
negXAxisHelper.position.x = - 1;
negXAxisHelper.scale.setScalar( 0.8 );
negYAxisHelper.position.y = - 1;
negYAxisHelper.scale.setScalar( 0.8 );
negZAxisHelper.position.z = - 1;
negZAxisHelper.scale.setScalar( 0.8 );
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 );
if ( point.x >= 0 ) {
posXAxisHelper.material.opacity = 1;
negXAxisHelper.material.opacity = 0.5;
} else {
posXAxisHelper.material.opacity = 0.5;
negXAxisHelper.material.opacity = 1;
}
if ( point.y >= 0 ) {
posYAxisHelper.material.opacity = 1;
negYAxisHelper.material.opacity = 0.5;
} else {
posYAxisHelper.material.opacity = 0.5;
negYAxisHelper.material.opacity = 1;
}
if ( point.z >= 0 ) {
posZAxisHelper.material.opacity = 1;
negZAxisHelper.material.opacity = 0.5;
} else {
posZAxisHelper.material.opacity = 0.5;
negZAxisHelper.material.opacity = 1;
}
//
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.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 = null ) {
const canvas = document.createElement( 'canvas' );
canvas.width = 64;
canvas.height = 64;
const context = canvas.getContext( '2d' );
context.beginPath();
context.arc( 32, 32, 16, 0, 2 * Math.PI );
context.closePath();
context.fillStyle = color.getStyle();
context.fill();
if ( text !== null ) {
context.font = '24px Arial';
context.textAlign = 'center';
context.fillStyle = '#000000';
context.fillText( text, 32, 41 );
}
const texture = new CanvasTexture( canvas );
return new SpriteMaterial( { map: texture, toneMapped: false } );
}
}
}
export { ViewHelper };