/**
* @module Player
* @description 3D objects animation.
* @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 ScaleController from '../ScaleController.js';
import PositionController from '../PositionController.js';
import { controllers } from '../dat.gui/CustomController/build/dat.gui.module.js';
import {
getWorldPosition,
getObjectLocalPosition,
getObjectPosition,
} from '../getPosition.js';
import three from '../three.js'
import Options from '../Options.js'
import { createController } from '../controller.js'
/**
* @callback onSelectScene
* @description This function is called at each new step of the playing. See <b>settings.onSelectScene</b> parameter of the <a href="../../player/jsdoc/module-Player-Player.html" target="_blank">Player</a> class.
* @param {number} index current index of the scene of the animation
* @param {number} t current time
*/
/**
* @callback onChangeScaleT
* @description User has updated the time settings. See <b>settings.onChangeScaleT</b> parameter of the <a href="../../player/jsdoc/module-Player-Player.html" target="_blank">Player</a> class.
* @param {object} scale the updated time settings
*/
class Player {
/**
* 3D objects animation.
* @class
* @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} of the meshes for playing.
* @param {Object} [settings={}] the following settings are available
*
* @param {Options} [settings.options=new Options()] <a href="../../jsdoc/Options/Options.html" target="_blank">Options</a> instance. The following options are available.
* See <b>options</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* @param {boolean} [settings.options.dat.playerGui] false - do not adds a <a href="../../player/jsdoc/module-Player-Player.html#gui" target="_blank">Player controllers</a> into [dat.gui]{@link https://github.com/dataarts/dat.gui}.
* @param {boolean} [settings.options.dat.playController] false - do not adds a <a href="../../player/jsdoc/module-Player-Player_PlayController_PlayController.html" target="_blank">PlayController</a> into [dat.gui]{@link https://github.com/dataarts/dat.gui}.
* @param {Object} [settings.options.playerOptions] Player settings.
* @param {number} [settings.options.playerOptions.min=0] Start time of the playing.
* @param {number} [settings.options.playerOptions.max=1] Stop time of the playing.
* @param {number} [settings.options.playerOptions.marks=10] Ticks count of the playing. Number of scenes of 3D objects animation.
* Have effect for <b>max</b> is not Infinity.
* @param {number} [settings.options.playerOptions.selectSceneIndex=0] current time index.
* <pre>
* Legal interval from 0 to to <b>marks - 1</b>.
* If <b>selectSceneIndex</b> = 0 then time = <b>min</b>.
* If <b>selectSceneIndex</b> = <b>marks - 1</b> then time = <b>max</b>.
* <pre>
* @param {number} [settings.options.playerOptions.dt=0.1] Step of the animation. Have effect only if <b>max</b> is infinity.
* @param {boolean} [settings.options.playerOptions.repeat=false] true - Infinitely repeating 3D objects animation.
* @param {number} [settings.options.playerOptions.interval=1] Ticks per seconds.
* @param {number} [settings.options.playerOptions.zoomMultiplier=1.1] zoom multiplier of the time.
* @param {number} [settings.options.playerOptions.offset=0.1] offset of the time.
* @param {number} [settings.options.playerOptions.name=""] name of the time.
* @param {boolean} [settings.options.player] false - do not create a <b>Player</b> instance.
*
* @param {onSelectScene} [settings.onSelectScene] This function is called at each new step of the playing. See <a href="../../player/jsdoc/module-Player.html#~onSelectScene" target="_blank">onSelectScene</a>.
* @param {onChangeScaleT} [settings.onChangeScaleT] event. User has updated the time settings. See <a href="../../player/jsdoc/module-Player.html#~onChangeScaleT" target="_blank">onChangeScaleT</a>.
* @param {FrustumPoints} [settings.frustumPoints] See <a href="../../frustumPoints/jsdoc/index.html" target="_blank">FrustumPoints</a>.
* Have effect only if <b>settings.options</b> is not defined.
* @param {THREE.PerspectiveCamera} settings.cameraTarget.camera [PerspectiveCamera]{@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera}.
*/
constructor( group, settings={} ) {
assign();
if ( !settings.options && settings.frustumPoints ) settings.options = settings.frustumPoints.getOptions();
settings.options = settings.options || new Options();
const options = settings.options;
if ( !options.boOptions ) {
console.error( 'Player: call options = new Options( options ) first' );
return;
}
if ( options.player === false ) return;
options.boPlayer = options.boPlayer || false;
//если тут создавать палитру то она не создастся если не создается плееер
//palette = new palette();
if ( options.player ) {
console.error( 'Player: duplicate player.' );
return;
}
options.player = this;
settings.cameraTarget = settings.cameraTarget || {};
/**
* @description This function is called at each new step of the playing.
* @param {number} [index=0] current index of the scene of the animation
*/
function onSelectScene( index ) {
index = index || 0;
const t = _this.getTime();
Player.selectPlayScene( group, { t: t, index: index, options: settings.options } );
_this.setIndex( index, ( options.playerOptions.name === '' ? '' : options.playerOptions.name + ': ' ) + t );
if ( settings.onSelectScene ) _this.selectScenePause = settings.onSelectScene( index, t );
if ( options.frustumPoints ) options.frustumPoints.updateCloudPoints();
}
//Теперь не нужно создавать color attribute на веб странице что бы цвет точек был верным еще до начала проигрывания
//Кроме того трассировака начинается с нулевой точки
setTimeout( function () { onSelectScene(); }, 0 );
options.playerOptions.selectSceneIndex = options.playerOptions.selectSceneIndex || 0;
var selectSceneIndex = options.playerOptions.selectSceneIndex;
const _this = this;
/**
* get time id
*/
this.getTimeId = function () { return selectSceneIndex; }
/**
* get time
* @param {number} [timeId] Index of the time. The default is the time index of the selected scene.
*/
this.getTime = function (timeId) {
const playerOptions = options.playerOptions, t = playerOptions.min + (timeId != undefined ? timeId : selectSceneIndex) * playerOptions.dt;
if ( isNaN( t ) ) console.error( 'Player.getTime(): t = ' + t );
if ( ( playerOptions.max !== null ) && ( t > playerOptions.max ) )
console.error( 'Player.getTime(): t = ' + t + ' playerOptions.max = ' + playerOptions.max );
if ( ( t < playerOptions.min ) && ( playerOptions.max !== null ) )
console.error( 'Player.getTime(): t = ' + t + ' playerOptions.min = ' + playerOptions.min );
return t;
}
/**
* set time
* @param {number} t time
* @returns false - invalid <b>t</b>.
*/
this.setTime = function ( t ) {
return this.selectScene( parseInt( ( t - options.playerOptions.min ) / options.playerOptions.dt ) );
}
/**
* select scene for playing
* @param {number} index Index of the scene. Range from 0 to options.playerOptions.marks - 1
* @returns false - invalid <b>index</b>.
*/
this.selectScene = function ( index ) {
if ( index === undefined ) {
onSelectScene( selectSceneIndex );
return true;
}
if ( isNaN( index ) ) {
console.error( 'Player.selectScene: index = ' + index );
return false;
}
index = parseInt( index );
if ( options.playerOptions.max !== null ) {
if ( index >= options.playerOptions.marks )
index = 0;
else if ( index < 0 )
index = options.playerOptions.marks - 1;
if ( selectSceneIndex > options.playerOptions.marks )
selectSceneIndex = options.playerOptions.marks;
}
while ( selectSceneIndex !== index ) {
if ( selectSceneIndex < index )
selectSceneIndex++;
else selectSceneIndex--;
onSelectScene( selectSceneIndex );
}
return true;
}
/**
* Go to next animation scene
*/
this.next = function () {
_this.selectScene( selectSceneIndex + 1 );
}
/**
* Go to previous animation scene
*/
this.prev = function () {
_this.selectScene( selectSceneIndex - 1 );
}
/**
* Add controller into controllers array
* @param {controller} controller
*/
this.pushController = function ( controller ) {
if ( ( controller.object !== undefined ) && ( controller.object.playRate !== undefined ) )
controller.object.playRate = options.playerOptions.min;
this.controllers.push( controller );
}
//Play/Pause
this.controllers = [];
var playing = false, time, timeNext;
function RenamePlayButtons() {
options.player.controllers.forEach( function ( controller ) { if ( controller.onRenamePlayButtons ) controller.onRenamePlayButtons( playing ); } );
}
function play() {
if (
( selectSceneIndex === -1 ) ||
(
( selectSceneIndex === options.playerOptions.marks ) &&
( options.playerOptions.max !== null )
)
) {
selectSceneIndex = 0;
}
onSelectScene( selectSceneIndex );
}
function pause() {
playing = false;
RenamePlayButtons();
time = undefined;
}
function isRepeat() {
return options.playerOptions.repeat;
}
function playNext() {
selectSceneIndex++;
if ( ( options.playerOptions.max !== null ) && selectSceneIndex >= options.playerOptions.marks ) {
if ( isRepeat() )
selectSceneIndex = 0;
else {
//Для вычисления текущего времени в случае:
//1. Запустить плеер и дождаться когда проигрывание остановится после достижения максимального времени
//2. В guiSelectPoint выбрать точку, цвет которой зависит от времени
//Тогда если убрать эту строку, то selectSceneIndex и время окажется за диапазоном допустимых значений
//и цвет точки окажется неверным
selectSceneIndex = options.playerOptions.marks - 1;
pause();
return;
}
}
play();
}
function step(timestamp) {
if (_this.selectScenePause) return;//При построении сцены был применен ProgressBar. Поэтому возврат из options.onSelectScene произошел еще до построения сцены. Нужно приостановить проигрывание, пока сцена не построится.
if (playing)
window.requestAnimationFrame(step);
else time = undefined;
if (time === undefined) {
time = timestamp;
timeNext = time + 1000 / options.playerOptions.interval;
}
if (isNaN(timeNext) || (timeNext === Infinity)) {
console.error('Player.animate: timeNext = ' + timeNext);
playing = false;
}
if (timestamp < timeNext)
return;
while (timestamp > timeNext)
timeNext += 1000 / options.playerOptions.interval;
playNext();
}
/**
* User has clicked the Play ► / Pause ❚❚ button
*/
this.play3DObject = function () {
if ( playing ) {
pause();
return;
}
playing = true;
if ( ( options.playerOptions.max !== null ) && ( selectSceneIndex >= ( options.playerOptions.marks - 1 ) ) )
selectSceneIndex = 0;//-1;
playNext();
RenamePlayButtons();
window.requestAnimationFrame( step );
}
/**
* <pre>
*Continue playing asynchronously.
* Usual using if creating new scene to take long time.
* Please call it from <b>settings.options.onSelectScene()</b> function after asynchronously creating of the scene.
* Return true from <b>settings.options.onSelectScene()</b> function for pause of the playing.
* </pre>
* @see <a href="../../../../universe/main/jsdoc/" target="_blank">example</a>
*/
this.continue = () => {
_this.selectScenePause = false;
window.requestAnimationFrame(step);
}
/**
* User has clicked the repeat ⥀ button
*/
this.repeat = function () {
options.playerOptions.repeat = !options.playerOptions.repeat;
this.onChangeRepeat( options.playerOptions.repeat );
}
/**
* @returns Player settings.
*/
this.getSettings = function () { return settings; }
/**
* @returns selected scene index.
*/
this.getSelectSceneIndex = function () { return selectSceneIndex; }
/**@namespace
* @descriptionUser has pressed the <b>Repeat</b> button of the <a href="../../player/jsdoc/module-Player-Player_PlayController_PlayController.html" target="_blank">Player.PlayController</a>.
* @param {boolean} value true - repeat is off
* <p>false - repeat is on</p>
*/
this.onChangeRepeat = function ( value ) {
options.playerOptions.repeat = value;
this.controllers.forEach( function ( controller ) { if ( controller.onChangeRepeat ) controller.onChangeRepeat(); } );
}
//Localization
function getLang( params ) {
params = params || {};
const lang = {
player: 'Player',
playerTitle: '3D objects animation.',
min: 'Min',
max: 'Max',
dt: 'Step',
dtTitle: 'Time between frames',
marks: 'Frames',
marksTitle: 'Player frames count',
interval: 'Rate',
intervalTitle: 'Rate of changing of animation scenes per second.',
time: 'Time',
defaultButton: 'Default',
defaultTitle: 'Restore default player settings.',
};
const _languageCode = params.getLanguageCode === undefined ? 'en'//Default language is English
: params.getLanguageCode();
switch ( _languageCode ) {
case 'ru'://Russian language
lang.player = 'Проигрыватель';
lang.playerTitle = 'Анимация 3D объектов.';
lang.min = 'Минимум';
lang.max = 'Максимум';
lang.dt = 'Шаг';
lang.dtTitle = 'Веремя между кадрами';
lang.marks = 'Кадры';
lang.marksTitle = 'Количество кадров проигрывателя';
lang.interval = 'Темп',
lang.intervalTitle = 'Скорость смены кадров в секунду.';
lang.time = 'Время';
lang.defaultButton = 'Восстановить';
lang.defaultTitle = 'Восстановить настройки проигрывателя по умолчанию.';
break;
default://Custom language
if ( ( params.lang === undefined ) || ( params.lang._languageCode != _languageCode ) )
break;
Object.keys( params.lang ).forEach( function ( key ) {
if ( _lang[key] === undefined )
return;
_lang[key] = params.lang[key];
} );
}
return lang;
}
//PlayController localization
const lang = {
prevSymbol: '←',
prevSymbolTitle: 'Go to previous animation scene',
nextSymbol: '→',
nextSymbolTitle: 'Go to next animation scene',
playSymbol: '►',
playTitle: 'Play',
pause: '❚❚',
pauseTitle: 'Pause',
repeat: '⥀',
repeatOn: 'Turn repeat on',
repeatOff: 'Turn repeat off',
controllerTitle: 'Current time.',
stereoEffects: 'Stereo effects',
mono: 'Mono',
sideBySide: 'Side by side',
topAndBottom: 'Top and bottom',
};
function localization( getLanguageCode ) {
switch ( getLanguageCode() ) {
case 'ru'://Russian language
lang.prevSymbolTitle = 'Кадр назад';//'Go to previous animation scene',
lang.playTitle = 'Проиграть';//'Play'
lang.nextSymbolTitle = 'Кадр вперед';//'Go to next animation scene';
lang.pauseTitle = 'Пауза';//'Pause',
lang.repeatOn = 'Повторять проигрывание';
lang.repeatOff = 'Остановить повтор проигрывания';
lang.controllerTitle = 'Текущее время.';
lang.stereoEffects = 'Стерео эффекты';
lang.mono = 'Моно';
lang.sideBySide = 'Слева направо';
lang.topAndBottom = 'Сверху вниз';
break;
}
}
/** <a href="../../player/jsdoc/module-Player-Player_PlayController_PlayController.html" target="_blank">PlayController</a> localization
* @param {Function} [getLanguageCode="en"] Your custom getLanguageCode() function.
* <pre>
* returns the "primary language" subtag of the language version of the browser.
* Examples: "en" - English language, "ru" Russian.
* See the {@link https://tools.ietf.org/html/rfc4646#section-2.1|rfc4646 2.1 Syntax} for details.
* Default returns the 'en' is English language.
* You can import { getLanguageCode } from '../../commonNodeJS/master/lang.js';
* </pre>
* @returns Names and titles of the <b>PlayController</b> controls
*/
this.localization = function ( getLanguageCode = function() { return 'en' } ) {
localization( getLanguageCode );
return lang;
}
this.PlayController = class extends controllers.CustomController {
/**
* @class playController class for using in my version of [dat.gui]{@link https://github.com/anhr/dat.gui} for animate of 3D objects in my projects.
* @extends dat.controllers.CustomController. See [CustomController.js]{@link https://github.com/anhr/commonNodeJS/blob/master/dat.gui/CustomController/src/dat/controllers/CustomController.js}
* @param {GUI} [gui] [new dat.GUI(...)]{@link https://github.com/anhr/dat.gui}. Folder for controller
*/
constructor( gui ) {
const player = options.player, getLanguageCode = options.getLanguageCode;
player.createControllersButtons( options );
gui = gui || options.dat.gui;
if ( !gui || options.dat.playController === false ) {
super( {} );
return;
}
localization( getLanguageCode );
function addButton( innerHTML, title, onclick ) {
const button = document.createElement( 'span' );
button.innerHTML = innerHTML;
button.title = title;
button.style.cursor = 'pointer';
button.style.margin = '0px 2px';
button.onclick = onclick;
return button;
}
var _renamePlayButtons, _renameRepeatButtons;//_selectScene, _repeat, _getGroup,
const colorOff = 'rgb(255,255,255)', colorOn = 'rgb(128,128,128)';
super( {
playRate: 1,//Default play rate is 1 changes per second
property: function ( customController ) {
var buttons = {};
function RenamePlayButtons( innerHTML, title ) {
buttons.buttonPlay.innerHTML = innerHTML;
buttons.buttonPlay.title = title;
}
_renamePlayButtons = RenamePlayButtons;
buttons.buttonPrev = addButton( lang.prevSymbol, lang.prevSymbolTitle, player.prev );
buttons.buttonPlay = addButton( playing ? lang.pause : lang.playSymbol,
playing ? lang.pauseTitle : lang.playTitle, player.play3DObject );
if ( player.getSettings().options.playerOptions.max !== null ) {
//Repeat button
function RenameRepeatButtons( isRepeat ) {
var title, color;
if ( isRepeat ) {
title = lang.repeatOff;
color = colorOff;
} else {
title = lang.repeatOn;
color = colorOn;
}
if ( buttons.buttonRepeat.title === title )
return;//stop of infinite recursive call
buttons.buttonRepeat.title = title;
buttons.buttonRepeat.style.color = color;
player.onChangeRepeat( isRepeat );
}
_renameRepeatButtons = RenameRepeatButtons;
function repeat( value ) {
RenameRepeatButtons( buttons.buttonRepeat.title === lang.repeatOn );
}
var title, color;
if ( player.getSettings().repeat ) {
title = lang.repeatOff;
color = colorOff;
} else {
title = lang.repeatOn;
color = colorOn;
}
buttons.buttonRepeat = addButton( lang.repeat, title, repeat );
buttons.buttonRepeat.style.color = color;
}
buttons.buttonNext = addButton( lang.nextSymbol, lang.nextSymbolTitle, player.next );
function getGroup() {
return group;
}
return buttons;
},
}, 'playRate' );
player.PlayController = this;
this.lang = lang;
/** @namespace
* @description Get array of THREE.Vector4 points. User has pressed the <b>Play</b> button. Rename the <b>Play</b> putton.
* @param {boolean} playing true - name is "❚❚"
* <p>false = name is "►"
*/
this.onRenamePlayButtons = function ( playing ) {
var name, title;
if ( playing ) {
name = lang.pause;
title = lang.pauseTitle;
} else {
name = lang.playSymbol;
title = lang.playTitle;
}
_renamePlayButtons( name, title, true );
}
/** @namespace
* @description User has pressed the <b>Repeat</b> button.
*/
this.onChangeRepeat = function () {
_renameRepeatButtons( player.getSettings().options.playerOptions.repeat );
}
player.pushController( this );
/**
* @param {number} value current time of the playing
*/
this.setValue = function ( value ) {
this._controller.domElement.childNodes[0].value = value;
}
const controler = gui.add( this );
//что бы можно было вводить цифры после запятой
controler.__truncationSuspended = true;
}
set controller( newController ) {
this._controller = newController;
var _this = this;
this._controller.onChange( function ( value ) { options.player.setTime( value ); /*_this.onChange( value );*/ } );
this._controller.domElement.title = this.lang.controllerTitle;
}
get controller() { return this._controller; }
}
/**
* Adds a Player's controllers into [dat.gui]{@link https://github.com/anhr/dat.gui}.
* @param {GUI} [folder] Player's folder
*/
this.gui = function ( folder ) {
const cookie = options.dat.cookie,
cookieName = options.dat.getCookieName( 'Player' ),
getLanguageCode = options.getLanguageCode,
dat = three.dat;// options.dat.dat;
folder = folder || options.dat.gui;
if ( !folder || options.dat.playerGui === false )
return;
function setDT() {
if ( options.playerOptions.max === null ) options.playerOptions.dt = options.playerOptions.dt || 0.1;
else options.playerOptions.dt = ( options.playerOptions.max - options.playerOptions.min ) / ( options.playerOptions.marks - 1 );
}
function setSettings() {
setDT();
cookie.setObject( cookieName, options.playerOptions );
if ( settings.onChangeScaleT ) settings.onChangeScaleT( options.playerOptions );
}
function setMax() {
if ( options.playerOptions.max !== null )
options.playerOptions.max = options.playerOptions.min + options.playerOptions.dt * ( options.playerOptions.marks - 1 );
}
setMax();
const axesDefault = JSON.parse( JSON.stringify( options.playerOptions ) ),
lang = getLang( {
getLanguageCode: getLanguageCode,
} );
Object.freeze( axesDefault );
const max = options.playerOptions.max, marks = options.playerOptions.marks;
cookie.getObject( cookieName, options.playerOptions, options.playerOptions );
if ( ( max === null ) || ( max === Infinity ) ||
( options.playerOptions.max === null )//раньше на веб странице плеер был настроен на бесконечное проигрыванияе а сейчас проигрывание ограничено по времени
) {
options.playerOptions.max = max;
options.playerOptions.marks = marks;
}
const fPlayer = folder.addFolder( lang.player );
dat.folderNameAndTitle( fPlayer, lang.player, lang.playerTitle );
function scale() {
const axes = options.playerOptions,
scaleControllers = {};
function onclick( customController, action ) {
var zoom = customController.controller.getValue();
axes.min = action( axes.min, zoom );
scaleControllers.min.setValue( axes.min );
if ( axes.max ) {
axes.max = action( axes.max, zoom );
setDT();
scaleControllers.max.setValue( axes.max );
}
setSettings();
}
scaleControllers.folder = fPlayer.addFolder( axes.name !== '' ? axes.name : lang.time );
scaleControllers.scaleController = scaleControllers.folder.add( new ScaleController( onclick,
{ settings: settings.options.playerOptions, getLanguageCode: getLanguageCode, } ) ).onChange( function ( value ) {
axes.zoomMultiplier = value;
setSettings();
} );
const positionController = new PositionController( function ( shift ) {
onclick( positionController, function ( value, zoom ) {
value += shift;
return value;
} );
}, { settings: settings.options.playerOptions, getLanguageCode: getLanguageCode, } );
scaleControllers.positionController = scaleControllers.folder.add( positionController ).onChange( function ( value ) {
axes.offset = value;
setSettings();
} );
//min
scaleControllers.min = dat.controllerZeroStep( scaleControllers.folder, axes, 'min', function ( value ) { setSettings(); } );
dat.controllerNameAndTitle( scaleControllers.min, lang.min );
//max
//axes.max сейчас используется исключитьельно для удобства пользовательского интерфейса
//Вместо axes.max используется axes.dt
setMax();
if ( axes.max !== null ) {
scaleControllers.max = dat.controllerZeroStep( scaleControllers.folder, axes, 'max', function ( value ) { setSettings(); } );
dat.controllerNameAndTitle( scaleControllers.max, lang.max );
} else {
//dt
scaleControllers.dt = dat.controllerZeroStep( scaleControllers.folder, axes, 'dt', function ( value ) { setSettings(); } );
dat.controllerNameAndTitle( scaleControllers.dt, lang.dt, lang.dtTitle );
}
//marks
if ( axes.marks ) {
scaleControllers.marks = scaleControllers.folder.add( axes, 'marks' ).onChange( function ( value ) {
axes.marks = parseInt( axes.marks );
setSettings();
const elSlider = getSliderElement();
if ( elSlider ) elSlider.max = options.playerOptions.marks - 1;
} );
dat.controllerNameAndTitle( scaleControllers.marks, axes.marksName === undefined ? lang.marks : axes.marksName,
axes.marksTitle === undefined ? lang.marksTitle : axes.marksTitle );
}
//Ticks per seconds.
scaleControllers.interval = scaleControllers.folder.add( settings.options.playerOptions, 'interval', 1, 25, 1 ).onChange( function ( value ) {
setSettings();
} );
dat.controllerNameAndTitle( scaleControllers.interval, lang.interval, lang.intervalTitle );
//Default button
dat.controllerNameAndTitle( scaleControllers.folder.add( {
defaultF: function ( value ) {
axes.zoomMultiplier = axesDefault.zoomMultiplier;
scaleControllers.scaleController.setValue( axes.zoomMultiplier );
axes.offset = axesDefault.offset;
scaleControllers.positionController.setValue( axes.offset );
axes.min = axesDefault.min;
scaleControllers.min.setValue( axes.min );
if ( scaleControllers.max ) {
axes.max = axesDefault.max;
setDT();
scaleControllers.max.setValue( axes.max );
}
if ( scaleControllers.dt ) {
axes.dt = axesDefault.dt;
scaleControllers.dt.setValue( axes.dt );
}
if ( axesDefault.marks ) {
axes.marks = axesDefault.marks;
//scaleControllers.marks is undefined если программист сначала установил max: Infinity,
//соханил Player in cookie, например изменил marks
//удалил max: Infinity,
//Нажал кнопку Default для проигрывателя
if ( scaleControllers.marks )
scaleControllers.marks.setValue( axes.marks );
}
axes.interval = axesDefault.interval;
scaleControllers.interval.setValue( axes.interval );
setSettings();
},
}, 'defaultF' ), lang.defaultButton, lang.defaultTitle );
}
scale();
}
/**
* Adds player buttons into web page.
* @param {Object} options the following options are available
* @param {Object} [options.controllers.player] player buttons, available from web page
* @param {HTMLElement|string} [options.controllers.player.buttonPrev="prev"] Go to previous animation scene.
* <pre>
* HTMLElement - <b>input</b> element of the "button" type.
* string - <b>id</b> of <b>input</b> element.
* </pre>
* @param {HTMLElement|string} [options.controllers.player.buttonPlay="play"] play or pause of the playing.
* <pre>
* HTMLElement - <b>input</b> element of the "button" type.
* string - <b>id</b> of <b>input</b> element.
* </pre>
* @param {HTMLElement|string} [options.controllers.player.buttonNext="next"] Go to next animation scene.
* <pre>
* HTMLElement - <b>input</b> element of the "button" type.
* string - <b>id</b> of <b>input</b> element.
* </pre>
*/
this.createControllersButtons = function ( options ) {
if ( !options.controllers || !options.controllers.player ) return;
const settings = options.controllers.player
//Previous button
if ( settings.buttonPrev === null ) console.warn( 'Player.createControllersButtons: invalid options.controllers.player.buttonPrev = ' + settings.buttonPrev );
if ( settings.buttonPrev ) {
const buttonPrev = typeof settings.buttonPrev === 'string' ? document.getElementById( settings.buttonPrev ) : settings.buttonPrev;
if ( buttonPrev === null ) console.warn( 'Player.createControllersButtons: invalid options.controllers.player.buttonPrev = ' + settings.buttonPrev );
if ( buttonPrev ) {
buttonPrev.value = lang.prevSymbol;
buttonPrev.title = lang.prevSymbolTitle;
buttonPrev.onclick = function ( event ) { if ( options.player ) options.player.prev(); }
settings.buttonPrev = buttonPrev;
}
}
//Play button
if ( settings.buttonPlay === null ) console.warn( 'Player.createControllersButtons: invalid options.controllers.player.buttonPlay = ' + settings.buttonPlay );
if ( settings.buttonPlay ) {
const buttonPlay = typeof settings.buttonPlay === 'string' ? document.getElementById( settings.buttonPlay ) : settings.buttonPlay;
if ( buttonPlay === null ) console.warn( 'Player.createControllersButtons: invalid options.controllers.player.buttonPlay = ' + settings.buttonPlay );
if ( buttonPlay ) {
buttonPlay.value = playing ? lang.pause : lang.playSymbol;
buttonPlay.title = playing ? lang.pauseTitle : lang.playTitle;
buttonPlay.onclick = function ( event ) { if ( options.player ) options.player.play3DObject(); }
settings.buttonPlay = buttonPlay;
}
}
//Next button
if ( settings.buttonNext === null ) console.warn( 'Player.createControllersButtons: invalid options.controllers.player.buttonNext = ' + settings.buttonNext );
if ( settings.buttonNext ) {
const buttonNext = typeof settings.buttonNext === 'string' ? document.getElementById( settings.buttonNext ) : settings.buttonNext;
if ( buttonNext === null ) console.warn( 'Player.createControllersButtons: invalid options.controllers.player.buttonNext = ' + settings.buttonNext );
if ( buttonNext ) {
buttonNext.value = lang.nextSymbol;
buttonNext.title = lang.nextSymbolTitle;
buttonNext.onclick = function ( event ) { if ( options.player ) options.player.next(); }
settings.buttonNext = buttonNext;
}
}
}
var _canvasMenu;
/**
* Adds a Player's menu item into [CanvasMenu]{@link https://github.com/anhr/commonNodeJS/tree/master/canvasMenu}.
* @param {CanvasMenu} canvasMenu [CanvasMenu]{@link https://github.com/anhr/commonNodeJS/tree/master/canvasMenu}.
* @param {Function} [getLanguageCode="en"] Your custom getLanguageCode() function.
* <pre>
* returns the "primary language" subtag of the language version of the browser.
* Examples: "en" - English language, "ru" Russian.
* See the {@link https://tools.ietf.org/html/rfc4646#section-2.1|rfc4646 2.1 Syntax} for details.
* Default returns the 'en' is English language.
* You can import { getLanguageCode } from '../../commonNodeJS/master/lang.js';
* </pre>
*/
this.createCanvasMenuItem = function ( canvasMenu, getLanguageCode = function() { return 'en' } ) {
_canvasMenu = canvasMenu;
const player = this, menu = canvasMenu.menu, lang = player.localization( getLanguageCode );
//Previous button
menu.push( {
name: lang.prevSymbol,
title: lang.prevSymbolTitle,
onclick: function ( event ) { player.prev(); }
} );
//Play button
menu.push( {
name: playing ? lang.pause : lang.playSymbol,
title: playing ? lang.pauseTitle : lang.playTitle,
id: "menuButtonPlay",
onclick: function ( event ) {
player.play3DObject();
}
} );
if ( options.playerOptions.max !== null ) {
//Repeat button
menu.push( {
name: lang.repeat,
title: this.getSettings().repeat ? lang.repeatOff : lang.repeatOn,
id: "menuButtonRepeat",
onclick: function ( event ) { player.repeat(); }
} );
}
//Next button
menu.push( {
name: lang.nextSymbol,
title: lang.nextSymbolTitle,
onclick: function ( event ) { player.next(); }
} );
this.controllers.push( {
/* *
* Renames the "Play" button of the player's menu.
* @param {boolean} playing <b>true</b> - pause.
* <p><b>false</b> - play</p>
*/
onRenamePlayButtons: function ( playing ) {
var name, title;
if ( playing ) {
name = lang.pause;
title = lang.pauseTitle;
} else {
name = lang.playSymbol;
title = lang.playTitle;
}
const elMenuButtonPlay = canvasMenu.querySelector( '#menuButtonPlay' );
elMenuButtonPlay.innerHTML = name;
elMenuButtonPlay.title = title;
if ( options.controllers && options.controllers.player && options.controllers.player.buttonPlay ) {
options.controllers.player.buttonPlay.value = name;
options.controllers.player.buttonPlay.title = title;
}
},
/* *
* Changes "Repeat" button of the player's menu between <b>repeat Off</b> and <b>repeat On</b>.
*/
onChangeRepeat: function () {
canvasMenu.querySelector( '#menuButtonRepeat' ).title = options.playerOptions.repeat ? lang.repeatOff : lang.repeatOn;
}
} );
}
/**
* Adds slider menu item into [CanvasMenu]{@link https://github.com/anhr/commonNodeJS/tree/master/canvasMenu}.
*/
this.addSlider = function () {
if ( options.playerOptions.max === null )
return;
_canvasMenu.menu.push( {
name: '<input type="range" min="0" max="' + ( options.playerOptions.marks - 1 ) + '" value="0" class="slider" id="sliderPosition">',
style: 'float: right;',
} );
}
function getSliderElement() { if ( _canvasMenu ) return _canvasMenu.querySelector( '#sliderPosition' ); }
/**
* Adds an events into slider menu item of the [CanvasMenu]{@link https://github.com/anhr/commonNodeJS/tree/master/canvasMenu}.
* @returns slider element
*/
this.addSliderEvents = function () {
const elSlider = getSliderElement(), player = this;
if ( elSlider ) {
elSlider.onchange = function ( event ) { player.selectScene( parseInt( elSlider.value ) ); };
elSlider.oninput = function ( event ) { player.selectScene( parseInt( elSlider.value ) ); };
var pointerdown;
const player = this;
elSlider.addEventListener( 'pointerdown', e => { pointerdown = true; } );
elSlider.addEventListener( 'pointerup', e => { pointerdown = false; } );
elSlider.addEventListener( 'mousemove', e => {
if ( !pointerdown )
return;
player.selectScene( ( options.playerOptions.marks - 1 ) * e.offsetX / elSlider.clientWidth );
} );
}
return elSlider;
}
/**
* Sets <b>index</b> and <b>title</b> of the slider element of the player's menu.
* @param {string} index
* @param {string} title
*/
this.setIndex = function ( index, title ) {
const t = this.getTime();
if ( options.controllers && options.controllers.t )
options.controllers.t.controller.value = t;
if ( typeof this.PlayController === "object" ) this.PlayController.setValue( t );
const elSlider = getSliderElement();
if ( elSlider ) {
elSlider.value = index;
elSlider.title = title;
}
}
/**
* Changes the "max" value of the slider of the player's menu. Moves <b>Player</b> to the first scene.
* @param {Object} scale See <b>options.playerOptions</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
*/
this.onChangeScale = function ( scale ) {
getSliderElement().max = scale.marks - 1;
this.selectScene( 0 );
}
}
}
/**
* @class
*/
Player.cameraTarget = class {
/**
* Functions for camera for looking at selected point.
*/
constructor() {
const cameraTargetDefault = { boLook: false, },//По умолчанию не слежу за точкой
_cameraTarget = {
boLook: cameraTargetDefault.boLook,
getDistanceToCamera() {
if ( typeof this.distanceToCameraCur !== 'undefined' ) return this.distanceToCameraCur;
return this.distanceToCamera;
}
};
var _options;
cameraTargetDefault.rotation = {};
_cameraTarget.rotation = {};
var boTarget = false,//true - target point was detected. For displaying of the console warning if duplicate target point was detected
boPlayer = false;//true - была попытка получить camera из _options.player. Добавил что бы не выполнялась лишняя работа
//Если определен ( boCameraTargetLook !== undefined ) , то явно было задано следить или не следить за точкой.
//Тогда если есть точка, за которой надо следить ( cameraTarget.bodefault === false )
//и явно не было задано следить или не следить заточкой ( boCameraTargetLook === undefined ),
//то надо следить за точкой ( cameraTargetDefault.boLook = true )
var boCameraTargetLook;
/**
* get camera target
* @param {Object} [options] See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* @param {Object} [options.player] <a href="../../player/jsdoc/index.html" target="_blank">Player</a> instance.
*/
this.get = function ( options ) {
if ( !options && !_options )
console.error( 'Player.cameraTarget.get: options = ' + options );
else if ( _options && options && !Object.is( _options, options ) )
console.error( 'Player.cameraTarget.get: options is ambiguous' );
_options = _options || options;
if ( !_cameraTarget.camera && !boPlayer && _options.player ) {
cameraTargetDefault.camera = _options.player.getSettings().cameraTarget.camera;
if ( cameraTargetDefault.camera ) setCameraTarget();
boPlayer = true;
}
if ( _cameraTarget.camera )
return _cameraTarget;
}
/**
* Create default camera target
* @param {object} cameraTarget the following cameraTarget are available:
* @param {THREE.PerspectiveCamera} [cameraTarget.camera] [PerspectiveCamera]{@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera}
* @param {boolean} [cameraTarget.boLook=false] true - camera look at the target.
* @param {THREE.Vector3} [cameraTarget.distanceToCamera] Distance from target point to camera.
* You can set the distance to the camera depending on the time.
* <pre>
* Example 1: new THREE.Vector3( 0, 0, new Function( 't', 'return 2+t' ) )
* Example 2: new THREE.Vector3( 0, 0,
* [ { t: 0, v: 5 }, { t: 1, v: 2 }, { t: 10, v: 2 }, { t: 11, v: 5 } ] )
* Default is camera.position.
* </pre>
* @param {object} [cameraTarget.rotation] rotation camera around point specified by an axis and an angle. Default is undefined - no rotation
* @param {number|function|array} [cameraTarget.rotation.angle=0] Angle of rotation in radians.
* <pre>
* number. Example: Math.PI / 2 rotate to 90 degrees.
* function. Example: new Function( 't', 'return 5*t' ).
* array.
* Example 1: [0, Math.PI]
* 0 is angle for t = min is start time of the playing.
* Math.PI is rotate to 180 degrees
* for t = max is time of the stopping of the playing.
* If max time is infinity, then angle is for t = min.
* Example 2: [{ t: 0, v: 0 }, { t: 1, v: Math.PI / 2 }
* t is time,
* v is angle for current t.
* </pre>
* @param {THREE.Vector3} [cameraTarget.rotation.axis] Axis of rotattion.
* <pre>
* Example: new THREE.Vector3( 1, 0, 0 ) - rotate around x axis.
* Default is rotate around y axis
* </pre>
* @param {Object} options See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* @param {boolean} [boErrorMessage=true] false - Do not send an error message to the console if <b>cameraTarget.camera</b> is not defined.
*/
this.init = function ( cameraTarget, options, boErrorMessage = true ) {
if ( !cameraTarget ) return;
if ( !options && !_options )
console.error( 'Player.cameraTarget.init: options = ' + options );
else if ( _options && options && !Object.is( _options, options ) )
console.error( 'Player.cameraTarget.init: options is ambiguous' );
_options = _options || options;
//cameraTargetDefault.boLook = false по умолчанию камера не смотритт на выбранную точку если:
// 1 ни разу не вызывается init. Тоесть нет настроек cameraTargetDefault и нет ни однойточки, на которую смотрит камера
// 2 cameraTarget.bodefault !== false и cameraTarget.boLook = false - программист явно запретил смотреть на точку
//Если есть хоть одна точка, на которую должна смотреть камера cameraTarget.bodefault === false,
//то для этой точки не устанавливаем playerCameraTarget.cameraTargetDefault
//Другими словами если есть точка, на которую должна смотреть камера,
//то камера будет на нее смотреть если нет запрета смотеть на точку - playerCameraTarget.cameraTargetDefault = false
if ( cameraTarget.bodefault !== false ) {
if ( boTarget )
return;//Не надо устанавливать cameraTargetDefault после того,
//как были устанвлены настройки cameraTargetDefault из точек
//Иначе натройки точки не будут приоритетными
if ( cameraTarget.boLook !== undefined ) {
cameraTargetDefault.boLook = cameraTarget.boLook;
boCameraTargetLook = cameraTarget.boLook;
}
} else if ( cameraTarget.boLook === true ) {
//Есть точка, за которой надо следить
if ( boTarget ) console.warn( 'playerCameraTarget().init(...): duplicate target point' );
boTarget = true;
if ( boCameraTargetLook === undefined )
cameraTargetDefault.boLook = true;//запрета на слежение камерой за точкой не было и есть точка, за которой надо следить
} else return;
cameraTargetDefault.camera = cameraTargetDefault.camera ||
cameraTarget.camera ||
( _options.player ? _options.player.getSettings().cameraTarget.camera : undefined );
if ( !cameraTargetDefault.camera && boErrorMessage ) {
console.error( 'playerCameraTarget().init(...): cameraTargetDefault.camera = ' + cameraTargetDefault.camera );
return;
}
cameraTargetDefault.distanceToCamera = cameraTargetDefault.distanceToCamera || cameraTarget.distanceToCamera;
cameraTarget.rotation = cameraTarget.rotation || {};
cameraTargetDefault.rotation.angle = cameraTargetDefault.rotation.angle || cameraTarget.rotation.angle;
cameraTargetDefault.rotation.axis = cameraTargetDefault.rotation.axis || cameraTarget.rotation.axis;
setCameraTarget( cameraTarget );
}
function setCameraTarget( cameraTarget ) {
assign();
if ( !cameraTarget )
cameraTarget = cameraTargetDefault;//У выбранной для слежения точки нет cameraTarget
if ( !_cameraTarget.boMaual ) {//если не делать эту проверку, то пользователь не сможет изменить вручную _cameraTarget.boLook когда выбрана точка, за которой надо следить
if ( cameraTarget.boLook !== undefined )
_cameraTarget.boLook = cameraTarget.boLook;
else _cameraTarget.boLook = cameraTargetDefault.boLook;
}
cameraTargetDefault.camera = cameraTargetDefault.camera || cameraTarget.camera;
_cameraTarget.camera = cameraTarget.camera || cameraTargetDefault.camera;
//Если в программе не определены функции distanceToCamera (тоесть cameraTarget.distanceToCamera === undefined и cameraTargetDefault.distanceToCamera === undefined),
//то при проигрывании каждый раз distanceToCamera будет равна camera.position
// _cameraTarget.distanceToCamera = cameraTarget.distanceToCamera || cameraTargetDefault.distanceToCamera || new THREE.Vector3().copy( cameraTargetDefault.camera.position );
//Если в программе не определены функции distanceToCamera (тоесть cameraTarget.distanceToCamera === undefined и cameraTargetDefault.distanceToCamera === undefined),
//То сначала _cameraTarget.distanceToCamera будет равна camera.position, а потом при проигрывании не будет меняться
_cameraTarget.distanceToCamera =
cameraTarget.distanceToCamera ||
cameraTargetDefault.distanceToCamera ||
_cameraTarget.distanceToCamera ||
new THREE.Vector3().copy( cameraTargetDefault.camera.position );
if ( !cameraTarget.rotation ) cameraTarget.rotation = {};
if ( cameraTarget.rotation.angle !== undefined )
_cameraTarget.rotation.angle = cameraTarget.rotation.angle;
else _cameraTarget.rotation.angle = cameraTargetDefault.rotation.angle || 0;
_cameraTarget.rotation.axis = cameraTarget.rotation.axis || cameraTargetDefault.rotation.axis || new THREE.Vector3( 0, 1, 0 );//Rotate around y axis
}
/**
* Change target.
* @param {THREE.Mesh} mesh [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} with selected point as target for camera.
* @param {number} i index of the point.
* @param {Options} [options] See <a href="../../jsdoc/Options/index.html" target="_blank">Options</a>.
*/
this.changeTarget = function ( mesh, i, options ) {
assign();
//Update cameraTarget
const func = !mesh.userData.player || ( typeof mesh.userData.player.arrayFuncs === "function" ) ? {} :
// mesh.userData.player.arrayFuncs[mesh.userData.myObject && mesh.userData.myObject.guiPoints ? mesh.userData.myObject.guiPoints.seletedIndex(i) : i];
mesh.userData.player.arrayFuncs[mesh.userData.myObject.guiPoints.seletedIndex(i)];
if ( !func.cameraTarget )
func.cameraTarget = { boLook: false };
setCameraTarget( func.cameraTarget );
_options = _options || options;
const cameraTarget = _options.playerOptions.cameraTarget.get( _options );
if ( cameraTarget ) {
if ( cameraTarget && cameraTarget.boLook ) {
const target = getWorldPosition( mesh, new THREE.Vector3().fromArray( mesh.geometry.attributes.position.array, i * mesh.geometry.attributes.position.itemSize ) );
cameraTarget.target = target;
} else delete cameraTarget.target;
}
}
/**
* Update camera settings.
* @param {Object} options See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
*/
this.setCameraTarget = function ( options ) {
assign();
var cameraTarget = options.playerOptions.cameraTarget.get( options );
if ( !cameraTarget ) cameraTarget = cameraTarget || {};
const camera = cameraTarget.camera;
if ( !camera )
return;//В этом приложении невозможно следить за точкой, потому что ни разу не была вызывана Player.cameraTarget.init()
//На случай когда не определена ни одна точка как cameraTarget и пользователь поставил птичку в controllerCameraTarget
if ( !cameraTarget.distanceToCamera )
cameraTarget.distanceToCamera = new THREE.Vector3().copy( camera.position );
if ( !cameraTarget.distanceToCameraCur )
cameraTarget.distanceToCameraCur = new THREE.Vector3();
const t = options.time,
distanceToCamera = cameraTarget.distanceToCamera,
distanceToCameraCur = new THREE.Vector3().copy( cameraTarget.distanceToCameraCur );
cameraTarget.distanceToCameraCur.set(
Player.execFunc( distanceToCamera, 'x', t, options ),
Player.execFunc( distanceToCamera, 'y', t, options ),
Player.execFunc( distanceToCamera, 'z', t, options )
);
if ( !cameraTarget.setCameraPosition )
cameraTarget.setCameraPosition = function ( /*setCameraDefault*/ ) {
var target = cameraTarget.target;
//не менять позицию камеры если
if (
!cameraTarget.boLook ||//не следить за точкой
(//или
!target &&//нет точки, за которой надо следить
cameraTarget.distanceToCameraCur.equals( distanceToCameraCur )//и не изменилось расстояние камеры до target
)
) {
return;//Камере не нужно следить за выбранной точкой или ни одна точка не определена как target
}
distanceToCameraCur.copy( cameraTarget.distanceToCameraCur );
const t = options.time;
camera.position.copy( cameraTarget.distanceToCameraCur );
camera.position.applyAxisAngle( cameraTarget.rotation.axis, Player.execFunc( cameraTarget.rotation, 'angle', t, options ) );
if ( !target ) {
if ( Player.orbitControls ) target = Player.orbitControls.target;
else {
//console.warn( 'Under constaction' );
return;
}
}
camera.position.add( target );
camera.lookAt( target );
if ( options.orbitControls ) {
if ( !options.orbitControls.target.equals( target ) ) {
options.orbitControls.target.copy( target );
if ( options.orbitControlsGui )
options.orbitControlsGui.setTarget( target );
}
if ( options.orbitControls._listeners )
options.orbitControls._listeners.change[0]();//move frustumpoints
}
}
if ( options.cameraGui ) options.cameraGui.update();
}
}
}
/** @namespace
* @description execute function
* @param {THREE.Vector4} funcs vector of the functions for executing.
* @param {string} axisName axis name of the function for executing. Can be as "x", "y", "z", "w".
* @param {number} t time. First parameter of the function for executing.
* @param {object} [options={}] the following options are available:
* @param {number} [options.a=1] multiplier. Second parameter of the function for executing.
* @param {number} [options.b=0] addendum. Third parameter of the function for executing.
* @returns function execution value.
*/
Player.execFunc = function ( funcs, axisName, t, options={} ) {
var func = funcs[axisName];
const a = options.a, b = options.b, typeofFuncs = typeof func;
if ( typeof t === "undefined" ) t = options.playerOptions ? options.playerOptions.min : 0;
switch ( typeofFuncs ) {
case "undefined":
return undefined;
case "number":
return func;
case "string":
func = new Function( 't', 'a', 'b', 'return ' + func );
case "function":
try {
const res = func( t, a, b );
if ( res === undefined )
throw 'function returns ' + res;
if ( !Array.isArray( res ) ) return res;
else func = res;
} catch( e ) {
console.error( e );
throw e;
return;
}
case "object":
if ( Array.isArray( func ) ) {
if ( func.length === 0 ) {
console.error( 'Player.execFunc: funcs["' + axisName + '"] array is empty' );
return;
}
const a = func,
l = func.length - 1,
max = options.playerOptions.max === null ? Infinity : options.playerOptions.max,
min = options.playerOptions.min,
tStep = ( max - min ) / l;
var tStart = min, tStop = max,
iStart = 0, iStop = l;
for ( var i = 0; i < func.length; i++ ) {
if ( tStep * i + min < t ) {
iStart = i;
iStop = i + 1;
tStart = tStep * iStart + min;
tStop = tStep * iStop + min;
}
}
function execW( i ) {
if ( typeof a[i] === "function" )
return a[i]( t, a, b );
if ( a[i] instanceof THREE.Color )
return a[i];
}
if ( typeof a[iStart] !== "number" ) {
if ( axisName === 'w' ) {
return execW( iStart );
}
if ( typeof a[iStart] === "object" ) {
for ( var i = 0; i < func.length; i++ ){
if ( i === ( func.length - 1 ) ) return a[i].v;
iStart = i; iStop = i + 1;
tStart = a[iStart].t; tStop = a[iStop].t;
if ( ( tStart <= t ) && ( tStop > t ) ) {
var x = ( a[iStop].v - a[iStart].v ) / ( tStop - tStart ),
y = a[iStart].v - x * tStart;
return x * t + y;
}
}
console.error( 'Player.execFunc: value is not detected' );
return;
} else {
console.error( 'Player.execFunc: funcs["' + axisName + '"] array item ' + iStart + ' typeof = ' + ( typeof a[iStart] ) + ' is not number' );
return;
}
}
if ( iStop >= func.length ) iStop = iStart;
if ( typeof a[iStop] !== "number" ) {
if ( axisName === 'w' )
return execW( iStop );
if ( typeof a[iStop] !== "object" ) {
console.error( 'Player.execFunc: funcs["' + axisName + '"] array item ' + iStop + ' typeof = ' + ( typeof a[iStop] ) + ' is not number' );
return;
}
}
var x = ( a[iStop] - a[iStart] ) / ( tStop - tStart ),
y = a[iStart] - x * tStart;
if ( isNaN( x ) || isNaN( y ) ) console.error( 'Player.execFunc: invalid x = ' + x + ' or y = ' + y );
return x * t + y;
}
if ( func.func )
return func.func instanceof Function ? func.func( t, a, b ) : func.func;
if ( axisName !== 'w' )
console.error( 'Player.execFunc: funcs["' + axisName + '"] object is not array' );
return func;
default:
console.error( 'Player.execFunc: Invalud typeof funcs["' + axisName + '"]: ' + typeofFuncs );
}
return;
}
var lang;
class Ids {
constructor() {
function addKeys( axisName ) {
function keyValue( controllerId ) {
const id = axisName + controllerId;
return {
get controllerId() { return this.boUsed ? undefined : id; },
get elController() { return document.getElementById( this.controllerId ); },
nameId: id + 'Name',
get elName() { return document.getElementById( this.nameId ); },
}
}
return {
func: keyValue( 'Func' ),
position: keyValue( 'Position' ),
worldPosition: keyValue( 'WorldPosition' ),
}
}
this.x = addKeys( 'x' );
this.y = addKeys( 'y' );
this.z = addKeys( 'z' );
this.w = addKeys( 'w' );
/*
y: addKeys( 'y' ),
z: addKeys( 'z' ),
w: addKeys( 'w' ),
*/
}
}
var ids = new Ids();
/** @namespace
* @description Select a scene for playing of the mesh
* @param {THREE.Mesh} mesh [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} for playing.
* @param {Object} [settings={}] the following settings are available
* @param {number} [settings.t=0] time
* @param {Object} [settings.options={ dat: false }] See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* @param {boolean} [settings.options.boPlayer] true - is not select play scene for mesh.userData.boFrustumPoints = true. Default is false.
* @param {object} [settings.options.playerOptions] The <b>settings.options.playerOptions</b> parameter of the <a href="./module-Player-Player.html" target="_blank">Player</a> .
* @param {number} [settings.options.a] multiplier. Second parameter of the arrayFuncs item function. Default is 1.
* @param {number} [settings.options.b] addendum. Third parameter of the arrayFuncs item function. Default is 0.
* @param {object} [settings.options.scales] axes scales.
* See <b>options.scales</b> of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> for details.
* @param {object} [settings.options.palette=new ColorPicker.palette();//palette: ColorPicker.paletteIndexes.BGYW] See <a href="../../colorPicker/jsdoc/module-ColorPicker.html#~Palette" target="_blank">ColorPicker.palette</a>.
* @param {object} [settings.options.point={}] point settings. Applies to points with ShaderMaterial.
* <pre>
* See [ShaderMaterial]{@link https://threejs.org/docs/index.html#api/en/materials/ShaderMaterial} for details.
* The size of the point seems constant and does not depend on the distance to the camera.
* </pre>
* @param {number} [settings.options.point.size=0.02] The apparent angular size of a point in radians.
*/
Player.selectMeshPlayScene = function ( mesh, settings = {} ) {
assign();
var t = settings.t, options = settings.options || { dat: false };
options = new Options( options );
if ( t === undefined ) t = options.playerOptions.min;
options.scales.setW();
if (
!mesh.userData.player ||
( options && options.boPlayer && mesh.userData.boFrustumPoints )
)
return;
//Эти строки нужны что бы появлялся текст возле точки, если на нее наведена мышка
//при условии, что до этого точка была передвинута с помошью проигрывателя.
if ( mesh.geometry ) {//scene do not have geometry
delete mesh.geometry.boundingSphere;
mesh.geometry.boundingSphere = null;
}
if ( mesh.userData.player.selectPlayScene ) {
mesh.userData.player.selectPlayScene( t );
//если не приводить угол поворота в диапазон 0 - 360 градусов,
//то угол поворота будет равен нулю или Math.PI * 2
//когда пользователь выберет mesh в guiSelectPoint.js
//потому что в органе управления угла поворота угол поворота ограничен 0 - 360 градусов,
//смотри строку
//cRotations[name] = fRotation.add( new THREE.Vector3(), name, 0, Math.PI * 2, 1 / 360 ).
//в guiSelectPoint
function setRotation( axisName ) {
while ( mesh.rotation[axisName] < 0 ) mesh.rotation[axisName] += Math.PI * 2;
while ( mesh.rotation[axisName] > Math.PI * 2 ) mesh.rotation[axisName] -= Math.PI * 2;
}
setRotation( 'x' );
setRotation( 'y' );
setRotation( 'z' );
}
function setAttributes( a, b ) {
if ( !mesh.geometry || mesh.userData.nd )
return;
const attributes = mesh.geometry.attributes,
arrayFuncs = mesh.userData.player.arrayFuncs;
if ( arrayFuncs === undefined )
return;
if ( t === undefined )
console.error( 'setPosition: t = ' + t );
/*
if ( mesh.userData.myObject && mesh.userData.myObject.isSetPosition ) {
}
*/
var min, max;
if ( options && ( options.scales.w !== undefined ) ) {
min = options.scales.w.min; max = options.scales.w.max;
} else {
max = value;
min = max - 1;
}
if ( !mesh.userData.myObject || !mesh.userData.myObject.isSetPosition ) {
for (var i = 0; i < arrayFuncs.length; i++) {
var funcs = arrayFuncs[i], needsUpdate = false;
function setPosition(axisName, fnName) {
var value = Player.execFunc(funcs, axisName, t, options);// a, b );
if (value !== undefined) {
attributes.position[fnName](i, value);
needsUpdate = true;
}
}
setPosition('x', 'setX');
setPosition('y', 'setY');
setPosition('z', 'setZ');
setPosition('w', 'setW');
//если тут поставить var то цвет точки, которая определена как THREE.Vector3 будет равет цвету предыдущей точки
//потому что перемнные типа var видны снаружи блока {}
let color;
function getColor() {
if (mesh.userData.player.palette)
color = mesh.userData.player.palette.toColor(value, min, max);
else if (options.palette)
color = options.palette.toColor(value, min, max);
else {
const c = { r: 255, g: 255, b: 255 };
color = new THREE.Color("rgb(" + c.r + ", " + c.g + ", " + c.b + ")");
return color;
}
}
if (typeof funcs.w === "function") {
var value = funcs.w(t, a, b);
if (options.scales.w) {
min = options.scales.w.min;
max = options.scales.w.max;
} else {
console.warn('Player.selectMeshPlayScene: Кажется эти экстремумы заданы неверно');
min = 0;
max = 100;
}
if (attributes.position.itemSize >= 4)
attributes.position.setW(i, value);
needsUpdate = true;
getColor();
} else if (typeof funcs.w === "object") {
if (funcs.w instanceof THREE.Color)
color = funcs.w;
else {
var value = Player.execFunc(funcs, 'w', t, options);
if (funcs.w.min !== undefined) min = funcs.w.min;
if (funcs.w.max !== undefined) max = funcs.w.max;
getColor();
}
}
color = setColorAttibute(
funcs.w === undefined ?
new THREE.Vector4().w :
typeof funcs.w === "number" ? funcs.w : Player.execFunc(funcs, 'w', t, options),
mesh, i, color);
if (needsUpdate)
attributes.position.needsUpdate = true;
if (funcs.trace && !funcs.line) {
funcs.line = new Player.traceLine(options);
funcs.trace = false;
}
if (funcs.line && funcs.line.addPoint)
funcs.line.addPoint(mesh, i, color);
if (funcs.cameraTarget && (funcs.cameraTarget.boLook === true))
options.playerOptions.cameraTarget.changeTarget(mesh, i, options);
}
}
}
setAttributes( options ? options.a : 1, options ? options.b : 0 );
const message = 'Player.selectMeshPlayScene: invalid mesh.scale.';
if ( mesh.scale.x <= 0 ) console.error( message + 'x = ' + mesh.scale.x );
if ( mesh.scale.y <= 0 ) console.error( message + 'y = ' + mesh.scale.y );
if ( mesh.scale.z <= 0 ) console.error( message + 'z = ' + mesh.scale.z );
function setColorAttibute( value, mesh, index , color ){
if (
//не менять цвет точки если позиция состоит из 3 значений
//такая ситуация имеется в геометрических фигурах. Например в кубе
( mesh.geometry.attributes.position.itemSize < 4 )// ||
//( options.scales.w.isColor === false )//цвет точки не зависит от координаты w точки
) return;
if ( options.palette )
color = options.palette.toColor( value, options.scales.w.min, options.scales.w.max );
if (!color) return;
const attributes = mesh.geometry.attributes, arrayFuncs = mesh.userData.player.arrayFuncs;
if ( !Player.setColorAttribute( attributes, index, color ) && arrayFuncs[index] instanceof THREE.Vector4 ) {
if ( mesh.userData.player && arrayFuncs ) {
mesh.geometry.setAttribute( 'color',
new THREE.Float32BufferAttribute( Player.getColors( arrayFuncs,
{
positions: attributes.position,
options: options,
} ), 4 ) );
if ( !Player.setColorAttribute( attributes, index, color ) )
console.error( 'Player.selectMeshPlayScene: the color attribute is not exists. Please use THREE.Vector3 instead THREE.Vector4 in the arrayFuncs or add "color" attribute' );
} else console.error( 'Player.selectMeshPlayScene: set color attribute failed. Invalid mesh.userData.player.arrayFuncs' );
}
return color;
}
if ( mesh.userData.player && mesh.userData.player.arrayFuncs && mesh.userData.player.arrayFuncs instanceof Array )
mesh.userData.player.arrayFuncs.forEach( function ( func, index ) {
if ( func.controllers ) {
//Localization
if ( !lang ) {
lang = {
controllerXTitle: 'X position',
controllerYTitle: 'Y position',
controllerZTitle: 'Z position',
controllerWTitle: 'color index',
controllerWorld: 'World',
controllerXWorldTitle: 'X world position',
controllerYWorldTitle: 'Y world position',
controllerZWorldTitle: 'Z world position',
controllerWWorldTitle: 'color index',
controllerXFunctionTitle: 'X = f(t)',
controllerYFunctionTitle: 'Y = f(t)',
controllerZFunctionTitle: 'Z = f(t)',
controllerWFunctionTitle: 'W = f(t)',
positionAlert: 'Invalid position fromat: ',
};
switch ( options.getLanguageCode() ) {
case 'ru'://Russian language
lang.controllerXTitle = 'Позиция X';
lang.controllerYTitle = 'Позиция Y';
lang.controllerZTitle = 'Позиция Z';
lang.controllerWTitle = 'Индекс цвета';
lang.controllerWorld = 'Абсолютный';
lang.controllerXWorldTitle = 'Абсолютная позиция X';
lang.controllerYWorldTitle = 'Абсолютная позиция Y';
lang.controllerZWorldTitle = 'Абсолютная позиция Z';
lang.controllerWWorldTitle = 'Индекс цвета';
lang.positionAlert = 'Неправильный формат позиции точки: ';
break;
default://Custom language
if ( ( options.lang === undefined ) || ( options.lang.languageCode != languageCode ) )
break;
Object.keys( options.lang ).forEach( function ( key ) {
if ( lang[key] === undefined )
return;
lang[key] = options.lang[key];
} );
}
}
const positionLocal = getObjectLocalPosition( mesh, index );
function setPosition( value, axisName ) {
const axesId = axisName === 'x' ? 0 : axisName === 'y' ? 1 : axisName === 'z' ? 2 : axisName === 'w' ? 3 : undefined;
if ( axisName === 'w' ){
setColorAttibute( value, mesh, index );
if ( options.guiSelectPoint )
options.guiSelectPoint.update();
}
const indexValue = axesId + mesh.geometry.attributes.position.itemSize * index,
valueOld = mesh.geometry.attributes.position.array[ indexValue ];
mesh.geometry.attributes.position.array[ indexValue ] = value;
const axisControllers = func.controllers[axisName];
if ( isNaN( mesh.geometry.attributes.position.array[ indexValue ] ) ) {
alert( lang.positionAlert + value );
const controller = axisControllers.position.controller;
controller.focus();
controller.value = valueOld;
mesh.geometry.attributes.position.array[ indexValue ] = valueOld;
return;
}
mesh.geometry.attributes.position.needsUpdate = true;
if ( options.axesHelper )
options.axesHelper.updateAxes();
if ( options.guiSelectPoint )
options.guiSelectPoint.update();
if ( axisControllers.worldPosition && axisControllers.worldPosition.controller ) {
const controller = axisControllers.worldPosition.controller;
controller.innerHTML = getObjectPosition( mesh, index )[axisName];
}
}
function createControllers( axisName ) {
var axisControllers = func.controllers[axisName];
if ( axisControllers === false ) return;
const position = 'position';
//если Controllers для текущей оси не определен а на веб странице есть Controllers для этой оси
//то нужно создать axisControllers
if ( !axisControllers && ( ids[axisName].func.elController || ids[axisName].position.elController || ids[axisName].worldPosition.elController ) ) {
axisControllers = {};
func.controllers[axisName] = axisControllers;
}
if ( !axisControllers ) return;
//добавляем в axisControllers те Controllers, которые есть на веб странице для текущей оси
function addKey( keyName ) {
if ( !ids[axisName][keyName].elController ) return;
if ( !axisControllers[keyName] ) {
if ( !ids[axisName][keyName].boUsed ) {
axisControllers[keyName] = {
controller: ids[axisName][keyName].elController,
elName: ids[axisName][keyName].elName ? ids[axisName][keyName].elName : false,
}
ids[axisName][keyName].boUsed = true;
if ( ( keyName === position ) && ( axisName === 'w' ) )
axisControllers[keyName].elSlider = true;
} else console.warn( 'Player.selectMeshPlayScene createControllers: Same controller is using for different points. Controller ID is "' + ids[axisName][keyName].controllerId + '""' );
}
}
addKey( 'func' );
addKey( position );
addKey( 'worldPosition' );
createController( axisControllers.func, ids[axisName].func.controllerId, function () { return options.scales[axisName].name + ' = f(t)'; }, {
value: func[axisName],
title: axisName === 'x' ? lang.controllerXFunctionTitle : axisName === 'y' ? lang.controllerYFunctionTitle : axisName === 'z' ? lang.controllerZFunctionTitle : axisName === 'w' ? lang.controllerWFunctionTitle : '',
onchange: function ( event ) {
try {
func[axisName] = event.currentTarget.value;
const value = Player.execFunc( func, axisName, options.player.getTime(), options );
if ( axisControllers.position && axisControllers.position.controller ) {
const controller = axisControllers.position.controller;
controller.onchange( { currentTarget: { value: value } } );
controller.value = value;
} else
setPosition( value, axisName );
if ( options.guiSelectPoint )
options.guiSelectPoint.update();
} catch ( e ) {
alert( 'Axis: ' + options.scales[axisName].name + '. Function: "' + func[axisName] + '". ' + e );
event.currentTarget.focus();
}
},
} );
//position
createController( axisControllers.position, axisName + 'Position', function () { return options.scales[axisName].name; }, {
value: positionLocal[axisName],
title: axisName === 'x' ? lang.controllerXTitle : axisName === 'y' ? lang.controllerYTitle : axisName === 'z' ? lang.controllerZTitle : axisName === 'w' ? lang.controllerWTitle : '',
onchange: function ( event ) { setPosition( event.currentTarget.value, axisName ); },
axisName: axisName,
} );
//World position
createController( axisControllers.worldPosition, axisName + 'WorldPosition', function () { return lang.controllerWorld + ' ' + options.scales[axisName].name; }, {
value: getWorldPosition( mesh, positionLocal )[axisName],
title: axisName === 'x' ? lang.controllerXWorldTitle : axisName === 'y' ? lang.controllerYWorldTitle : axisName === 'z' ? lang.controllerZWorldTitle : axisName === 'w' ? lang.controllerWTitle : '',
} );
}
//point name
if ( func.name ) {
if ( !func.controllers.pointName ) func.controllers.pointName = 'pointName';
const elPointName = typeof func.controllers.pointName === "string" ? document.getElementById( func.controllers.pointName ) : func.controllers.pointName ;
if ( elPointName ) elPointName.innerHTML = func.name;
}
createControllers( 'x' );
createControllers( 'y' );
createControllers( 'z' );
createControllers( 'w' );
}
} );
if ( !options || !options.guiSelectPoint ) {
if ( options.axesHelper ) options.axesHelper.movePosition();
return;
}
options.guiSelectPoint.setMesh();
var selectedPointIndex = options.guiSelectPoint.getSelectedPointIndex();
if ( ( selectedPointIndex !== -1 ) && options.guiSelectPoint.isSelectedMesh( mesh ) ) {
options.guiSelectPoint.setPosition( {
object: mesh,
index: selectedPointIndex,
} );
}
}
/** @namespace
* @description set color attribute
* @param {Object} attributes [geometry.attributes]{@link https://threejs.org/docs/index.html?q=geometry#api/en/core/BufferGeometry.attributes} of the mesh
* @param {number} i index of the color in the color attribute array.
* @param {THREE.Color} color color.
* @returns true - success
* <p>false - colorAttribute was not detected.</p>
*/
Player.setColorAttribute = function ( attributes, i, color ) {
if ( typeof color === "string" )
color = new THREE.Color( color );
const colorAttribute = attributes.color;// || attributes.ca;
if ( colorAttribute === undefined )
return false;
colorAttribute.setX( i, color.r );
colorAttribute.setY( i, color.g );
colorAttribute.setZ( i, color.b );
colorAttribute.needsUpdate = true;
return true;
}
/** @namespace
* @description Get array of THREE.Vector4 points.
* @param {THREE.Vector4|THREE.Vector3|THREE.Vector2|object|array} arrayFuncs <b>points.geometry.attributes.position</b> array
* <pre>
* THREE.Vector4: 4D point.
* THREE.Vector3: 3D point. w = 1. Default is white color
* THREE.Vector2: 2D point. w = 1, z = 0. Default is white color
* Vector's x, y, z, w is position of the point.
* Can be as:
* <ul>
* <li>float - position of the point.</li>
* Example: 0.5
* <li>[float] - array of positions of the point.</li>
* Example 1: [0.6, 0, -0.5]
* 0.6 is positon for t = min is start time of the playing.
* 0 is position for t = ( max - min ) / 2
* -0.5 is positon for t = max is stop time of the playing.
* If stop time of the playing is infinity, then position is equal to the first item of the array or 0.6 in the example 1.
*
* Example 2: [{ t: 0, v: 0.6 }, { t: 1, v: -0.6 }]
* Every item of array is object with:
* t is time
* v is position for current t.
* <li>[Function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function} - position of the point is function of the t.
* Example: new Function( 't', 'a', 'b', 'return Math.sin(t*a*2*Math.PI)*0.5+b' )</li>
* <li>string - text of the function.
* Example: 'Math.sin(t*a*2*Math.PI)*0.5+b' )</li>
* </ul>
* <b>Vector.w</b> is index of the [palette]{@link https://github.com/anhr/commonNodeJS/tree/master/colorpicker}.
* Default range of the <b>Vector.w</b> is from 0 to 1. You can change range by use an object:
* {
* func: Vector.w
* max: new max value of tne Vector.w
* min: new min value of tne Vector.w
* }
* Example:
* {
*
* func: new Function( 't', 'return 1-2*t' ),
* min: -1,
* max: 1,
*
* }
* <b>Vector.w</b> can be as [THREE.Color]{@link https://threejs.org/docs/index.html?q=colo#api/en/math/Color}. Example: new THREE.Color( "rgb(255, 127, 0)" )
*
* object: {
* vector: THREE.Vector4|THREE.Vector3|THREE.Vector2 - point position
* [name]: point name. Default is undefined.
* [trace]: true - displays the trace of the point movement. Default is undefined.
*
* //You can set the camera to look at a point. Use <b>cameraTarget</b> key for it.
* //ATTENTION!!! Only one point can have <b>cameraTarget</b> key!
* // You will receive the
*
* // Player.getPoints: duplicate cameraTarget
*
* // console warning if two or more points have <b>cameraTarget</b> key.
* // Then only last point with <b>cameraTarget</b> key will be have an effect.
* [cameraTarget]: {
*
* camera: [camera]{@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera},
*
* //rotation camera around point specified by an axis and an angle. Default is undefined - no rotation
* [rotation]: {
*
* [angle]: number|function|array. Angle of rotation in radians.
* number. Example: Math.PI / 2 rotate to 90 degrees.
* function. Example: new Function( 't', 'return 5*t' ).
* array. Example 1: [0, Math.PI]
* 0 is angle for t = min is start time of the playing.
* Math.PI is rotate to 180 degrees for t = max is time of the stopping of the playing.
* If max time is infinity, then angle is for t = min.
* Example 2: [{ t: 0, v: 0 }, { t: 1, v: Math.PI / 2 }
* t is time,
* v is angle for current t.
*
* Default is 0
*
* [axis]: THREE.Vector3. Axis of rotattion. Example: new THREE.Vector3( 1, 0, 0 ) - rotate around x axis.
* Default is new THREE.Vector3( 0, 1, 0 );//Rotate around y axis
*
* },
*
* [distanceToCamera]: <b>THREE.Vector3</b>. Distance from point to camera.
* You can set the distance to the camera depending on the time.
* Example 1: new THREE.Vector3( 0, 0, new Function( 't', 'return 2+t' ) )
* Example 2: new THREE.Vector3( 0, 0, [{ t: 0, v: 5 }, { t: 1, v: 2 }, { t: 10, v: 2 }, { t: 11, v: 5 }] )
* Default is camera.position.
* [orbitControls]: [OrbitControls]{@link https://threejs.org/docs/index.html#examples/en/controls/OrbitControls}. Change the <b>OrbitControl</b> setting during playing.
* [orbitControlsGui]: <a href="../../OrbitControls/jsdoc/index.html" target="_blank">OrbitControlsGui</a> instance;
*
* },
* //Use controls on the web page for display and edit of the point values.
* [controllers]: {
*
* pointName: HTML element or id of element for point name
*
* //axisName is "x" or "y" or "z" or "w"
* [axisName]: {
*
* //Function text controller
* func: {
*
* controller: HTMLElement - <b>input</b> element of function text
* or string - id of the <b>input</b> element. Default id is "[axisName]Func". Example: "xFunc".
* elName: HTMLElement - <b>span</b> element of the function name
* or string - id of the <b>span</b> element. Default id is "[axisName]FuncName". Example: "xFuncName".
* or false - name element is not exists.
*
* }
*
* //Point position controller
* position: {
*
* controller: HTMLElement - <b>input</b> element of the point position
* or string - id of the <b>input</b> element. Default id is "[axisName]Position". Example: "xPosition".
* elName: HTMLElement - <b>span</b> element of the point position name
* or string - id of the <b>span</b> element. Default id is "[axisName]PositionName". Example: "xPositionName".
* or false - name element is not exists.
*
* }
*
* //World point position controller.
* worldPosition: {
*
* controller: HTMLElement - <b>input</b> element of the world point position
* or string - id of the <b>input</b> element. Default id is "[axisName]WorldPosition". Example: "xWorldPosition".
* elName: HTMLElement - <b>span</b> element of the world point position name
* or string - id of the <b>span</b> element. Default id is "[axisName]WorldPositionName". Example: "xWorldPositionName".
* or false - name element is not exists.
*
* }
*
* }
*
* },
*
* }
* or
* object: {
* x: x axis. Defauilt is 0.
* y: y axis. Defauilt is 0.
* z: z axis. Defauilt is 0.
* w: w axis. Defauilt is 0.
* }
*
* array: [
* 0: x axis. Defauilt is 0.
* 1: y axis. Defauilt is 0.
* 2: z axis. Defauilt is 0.
* 3: w axis. Defauilt is 0.
* ]
* </pre>
* @param {object} [optionsPoints] followed optionsPoints is available
* @param {THREE.Group} [optionsPoints.group] {@link https://threejs.org/docs/index.html#api/en/objects/Group|Group}
* or {@link https://threejs.org/docs/index.html#api/en/scenes/Scene|Scene}.
* Use only if you want trace lines during playing. See <b>trace</b> of the <b>arrayFuncs</b> param above.
* @param {number} [optionsPoints.t=0] first parameter of the <b>arrayFuncs</b> item function. Start time of animation.
* @param {object} [optionsPoints.options] the following options are available
* @param {number} [optionsPoints.options.a=1] multiplier. Second parameter of the <b>arrayFuncs</b> item function.
* @param {number} [optionsPoints.options.b=0] addendum. Third parameter of the <b>arrayFuncs</b> item function.
* @param {object} [optionsPoints.options.playerOptions] See <b>settings.options.playerOptions</b> parameter of the <a href="../../player/jsdoc/module-Player-Player.html" target="_blank">Player</a> class.
* @returns array of <b>THREE.Vector4</b> points.
*/
Player.getPoints = function ( arrayFuncs, optionsPoints ) {
assign();
if ( !Array.isArray( arrayFuncs ) ) arrayFuncs = [ arrayFuncs ];
optionsPoints = optionsPoints || {};
if ( optionsPoints.t === undefined ) optionsPoints.t = optionsPoints.options && optionsPoints.options.player ? optionsPoints.options.player.getSettings().options.playerOptions.min : 0;
const options = optionsPoints.options || new Options(),
optionsDefault = new Options( { palette: options.palette } );
options.setW( optionsDefault );
const wDefault = optionsDefault.scales.w.max;//new THREE.Vector4().w;//1;//цвет точки по умолчанию равен цвету палитры для максимального значения value,
//которе по умолчанияю равно 1 и определяется в Options class.
//Палитра по умолчанию ColorPicker.paletteIndexes.BGYW
//у которой цвет максимального значения value белый.
for ( var i = 0; i < arrayFuncs.length; i++ ) {
var item = arrayFuncs[i];
if ( Array.isArray( item ) )
arrayFuncs[i] = new THREE.Vector4(
item[0] === undefined ? 0 : item[0],
item[1] === undefined ? 0 : item[1],
item[2] === undefined ? 0 : item[2],
item[3] === undefined ? wDefault : item[3]
);
else if (
( typeof item === "object" )
&& ( item instanceof THREE.Vector2 === false )
&& ( item instanceof THREE.Vector3 === false )
&& ( item instanceof THREE.Vector4 === false )
) {
if ( ( item.vector === undefined ) )
arrayFuncs[i] = new THREE.Vector4(
item.x === undefined ? 0 : item.x,
item.y === undefined ? 0 : item.y,
item.z === undefined ? 0 : item.z,
item.w === undefined ? 0 : item.w
);
else if (
( item.vector instanceof THREE.Vector2 === true )
|| ( item.vector instanceof THREE.Vector3 === true )
|| ( item.vector instanceof THREE.Vector4 === true )
) {
if ( item.vector instanceof THREE.Vector2 === true )
arrayFuncs[i].vector = new THREE.Vector3(
item.vector.x === undefined ? 0 : item.vector.x,
item.vector.y === undefined ? 0 : item.vector.y,
item.vector.z === undefined ? 0 : item.vector.z,
);
} else {
if ( item.vector.length === 4 )
arrayFuncs[i].vector = new THREE.Vector4(
item.vector[0] === undefined ? 0 : item.vector[0],
item.vector[1] === undefined ? 0 : item.vector[1],
item.vector[2] === undefined ? 0 : item.vector[2],
item.vector[3] === undefined ? 0 : item.vector[3]
);
else if ( item.vector.length === 3 )
arrayFuncs[i].vector = new THREE.Vector3(
item.vector[0] === undefined ? 0 : item.vector[0],
item.vector[1] === undefined ? 0 : item.vector[1],
item.vector[2] === undefined ? 0 : item.vector[2],
);
else if ( item.vector.length < 3 )
arrayFuncs[i].vector = new THREE.Vector4(
item.vector[0] === undefined ? 0 : item.vector[0],
item.vector[1] === undefined ? 0 : item.vector[1],
);
else console.error( 'Player.getPoints(...) falied! item.vector.length = ' + item.vector.length );
}
}
};
const points = [];
for ( var i = 0; i < arrayFuncs.length; i++ ) {
var funcs = arrayFuncs[i];
function getAxis( axisName ) {
if ( typeof funcs === "number" )
funcs = new THREE.Vector4( funcs, 0, 0, 0 );
if ( ( funcs instanceof THREE.Vector2 ) || ( funcs instanceof THREE.Vector3 ) || ( funcs instanceof THREE.Vector4 ) ) {
const value = Player.execFunc( funcs, axisName, optionsPoints.t, options );
return value;
}
if ( funcs.vector === undefined ) {
console.error( 'Player.getAxis().getPoints(): funcs.vector = ' + funcs.vector );
return;
}
if ( funcs.name !== undefined )
funcs.vector.name = funcs.name;
if ( funcs.trace ) funcs.vector.trace = funcs.trace;
if ( funcs.controllers ) funcs.vector.controllers = funcs.controllers;
if ( funcs.cameraTarget ) {
funcs.vector.cameraTarget = funcs.cameraTarget;
delete funcs.cameraTarget;
}
arrayFuncs[i] = funcs.vector;
funcs = funcs.vector;
return Player.execFunc( funcs, axisName, optionsPoints.t, options );
}
const point = funcs.vector instanceof THREE.Vector3 === true ?
new THREE.Vector3( getAxis( 'x' ), getAxis( 'y' ), getAxis( 'z' ) ) :
new THREE.Vector4( getAxis( 'x' ), getAxis( 'y' ), getAxis( 'z' ), getAxis( 'w' ) );
if ( funcs.cameraTarget ) {
funcs.cameraTarget.bodefault = false;
if ( funcs.cameraTarget.boLook === undefined ) funcs.cameraTarget.boLook = true;
options.playerOptions.cameraTarget.init( funcs.cameraTarget, options );
}
points.push( point );
}
return points;
}
var boColorWarning = true;
/** @namespace
* @description Get array of mesh colors.
* @param {THREE.Vector4|THREE.Vector3|THREE.Vector2|object|array} arrayFuncs points.geometry.attributes.position array
* <pre>
* THREE.Vector4: 4D point.
* THREE.Vector3: 3D point. w = 1. Default is white color
* THREE.Vector2: 2D point. w = 1, z = 0. Default is white color
* Vector's x, y, z, w is position of the point.
* Can be as:
* float - position of the point.
* [float] - array of positions of the point.
* [Function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function} - position of the point is function of the t.
* Example: new Function( 't', 'a', 'b', 'return Math.sin(t*a*2*Math.PI)*0.5+b' )
*
* Vector.w is index of the [palette]{@link https://github.com/anhr/commonNodeJS/tree/master/colorpicker}.
* Default range of the Vector.w is from 0 to 1. You can change range by use an object:
* {
* func: Vector.w
* max: new max value of tne Vector.w
* min: new min value of tne Vector.w
* }
* Example:
* {
*
* func: new Function( 't', 'return 1-2*t' ),
* min: -1,
* max: 1,
*
* }
* Vector.w can be as THREE.Color. Example: new THREE.Color( "rgb(255, 127, 0)" )
*
* object: {
* vector: THREE.Vector4|THREE.Vector3|THREE.Vector2 - point position
* [name]: point name. Default is undefined.
* [trace]: true - displays the trace of the point movement. Default is undefined.
* }
* or
* object: {
* x: x axis. Defauilt is 0.
* y: y axis. Defauilt is 0.
* z: z axis. Defauilt is 0.
* w: w axis. Defauilt is 0.
* }
*
* array: [
* 0: x axis. Defauilt is 0.
* 1: y axis. Defauilt is 0.
* 2: z axis. Defauilt is 0.
* 3: w axis. Defauilt is 0.
* ]
* </pre>
* @param {object} [optionsColor] the following options are available:
* @param {object} [optionsColor.options] See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* @param {THREE.BufferAttribute} [optionsColor.positions] geometry.attributes.position of the new mesh.
* @param {array} [optionsColor.colors=[]] array for mesh colors.
* @param {array} [optionsColor.opacity] array of opacities of each geometry position. Each item of array is float value in the range of 0.0 - 1.0 indicating how transparent the material is. A value of 0.0 indicates fully transparent, 1.0 is fully opaque.
* @returns array of mesh colors.
*/
Player.getColors = function ( arrayFuncs, optionsColor ) {
assign();
if ( !Array.isArray( arrayFuncs ) ) arrayFuncs = [ arrayFuncs ];
optionsColor = optionsColor || {};
optionsColor.options = optionsColor.options || {};
if (
( optionsColor.positions !== undefined ) &&
Array.isArray( arrayFuncs ) &&
( arrayFuncs.length !== optionsColor.positions.count )
) {
console.error( 'getColors failed! arrayFuncs.length: ' + arrayFuncs.length + ' != positions.count: ' + optionsColor.positions.count );
return optionsColor.colors;
}
//не надо убирать const length. Иначе переполнится память
const length = Array.isArray( arrayFuncs ) ? arrayFuncs.length : optionsColor.positions.count;
optionsColor.colors = optionsColor.colors || [];
const colors = [];
if ( !optionsColor.options.palette )
optionsColor.options.setPalette();
for ( var i = 0; i < length; i++ ) {
const iColor = 3 * i;
if (iColor >= optionsColor.colors.length) {
const funcs = Array.isArray( arrayFuncs ) ? arrayFuncs[i] : undefined;
var vector;
if (
( funcs instanceof THREE.Vector4 ) ||//w of the funcs is color of the point
( optionsColor.positions && ( optionsColor.positions.itemSize === 4 ) )//w position of the positions is color of the point
) {
let min, max;
var w = funcs.w;
if ( funcs.w instanceof Object && funcs.w.func ) {
if ( funcs.w.max ) max = funcs.w.max;
if ( funcs.w.min ) min = funcs.w.min;
w = funcs.w.func;
} else {
optionsColor.options.setW();
min = optionsColor.options.scales.w.min; max = optionsColor.options.scales.w.max;
}
if ( w instanceof Function && !optionsColor.options.player && boColorWarning ) {
boColorWarning = false;
}
const t = optionsColor.options.playerOptions ? optionsColor.options.playerOptions.min : 0;
var color = optionsColor.options.palette.toColor(
funcs === undefined ?
new THREE.Vector4().fromBufferAttribute( optionsColor.positions, i ).w :
w instanceof Function ?
w( t ) :
typeof w === "string" ?
Player.execFunc( funcs, 'w', t, optionsColor.options ) :
w === undefined ? new THREE.Vector4().w : w,
min, max );
colors.push( color.r, color.g, color.b );
} else if ( optionsColor.colors instanceof THREE.Float32BufferAttribute )
vector = new THREE.Vector3( 1, 1, 1 );
else if (optionsColor.color != undefined) {
const color = new THREE.Color(optionsColor.color);
colors.push( color.r, color.g, color.b );//white
} else colors.push( 1, 1, 1 );//white
}
else colors.push( optionsColor.colors[iColor], optionsColor.colors[iColor + 1], optionsColor.colors[iColor + 2] );
//opacity
if ( optionsColor.opacity instanceof Array )
colors.push( i < optionsColor.opacity.length ? optionsColor.opacity[i] : 1 );
else colors.push( 1 );
}
optionsColor.colors = colors;
return optionsColor.colors;
}
/** @class */
Player.traceLine = class traceLine
{
/**
* trace line of moving of the point during playing
* @param {object} options the following options are available
* @param {object} options.player See <a href="../../player/jsdoc/module-Player.html" target="_blank">Player</a>.
*/
constructor( options ) {
var line;
const arrayLines = [];//сюда добавляются линии когда max: Infinity,
assign();
if ( !options.player ) {
return;
}
/**
* Is trace line visible?
* @returns true - trace line is visible.
* <p>false - trace line is not visible.</p>
*/
this.isVisible = function () {
if ( !options.player )
return false;//не запущен Player(...)
if ( line ) return line.visible;
if ( arrayLines.length === 0 ) return false;
//сюда попадает когда t max is Infinity ( options.playerOptions.max === null ) и когда пользователь выбрал точку в guiSelectPoint у которой установлена трассировка
return arrayLines[0].visible;
}
/**
* Show or hide trace line.
* @param {boolean} visible true - show trace line.
* <p>false - hide trace line.</p>
*/
this.visible = function ( visible ) {
if ( !options.player )
return false;//не запущен Player(...)
if ( line ) {
line.visible = visible;
return;
}
//сюда попадает когда t max is Infinity (options.playerOptions.max === null) и когда пользователь в выбранной в guiSelectPoint точке изменил галочку трассировки
arrayLines.forEach( function ( line ) {
line.visible = visible;
} );
}
/**
* add point into trace line.
* @param {THREE.Mesh} mesh. See [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} for tracing.
* @param {number} index of the point for tracing.
* @param {THREE.Color} color. Line color. See [Color]{@link https://threejs.org/docs/index.html#api/en/math/Color}.
*/
this.addPoint = function ( mesh, index, color ) {
const attributesPosition = mesh.geometry.attributes.position;
var point = attributesPosition.itemSize >= 4 ? new THREE.Vector4( 0, 0, 0, 0 ) : new THREE.Vector3();
point.fromArray( attributesPosition.array, index * attributesPosition.itemSize );
//нельзя ставить const потому что привыполнении npm run build будет ошибка
// (babel plugin) SyntaxError: D:/My documents/MyProjects/webgl/three.js/GitHub/commonNodeJS/master/player/player.js: "sceneIndex" is read-only
var sceneIndex = options.player ? options.player.getSelectSceneIndex() : 0;
if ( options.playerOptions.max === null ) {
//Infinity play
sceneIndex = Math.abs( sceneIndex );
if ( sceneIndex < ( arrayLines.length - 1 ) ) {
while ( sceneIndex < ( arrayLines.length - 1 ) ) {
mesh.remove( arrayLines[arrayLines.length - 1] );
arrayLines.pop();
}
return;
}
// geometry
const geometry = new THREE.BufferGeometry(), MAX_POINTS = 2;
// attributes
const positions = new Float32Array( MAX_POINTS * 3 ); // 3 coordinates per point
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
const colors = new Float32Array( MAX_POINTS * 3 ); // 3 coordinates per point
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
const line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { vertexColors: true } ) );
// group.add( line );
mesh.add( line );
//на случай когда пользователь изменил флажок трассировки
//первая линия arrayLines[0] всегда имеет visible = true потому что сюда попадат только если установлена трассировка по умолчанию
//или пользователь установил флаг трассировки
if ( arrayLines[0] ) line.visible = arrayLines[0].visible;
//point position
point = new THREE.Vector3().copy( point );
const itemSize = line.geometry.attributes.position.itemSize;
point.toArray( line.geometry.attributes.position.array, 1 * itemSize );
const point0 = arrayLines.length === 0 ? point :
new THREE.Vector3().fromArray( arrayLines[arrayLines.length - 1].geometry.attributes.position.array, 1 * itemSize );
point0.toArray( line.geometry.attributes.position.array, 0 * itemSize );
line.geometry.attributes.position.needsUpdate = true;
//point color
if ( color === undefined )
color = new THREE.Color( 1, 1, 1 );//White
Player.setColorAttribute( line.geometry.attributes, 0, arrayLines.length === 0 ? color :
new THREE.Color().fromArray( arrayLines[arrayLines.length - 1].geometry.attributes.color.array, 1 * itemSize ) );
Player.setColorAttribute( line.geometry.attributes, 1, color );
arrayLines.push( line );
return;
}
if ( line === undefined ) {
// geometry
const geometry = new THREE.BufferGeometry();
//Thanks to https://stackoverflow.com/questions/31399856/drawing-a-line-with-three-js-dynamically/31411794#31411794
var MAX_POINTS;
if ( options.playerOptions.max !== null ) {
if ( options.playerOptions && options.playerOptions.marks )
MAX_POINTS = options.playerOptions.marks;
else if ( options.player && options.player.marks )
MAX_POINTS = options.player.marks;
else {
console.error( 'Player.traceLine: MAX_POINTS = ' + MAX_POINTS + '. Create Player first or remove all trace = true from all items of the arrayFuncs' );
return;
}
} else MAX_POINTS = sceneIndex + 1;
// attributes
const positions = new Float32Array( MAX_POINTS * 3 ); // 3 coordinates per point
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
const colors = new Float32Array( MAX_POINTS * 3 ); // 3 coordinates per point
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
// draw range
geometry.setDrawRange( sceneIndex, sceneIndex );
line = new THREE.Line( geometry, new THREE.LineBasicMaterial( {
//неудачная попытка исправить frustumPoints после перехода на THREE.REVISION = "145dev"
vertexColors: true,
//THREE.Material: 'vertexColors' parameter is undefined.
//vertexColors: THREE.VertexColors
} ) );
line.visible = true;
mesh.add( line );
}
//Если не удалять boundingSphere
//и если двигается камера от проигрывания или ее перемещает пользователь
//то в некоторых случаях линию не будет видно даже если она не выходит из поля видимости
//потому что она выходит за рамки frustupoints
if ( line.geometry ) {//scene do not have geometry
delete line.geometry.boundingSphere;
line.geometry.boundingSphere = null;
}
//point position
point = new THREE.Vector3().copy( point );
point.toArray( line.geometry.attributes.position.array, sceneIndex * line.geometry.attributes.position.itemSize );
line.geometry.attributes.position.needsUpdate = true;
//point color
if ( color === undefined )
color = new THREE.Color( 1, 1, 1 );//White
Player.setColorAttribute( line.geometry.attributes, sceneIndex, color );
//set draw range
var start = line.geometry.drawRange.start, count = sceneIndex + 1 - start;
if ( start > sceneIndex ) {
var stop = start + line.geometry.drawRange.count;
start = sceneIndex;
count = stop - start;
}
line.geometry.setDrawRange( start, count );
}
/**
* Remove trace line.
*/
this.remove = function () {
if ( line === undefined )
return;
line.geometry.dispose();
line.material.dispose();
line.parent.remove( line );
// group.remove( line );
}
}
}
/** @namespace
* @description get item size of the attribute of the mesh geometry
* @param {array} arrayFuncs points.geometry.attributes.position array.
* See arrayFuncs parametr of the <a href="../../player/jsdoc/module-Player-Player.getPoints.html" target="_blank">Player.getPoints(...)</a> for details.
*/
Player.getItemSize = function ( arrayFuncs ) {
assign();
for ( var i = 0; i < arrayFuncs.length; i++ ) {
var func = arrayFuncs[i];
if ( func instanceof THREE.Vector4 )
return 4;
}
return 3;
}
/** @namespace
* @description Select a scene for playing
* @param {THREE.Group} group [THREE.Group]{@link https://threejs.org/docs/index.html#api/en/objects/Group}
* @param {Object} [settings={}] the following settings are available.
* @param {number} [settings.t=0] time
* @param {number} [settings.index] index of the time.
* @param {Object} [settings.options={}] See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
*/
Player.selectPlayScene = function ( group, settings = {} ) {
const t = settings.t !== undefined ? settings.t : 0,
index = settings.index !== undefined ? settings.index : undefined,
options = settings.options || new Options();
group.userData.index = index;
group.userData.t = t;
if (typeof options.player === "object") options.player.endSelect = () => {
Player.selectMeshPlayScene( group, { t: t, options: options } );
function selectMeshPlayScene( group ) {
group.children.forEach( function ( mesh ) {
if ( mesh instanceof THREE.Group ) selectMeshPlayScene( mesh );
else Player.selectMeshPlayScene( mesh, { t: t, options: options } );
} );
}
selectMeshPlayScene( group );
options.playerOptions.cameraTarget.setCameraTarget( options );
const cameraTarget = options.playerOptions.cameraTarget.get();
//если index === undefined значит пользователь нажал кнопку 'Default' для восстановления положения камеры.
//Значит надо вызвать camera.userData.default.setDefault()
//
//если index !== undefined значит проигрыватель вызывает очередной кадр и не нужно перемещать камеру в исходное положение
//приуслвии что не выбрана ни одна точка как cameraTarget
if ( cameraTarget && cameraTarget.setCameraPosition ) cameraTarget.setCameraPosition( index === undefined );
}
if ( !group.userData.endSelect && (typeof options.player === "object")) options.player.endSelect();
}
var THREE;
/* * @namespace
* @description assign some THREE methods
*/
function assign( ) {
if ( !three.isThree() ) {
console.warn( 'Player: can not assign. Set THREE first.' )
return;
}
THREE = three.THREE;
Object.assign( THREE.BufferGeometry.prototype, {
setFromPoints: function ( points, itemSize ) {
itemSize = itemSize || 3;
var position = [];
for ( var i = 0, l = points.length; i < l; i++ ) {
var point = points[i];
position.push( point.x, point.y, point.z || 0 );
if ( itemSize >= 4 )
position.push( point.w || 0 );
}
this.setAttribute( 'position', new THREE.Float32BufferAttribute( position, itemSize ) );
return this;
},
} );
//three.js\dev\src\math\Vector4.js
Object.assign( THREE.Vector4.prototype, {
multiply: function ( v ) {
this.x *= v.x;
this.y *= v.y;
this.z *= v.z;
if ( v.w !== undefined )
this.w *= v.w;
return this;
},
} );
//three.js\dev\src\math\Vector4.js
Object.assign( THREE.Vector4.prototype, {
add: function ( v, w ) {
if ( w !== undefined ) {
console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
return this.addVectors( v, w );
}
this.x += v.x;
this.y += v.y;
this.z += v.z;
if ( v.w !== undefined )
this.w += v.w;
return this;
},
} );
//three.js\dev\src\objects\Points.js
Object.assign( THREE.Points.prototype, {
raycast: function ( raycaster, intersects ) {
const _inverseMatrix = new THREE.Matrix4();
const _ray = new THREE.Ray();
const _sphere = new THREE.Sphere();
const _position = new THREE.Vector3();
function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) {
const rayPointDistanceSq = _ray.distanceSqToPoint( point );
if ( rayPointDistanceSq < localThresholdSq ) {
const intersectPoint = new THREE.Vector3();
_ray.closestPointToPoint( point, intersectPoint );
intersectPoint.applyMatrix4( matrixWorld );
const distance = raycaster.ray.origin.distanceTo( intersectPoint );
if ( distance < raycaster.near || distance > raycaster.far ) return;
intersects.push( {
distance: distance,
distanceToRay: Math.sqrt( rayPointDistanceSq ),
point: intersectPoint,
index: index,
face: null,
object: object
} );
}
}
const geometry = this.geometry;
const matrixWorld = this.matrixWorld;
const threshold = raycaster.params.Points.threshold;
// Checking boundingSphere distance to ray
if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
_sphere.copy( geometry.boundingSphere );
_sphere.applyMatrix4( matrixWorld );
_sphere.radius += threshold;
if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
//
_inverseMatrix.copy( matrixWorld ).invert();
_ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
const localThresholdSq = localThreshold * localThreshold;
if ( geometry.isBufferGeometry ) {
const index = geometry.index;
const attributes = geometry.attributes;
const positions = attributes.position.array;
const itemSize = attributes.position.itemSize;
if ( index !== null ) {
const indices = index.array;
for ( let i = 0, il = indices.length; i < il; i++ ) {
const a = indices[i];
_position.fromArray( positions, a * itemSize );
testPoint( _position, a, localThresholdSq, matrixWorld, raycaster, intersects, this );
}
} else {
for ( let i = 0, l = positions.length / itemSize; i < l; i++ ) {
_position.fromArray( positions, i * itemSize );
testPoint( _position, i, localThresholdSq, matrixWorld, raycaster, intersects, this );
}
}
} else {
const vertices = geometry.vertices;
for ( let i = 0, l = vertices.length; i < l; i++ ) {
testPoint( vertices[i], i, localThresholdSq, matrixWorld, raycaster, intersects, this );
}
}
},
} );
}
//assign();
/**
* @namespace
* @description assign some THREE methods
* */
Player.assign = function () { assign(); }
export default Player;