/**
* @module AxesHelper
* @description An axis object to visualize the 1, 2 or 3 axes.
* @author [Andrej Hristoliubov]{@link https://github.com/anhr}
*
* @copyright 2011 Data Arts Team, Google Creative Lab
*
* @license under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
import { SpriteText } from '../SpriteText/SpriteText.js';
//import { SpriteText } from 'https://raw.githack.com/anhr/commonNodeJS/master/SpriteText/SpriteText.js';
import clearThree from '../clearThree.js';
//import clearThree from 'https://raw.githack.com/anhr/commonNodeJS/master/clearThree.js';
import { getObjectPosition } from '../getPosition.js';
import three from '../three.js'
import Options from '../Options.js'
class AxesHelper {
/**
* An axis object to visualize the 1, 2 or 3 axes.
* @param {THREE.Group|THREE.Scene} group [THREE.Group]{@link https://threejs.org/docs/index.html?q=group#api/en/objects/Group} or [THREE.Scene]{@link https://threejs.org/docs/index.html?q=sce#api/en/scenes/Scene}.
* @param {object} [options=new Options()] the following options are available.
* See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* @param {boolean} [options.axesHelper] false - do not create a <b>AxesHelper</b> instance.
* @param {object} [options.scales={}] axes scales.
*
* @param {object} [options.scales[axis]] axes options. <b>axis</b> is "x" or "y" or "z".
* @param {string} [options.scales[axis].name="X" or "Y" or "Z" or "W"] axis name.
* @param {number} [options.scales[axis].min=-1] Minimum range of the axis.
* @param {number} [options.scales[axis].max=1] Maximum range of the axis.
* @param {number} [options.scales[axis].marks=3] Number of scale marks.
* @param {number} [options.scales[axis].zoomMultiplier=1.1] zoom multiplier.
* @param {number} [options.scales[axis].offset=0.1] position offset.
*
* @param {THREE.Vector3} [options.scales.posAxesIntersection=new THREE.Vector3()] Position of the axes intersection.
* @param {boolean} [options.scales.display=true] true - displays the label and scale of the axes.
* @param {object} [options.scales.color='rgba(255, 255, 255, 0.5)'] axes color. Available color names see [THREE.Color]{@link https://threejs.org/docs/index.html?q=color#api/en/math/Color}.NAMES.
* @param {object} [options.scales.text={}] followed options of the text of the marks is available
* @param {boolean} [options.scales.text.precision=4] Formats a scale marks into a specified length.
* @param {number} [options.scales.text.textHeight=0.04] The height of the text.
* @param {object} [options.scales.text.rect={}] rectangle around the text.
* @param {boolean} [options.scales.text.rect.displayRect=true] true - the rectangle around the text is visible.
* @param {number} [options.scales.text.rect.borderRadius=15]
* @param {THREE.PerspectiveCamera} [options.camera] [PerspectiveCamera]{@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera}. Use the camera key if you want control cameras focus.
* Set the camera if you want to see text size is independent from camera.fov. The text height will be calculated as textHeight = camera.fov * textHeight / 50
* See https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera.fov about camera.fov.
* Default is undefined. Default camera.fov is 50.
*/
constructor( group, options ) {
const THREE = three.THREE;
//Этот вызов нужен на случай, когда в приложении import из guiSelectPoint.js происходит из разных файлов. Например
//В одном месте
//import { GuiSelectPoint, getObjectPosition } from 'https://raw.githack.com/anhr/commonNodeJS/master/guiSelectPoint/guiSelectPoint.js';
//а в другом месте
//import { GuiSelectPoint, getObjectPosition } from '../../../commonNodeJS/master/guiSelectPoint/guiSelectPoint.js';
//Тогда в приложении будет два экземпляра guiSelectPoint.js
//Для обоих экземпляров guiSelectPoint.js надо установить отдельный THREE
// GuiSelectPoint.setTHREE( THREE );
const axesHelper = this;
options = options || new Options();
if ( !options.boOptions ) {
console.error( 'AxesHelper: call options = new Options( options ) first' );
return;
}
if ( options.axesHelper === false ) return;
options.camera.fov = options.camera.fov || 50;
options.scales = options.scales || {};
options.scales.color = options.scales.color || 'rgba(255, 255, 255, 0.5)';//'white';//0xffffff;
/*
options.scales.display = options.scales.display !== undefined ? options.scales.display : true;
options.scales.text = options.scales.text || {};
*/
options.scales.text.textHeight = options.scales.text.textHeight || 0.04;//Please specify the textHeight if you want the changing of the text height is available in gui.
options.scales.text.precision = options.scales.text.precision || 4;
options.scales.text.rect = options.scales.text.rect || {};
options.scales.text.rect.displayRect = options.scales.text.rect.displayRect !== undefined ? options.scales.text.rect.displayRect : true;
options.scales.text.rect.borderRadius = options.scales.text.rect.borderRadius !== undefined ? options.scales.text.rect.borderRadius : 15;
function setScale( axisName ) {
const scale = options.scales[axisName];
if ( !scale ) return;
// if ( scale.marks === undefined ) scale.marks = 3;
if ( scale.offset === undefined ) scale.offset = 0.1;
if ( scale.zoomMultiplier === undefined ) scale.zoomMultiplier = 1.1;
}
setScale( 'x' );
setScale( 'y' );
setScale( 'z' );
/**
* See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* */
this.options = options;
const groupAxesHelper = new THREE.Group();
groupAxesHelper.userData.optionsSpriteText = {
fontColor: options.scales.color,
textHeight: options.scales.text.textHeight,
fov: options.camera.fov,
rect: options.scales.text.rect,
}
group.add( groupAxesHelper );
options.scales.posAxesIntersection = options.scales.posAxesIntersection || new THREE.Vector3();//For moving of the axes intersection to the center of the canvas ( to the camera focus )
/**
* create axis
* @param {string} axisName axis name
*/
this.createAxis = function ( axisName ) {
const group = new THREE.Group();
group.visible = options.scales.display;
const scale = options.scales[axisName];
if ( !scale.isAxis() )
return;
var color = options.scales.color, opacity = 1;
try {
var array = options.scales.color.split( /rgba\(\.*/ )[1].split( /\)/ )[0].split( /, */ );
color = 'rgb(' + array[0] + ', ' + array[1] + ', ' + array[2] + ')';
if ( array[3] !== undefined )
opacity = array[3];
} catch ( e ) { }
const lineAxis = new THREE.Line( new THREE.BufferGeometry().setFromPoints( [
//Begin vertice of the axis
new THREE.Vector3(
//X
axisName !== 'x' ? 0
: !options.scales.x ? 0//X axis is not exists
: options.scales.x.min,//begin of the X axix
//Y
axisName !== 'y' ? 0
: !options.scales.y ? 0//Y axis is not exists
: options.scales.y.min,//begin of the Y axix
//Z
axisName !== 'z' ? 0
: !options.scales.z ? 0//Z axis is not exists
: options.scales.z.min,//begin of the Z axix
),
//end vertice of the axis
new THREE.Vector3(
//X
axisName !== 'x' ? 0
: !options.scales.x ? 0//X axis is not exists
: options.scales.x.max,//end of the X axix
//Y
axisName !== 'y' ? 0
: !options.scales.y ? 0//Y axis is not exists
: options.scales.y.max,//end of the Y axix
//Z
axisName !== 'z' ? 0
: !options.scales.z ? 0//Z axis is not exists
: options.scales.z.max,//end of the Z axix
),
] ), new THREE.LineBasicMaterial( { color: color, opacity: opacity, transparent: true, } ) );
//moving of the axes intersection to the center of the canvas ( to the camera focus ) munus posAxesIntersection
if ( axisName !== 'x' ) lineAxis.position.x = options.scales.posAxesIntersection.x;
if ( axisName !== 'y' ) lineAxis.position.y = options.scales.posAxesIntersection.y;
if ( axisName !== 'z' ) lineAxis.position.z = options.scales.posAxesIntersection.z;
lineAxis.add( group );
lineAxis.userData.axisName = axisName;
groupAxesHelper.add( lineAxis );
if ( scale.marks !== undefined ) {
const SpriteMark = function (
position,
) {
position = position || new THREE.Vector3( 0, 0, 0 );
const sizeAttenuation = false;
const sprite = new THREE.Sprite( new THREE.SpriteMaterial( {
map: new THREE.Texture(),
sizeAttenuation: sizeAttenuation,
} ) );
const canvas = document.createElement( 'canvas' );
sprite.material.map.minFilter = THREE.LinearFilter;
const context = canvas.getContext( '2d' );
function update() {
const center = new THREE.Vector2(
//x
axisName !== 'y' ? 0.5 ://For x and z axes риска не сдвигается
0,//For y axes риска сдвигается вправо
//y
axisName === 'y' ? 0.5 ://For y axes риска не сдвигается
1//For x and z axes риска сдвигается вниз
);
var width = 3;//, linesCount = 1,
context.fillStyle = options.scales.color;//'rgba(0, 255, 0, 1)';
context.fillRect( 0, 0, canvas.width, canvas.height );
// Inject canvas into sprite
sprite.material.map.image = canvas;
sprite.material.map.needsUpdate = true;
if ( axisName === 'y' ) {
sprite.scale.x = ( width * ( canvas.width / canvas.height ) ) / canvas.width;
sprite.scale.y = 1 / canvas.height;
} else {
sprite.scale.x = 1 / canvas.width;
sprite.scale.y = width / canvas.height;
}
sprite.scale.x *= options.camera.fov / ( 50 * 2 );
sprite.scale.y *= options.camera.fov / ( 50 * 2 );
sprite.position.copy( position );
sprite.center = center;
//size attenuation. Whether the size of the sprite is attenuated by the camera depth. (Perspective camera only.) Default is false.
//See https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation
sprite.material.sizeAttenuation = sizeAttenuation;
sprite.material.needsUpdate = true;
function getTextPrecision() {
return options.scales.text.precision !== undefined ? text.toPrecision( options.scales.text.precision ) : text.toString();
}
var text = ( axisName === 'x' ? position.x : axisName === 'y' ? position.y : position.z );
function getCenterX() {
const a = ( 0.013 - 0.05 ) / 15, b = 0.013 - 17 * a;
return - width * ( getTextPrecision().length * a + b );
}
const spriteText = new SpriteText(
getTextPrecision(),
new THREE.Vector3(
position.x,
position.y,
position.z,
), {
group: group,
rotation: axisName === 'y' ? 0 : - Math.PI / 2,
center: new THREE.Vector2(
getCenterX(),//текст по оси y сдвигается вправо
//текст по оси x и z сдвигается вниз
axisName === 'x' ? 1 ://текст по оси x сдвигается влево
0,//текст по оси z сдвигается вправо,
//текст по оси y сдвигается вверх
),
} );
spriteText.userData.updatePrecision = function () {
spriteText.userData.updateText( text.toPrecision( options.scales.text.precision ) );
spriteText.center.x = getCenterX();
}
group.add( spriteText );
};
update();
return sprite;
};
const max = scale.max,
min = scale.min,
d = ( max - min ) / ( scale.marks - 1 );
for ( var i = 0; i < scale.marks; i++ ) {
const pos = i * d + min;
group.add( new SpriteMark( new THREE.Vector3(
axisName === 'x' ? pos : 0,
axisName === 'y' ? pos : 0,
axisName === 'z' ? pos : 0,
) ) );
}
}
//Axis name
var axisNameOptions = {
center: new THREE.Vector2(
axisName === 'y' ? 1.1 : -0.1,
axisName === 'y' ? 0 : -0.1
),
group: group,
}
// scale.name = scale.name || axisName;
group.add( new SpriteText(
scale.name,
new THREE.Vector3(
axisName === 'x' ? scale.max : 0,
axisName === 'y' ? scale.max : 0,
axisName === 'z' ? scale.max : 0,
), axisNameOptions ) );
group.add( new SpriteText(
scale.name,
new THREE.Vector3(
axisName === 'x' ? scale.min : 0,
axisName === 'y' ? scale.min : 0,
axisName === 'z' ? scale.min : 0,
), axisNameOptions ) );
}
this.createAxis( 'x' );
this.createAxis( 'y' );
this.createAxis( 'z' );
if ( groupAxesHelper.children.length === 0 )
console.warn( 'AxesHelper: Define at least one axis.' )
//dotted lines
function dotLines( _scene ) {
var lineX, lineY, lineZ, scene = _scene,
groupDotLines, intersection;
this.remove = function () {
if ( groupDotLines === undefined )
return;
//clear memory
//
//Если это не делать, то со временем может произойти событие webglcontextlost
//https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/webglcontextlost_event
//
//for testing
// open http://localhost/threejs/nodejs/controllerPlay/Examples/html/ page
// select a point
// open dat.gui
// in the PlayController:
// click the ⥀ button
// Rate of changing of animation scenes per second to 25
// click the ► play button
// Now you can see animation of the scene
// In the Windows open Resource Monitor
// Open the Memory tab
// The Commit(KB) for chrome.exe do not increasing about 20 minutes.
clearThree( groupDotLines );
scene.remove( groupDotLines );
groupDotLines = undefined;
lineX = undefined;
lineY = undefined;
lineZ = undefined;
};
function createGroup() {
dotLines.remove();
groupDotLines = new THREE.Group();
scene.add( groupDotLines );
}
function verticeAxis( axisName ) { return ( options.scales.posAxesIntersection[axisName] - group.position[axisName] ) / group.scale[axisName]; }
function getDashSize() { return 0.05 / ( Math.max( Math.max( group.scale.x, group.scale.y ), group.scale.z ) ); }
this.dottedLines = function ( _intersection ) {
intersection = _intersection;
const pointVertice = intersection instanceof THREE.Vector4 || intersection instanceof THREE.Vector3 ? intersection : getObjectPosition( intersection.object, intersection.index );
if ( groupDotLines !== undefined ) {
function dottedLine( axisName ) {
var line;
switch ( axisName ) {
case 'x':
line = lineX;
break;
case 'y':
line = lineY;
break;
case 'z':
line = lineZ;
break;
default: console.error( 'AxesHelper.dotLines.dottedLines.dottedLine: axesId = ' + axesId );
return;
}
if ( !line )
return;//Current axis is not exists
var lineVertices = line.geometry.attributes.position.array;
lineVertices[0] = axisName === 'x' ? pointVertice.x :
verticeAxis( 'x' );//group.position.x, group.scale.x );
lineVertices[1] = axisName === 'y' ? pointVertice.y :
verticeAxis( 'y' );//group.position.y, group.scale.y );
lineVertices[2] = axisName === 'z' ? pointVertice.z :
verticeAxis( 'z' );//group.position.z, group.scale.z );
lineVertices[3] = pointVertice.x;
lineVertices[4] = pointVertice.y;
lineVertices[5] = pointVertice.z;
var size = getDashSize();
line.material.dashSize = size;
line.material.gapSize = size;
line.geometry.attributes.position.needsUpdate = true;
}
dottedLine( 'x' );
dottedLine( 'y' );
dottedLine( 'z' );
return;
}
createGroup();
function dottedLine( axisName ) {
if ( !options.scales[axisName].isAxis() )
return;
var lineVertices = [
new THREE.Vector3().copy( options.scales.posAxesIntersection ),
pointVertice,
];
lineVertices[0].x = axisName === 'x' ? lineVertices[1].x : verticeAxis( 'x' );
lineVertices[0].y = axisName === 'y' ? lineVertices[1].y : verticeAxis( 'y' );
lineVertices[0].z = axisName === 'z' ? lineVertices[1].z : verticeAxis( 'z' );
var size = getDashSize();
if ( options.colorsHelper === undefined )
options.colorsHelper = 0x80;
var line = new THREE.LineSegments( new THREE.BufferGeometry().setFromPoints( lineVertices ),
new THREE.LineDashedMaterial( {
color: 'rgb(' + options.colorsHelper + ', ' + options.colorsHelper + ', ' + options.colorsHelper + ')',
dashSize: size, gapSize: size
} ) );
line.computeLineDistances();
groupDotLines.add( line );
return line;
}
lineX = dottedLine( 'x' );
lineY = dottedLine( 'y' );
lineZ = dottedLine( 'z' );
};
this.update = function () {
if ( ( groupDotLines === undefined ) || ( intersection === undefined ) )
return;
this.dottedLines( intersection );
}
this.movePointAxes = function ( axesId, value ) {
var line;
switch ( axesId ) {
case mathBox.axesEnum.x:
line = lineX;
break;
case mathBox.axesEnum.y:
line = lineY;
break;
case mathBox.axesEnum.z:
line = lineZ;
break;
default: console.error( 'point.userData.movePointAxes: invalid axesId: ' + axesId );
return;
}
if ( line === undefined )
return;
line.geometry.attributes.position.array[axesId + 3] = value;
lineX.geometry.attributes.position.array[axesId] = value;
lineY.geometry.attributes.position.array[axesId] = value;
lineZ.geometry.attributes.position.array[axesId] = value;
lineX.geometry.attributes.position.needsUpdate = true;
lineY.geometry.attributes.position.needsUpdate = true;
lineZ.geometry.attributes.position.needsUpdate = true;
};
}
dotLines = new dotLines( group );
var _intersection;
/**
* Expose position on axes.
* @param {THREE.Vector3|object} intersection position or intersection. See {@link https://threejs.org/docs/index.html#api/en/core/Raycaster|Raycaster} for detail.
*/
this.exposePosition = function ( intersection ) {
_intersection = intersection;
if ( intersection === undefined ) {
_intersection = undefined;
dotLines.remove();
return;
}
dotLines.dottedLines( intersection );
}
/**
* move exposed position on axes.
*/
this.movePosition = function () {
if ( !_intersection )
return;
this.exposePosition( _intersection );
}
/**
* get group
* @returns {@link https://threejs.org/docs/index.html#api/en/objects/Group|Group}
*/
this.getGroup = function () { return groupAxesHelper; }
/**
* update axes
*/
this.updateAxes = function () {
function updateAxis( axisName ) {
groupAxesHelper.children.forEach( function ( group ) {
if ( group.userData.axisName !== axisName )
return;
groupAxesHelper.remove( group );
axesHelper.createAxis( axisName );
} );
}
updateAxis( 'x' );
updateAxis( 'y' );
updateAxis( 'z' );
dotLines.update();
}
options.axesHelper = this;
}
}
export default AxesHelper;