/**
* @module GuiSelectPoint
*
* @description A dat.gui based graphical user interface for select a point from the mesh.
*
* @see {@link https://github.com/anhr/dat.gui|dat.gui}, {@link https://threejs.org/docs/index.html#api/en/objects/Mesh|three.js mesh}.
*
* @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 ScaleController from 'https://raw.githack.com/anhr/commonNodeJS/master/ScaleController.js';
import PositionController from '../PositionController.js';
//import PositionController from 'https://raw.githack.com/anhr/commonNodeJS/master/PositionController.js';
import ColorPicker from '../colorpicker/colorpicker.js';
//import ColorPicker from 'https://raw.githack.com/anhr/commonNodeJS/master/colorpicker/colorpicker.js';
//import Player from '../player/build/player.module.js';
//import Player from '../player/build/player.module.min.js';
import Player from '../player/player.js';
//import Player from 'https://raw.githack.com/anhr/commonNodeJS/master/player/player.js';
//import Player from 'https://raw.githack.com/anhr/commonNodeJS/master/player/build/player.module.min.js';
import functionsFolder from '../functionsFolder.js';
import { SpriteText } from '../SpriteText/SpriteText.js'
import {
getObjectPosition,
getObjectLocalPosition,
} from '../getPosition.js';
import three from '../three.js'
const none = 'none', block = 'block';
class GuiSelectPoint {
/**
* @class A dat.gui based graphical user interface for select a point from the mesh.
* @param {Options} options See the <b>options</b> parameter of the <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* @param {GUI} [options.dat.dat] [dat.GUI()]{@link https://github.com/dataarts/dat.gui}.
* @param {boolean|Object} [options.dat.guiSelectPoint] false - do not displays <b>GuiSelectPoint</b>.
* @param {Function} [options.dat.guiSelectPoint.point] Callback function to create custom controllers for each point of selected mesh with custom controllers.
* <pre>
* parameter <b>options</b> See <b>options</b> parameter of <a href="../../myThree/jsdoc/module-MyThree-MyThree.html" target="_blank">MyThree</a> class.
* parameter <b>dat</b> [dat.GUI()]{@link https://github.com/dataarts/dat.gui}.
* parameter <b>fParent</b> parent folder.
* example <b>point: function ( options, dat, fMesh ) { return new FermatSpiral.gui( options, dat, fMesh ); },</b>
* </pre>
* @param {boolean} [options.dat.guiSelectPoint.boDisplayVerticeID] true - display on the scene the point ID near to the point.
* @param {AxesHelper} [options.axesHelper] An axis object to visualize axes.
* See <a href="../../AxesHelper/jsdoc/index.html" target="_blank">AxesHelper</a>.
* @param {Function} [options.getLanguageCode=language code of your browser] 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 "Syntax" paragraph of RFC 4646 {@link https://tools.ietf.org/html/rfc4646#section-2.1|rfc4646 2.1 Syntax} for details.
* You can import { getLanguageCode } from '../../commonNodeJS/master/lang.js';
* </pre>
* @param {object} [options.lang] Object with localized language values
* @param {object} [guiParams={}] Followed parameters is allowed.
* @param {object} [guiParams.cameraTarget] camera looking at selected point during playing. See the <b>cameraTarget</b> parameter of the <a href="../../player/jsdoc/module-Player-Player.cameraTarget.html#init" target="_blank">Player.cameraTarget.init(...)</a> function for details.
* @param {THREE.PerspectiveCamera} guiParams.cameraTarget.camera [PerspectiveCamera]{@link https://threejs.org/docs/index.html#api/en/cameras/PerspectiveCamera}.
* @param {Function} [guiParams.pointControls] pointControls( fPoint, dislayEl, getMesh ) Adds the trace "Display the trace of the point movement" control checkbox into gui.
* <pre>
* fPoint - parent folder for new control.
* dislayEl( conrtol, true or false ) - function for dislay or hide of the control.
* getMesh() returns the mesh, selected in the GuiSelectPoint.
* Default is undefined.
* </pre>
* @param {Function} [guiParams.pointsControls] pointsControls( fPoints, dislayEl, getMesh ) Adds the trace "Display the trace of the movement of all points of the mesh." control checkbox into gui.
* <pre>
* fPoints - parent folder for new control.
* dislayEl( conrtol, true or false ) - function for dislay or hide of the control.
* getMesh() returns the mesh, selected in the GuiSelectPoint.
* Default is undefined.
* </pre>
* @param {Function} [guiParams.setIntersection] setIntersection( intersection ) sets the intersection value of myThreejs. Default is undefined
* @example
import GuiSelectPoint from 'https://raw.githack.com/anhr/commonNodeJS/master/guiSelectPoint/guiSelectPoint.js';
new GuiSelectPoint( options );
options.guiSelectPoint.add();
options.guiSelectPoint.addMesh( points );
*/
constructor( options, guiParams = {} ) {
const guiSelectPoint = this, THREE = three.THREE, folders = {};
if ( !options.boOptions ) {
console.error( 'GuiSelectPoint: call options = new Options( options ) first' );
return;
}
if ( ( options.dat.guiSelectPoint === false ) || !options.dat.gui ) {
//заменяем все функции на пустышки
this.add = function ( gui ) { };
this.addMesh = function ( points ) { };
this.select = function ( intersect ) { };
return;
}
const dat = three.dat;//options.dat.dat;
//Player changes the guiSelectPoint control's values during playing
options.guiSelectPoint = guiSelectPoint;
var cFrustumPoints;
//Localization
const getLanguageCode = options.getLanguageCode;
const lang = {
meshs: 'Meshes',
notSelected: 'Not selected',
select: 'Select',
position: 'Position',
rotation: 'Rotation',
points: 'Points',
displayVerticeID: 'Point ID',
displayVerticeIDTitle: 'Display on the scene the point ID near to the point',
cameraTarget: 'Look',
cameraTargetTitle: 'Choose this point the camera is looking at.',
point: 'Point Local Position',
pointTitle: 'The position attribute of the selected point',
trace: 'Trace',
traceTitle: 'Display the trace of the point movement.',
traceAllTitle: 'Display the trace of the movement of all points of the mesh.',
pointWorld: 'Point World Position',
pointWorldTitle: 'The position of the selected point after scaling, moving and rotation of the mesh',
mesh: 'Mesh',
scale: 'Scale',
color: 'Сolor',
opacity: 'Opacity',
opacityTitle: 'Float 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.',
defaultButton: 'Default',
defaultScaleTitle: 'Restore default 3d object scale.',
defaultPositionTitle: 'Restore default 3d object position.',
default3DObjectTitle: 'Restore default settings of all 3d objects.',
defaultRotationTitle: 'Restore default 3d object rotation.',
defaultLocalPositionTitle: 'Restore default local position.',
moveGroup: 'Move Scene',
};
const _languageCode = getLanguageCode();
switch ( _languageCode ) {
case 'ru'://Russian language
lang.meshs = '3D объекты';
lang.notSelected = 'Не выбран';
lang.select = 'Выбрать';
lang.position = 'Позиция';
lang.rotation = 'Вращение';
lang.points = 'Точки';
lang.displayVerticeID = 'Номера точек';
lang.displayVerticeIDTitle = 'На сцене возле каждой точки показать ее идентификатор';
lang.cameraTarget = 'Следить';
lang.cameraTargetTitle = 'Выберите эту точку, за которой следит камера.',
lang.point = 'Локальная позиция точки';
lang.pointTitle = 'Position attribute выбранной точки';
lang.trace = 'Трек';
lang.traceTitle = 'Показать трек перемещения точки.';
lang.traceAllTitle = 'Показать трек перемещения всех точек выбранного 3D объекта.';
lang.pointWorld = 'Абсолютная позиция точки';
lang.pointWorldTitle = 'Позиция выбранной точки после масштабирования, перемещения и вращения 3D объекта';
lang.mesh = '3D объект';
lang.scale = 'Масштаб';
lang.color = 'Цвет';
lang.opacity = 'Непрозрачность';
lang.opacityTitle = 'Число в диапазоне 0,0 - 1,0, указывающее, насколько прозрачен материал. Значение 0.0 означает полностью прозрачный, 1.0 - полностью непрозрачный.';
lang.defaultButton = 'Восстановить';
lang.defaultScaleTitle = 'Восстановить масштаб 3D объекта по умолчанию.';
lang.defaultPositionTitle = 'Восстановить позицию 3D объекта по умолчанию.';
lang.default3DObjectTitle = 'Восстановить настройки всех 3D объектов по умолчанию.';
lang.defaultRotationTitle = 'Восстановить поворот 3D объекта по умолчанию.';
lang.defaultLocalPositionTitle = 'Восстановить локальную позицию точки по умолчанию.';
break;
default://Custom language
}
var f3DObjects, fPoint, cRestoreDefaultLocalPosition, fPointWorld, fPoints, cMeshs, fMesh,// mesh,
intersection,
cScaleX, cScaleY, cScaleZ,
cPoints, selectedPointIndex = -1,
cX, cY, cZ, cW, cTrace, cTraceAll, cColor, cOpacity, cCameraTarget,
funcFolder,
boSetMesh = false,//Для предотвращения лишних вызовов exposePosition если выбрать точку и передвинуть камеру с помошью OrbitControls,
fRotation,
cCustom;//Custom point controllers
const _this = this, cPosition = new THREE.Vector3(), cRotations = new THREE.Vector3(), cWorld = new THREE.Vector3();
function displayPointControllers( display ) {
fPointWorld.domElement.style.display = display;
fPoint.domElement.style.display = display;
if ( cCameraTarget ) {
const mesh = getMesh();
cCameraTarget.domElement.parentElement.parentElement.style.display = mesh && mesh.userData.boFrustumPoints ?
'none' ://Камера не может следить за frustumPoints
display;
}
}
if ( options.frustumPoints )
cFrustumPoints = new options.frustumPoints.guiSelectPoint();
//сейчас exposePosition вызывается только один раз из this.setMesh
function getLiEl(controller) {
var el = controller.domElement;
while (el.tagName.toUpperCase() !== "LI") el = el.parentElement;
return el;
}
//dislay element
function dislayEl( controller, displayController ) {
if ( controller === undefined )
return;
if ( typeof displayController === "boolean" )
displayController = displayController ? 'block' : 'none';
else if ( displayController === undefined )
displayController = 'none';
else if ( typeof displayController !== "string" )
displayController = 'block';
getLiEl(controller).style.display = displayController;
}
function isDislayEl( controller ) {
if ( controller === undefined )
return;
return getLiEl(controller).style.display === none ? false : true;
}
//readOnly controller
const getInputEl = ( controller ) => { return controller ? controller.domElement.querySelector( 'input' ) : undefined; },
readOnlyEl = ( controller, boReadOnly ) => {
const element = getInputEl( controller );
if ( element ) element.readOnly = boReadOnly;
},
isReadOnlyEl = ( controller ) => {
const element = getInputEl( controller );
if ( element ) return element.readOnly;
},
isReadOnlyController = (controller) => {
if (controller.boSetValue) return true;
if (isReadOnlyEl(controller)) {
if (controller.getValue() !== controller.initialValue) {
if (controller.boSetValue === undefined) {
controller.boSetValue = true;
setValue(controller, controller.initialValue);
controller.boSetValue = undefined;
controller.initialValue = controller.getValue();//Эта строка нужна в случае когда новое зачения невозможно установиь точно таким же, как initialValue
//Иначе перепонится стек
}
}
return true;
}
return false;
}
function exposePosition( selectedPointIndex ) {
if ( selectedPointIndex === undefined )
selectedPointIndex = guiSelectPoint.getSelectedPointIndex();//Эта строка слишком медленно выполняется если число точек frustumPoints велико
//Поэтому selectedPointIndex беру из intersection.index индекс точки, над которй щелнул мышью
if ( selectedPointIndex === -1 )
return;
const mesh = cMeshs.__select.options[cMeshs.__select.options.selectedIndex].mesh,
position = getObjectPosition( mesh, selectedPointIndex );
if ( ( options.axesHelper !== undefined ) )
// && ( ( mesh.userData.isInfo === undefined ) || ( mesh.userData.isInfo() ) ) )
//если делать эту проверку, то будут неправильно отображаться пунктирные линии для frustumPoints точки
//когда в настройках frustumPoints не стоит галочка info
//когда в gui пользователь выбрал точку frustumPoints из списка '3D objects'(этот пункт будет недоступен когда я уберу frustumPoints из списка '3D objects' когда в настройках frustumPoints не стоит галочка info)
//и когда пользователь передвигает камеру с помощью orbitControls
if ( ( options.axesHelper !== false ) && ( options.axesHelper !== undefined ) )
options.axesHelper.exposePosition( { object: mesh, index: selectedPointIndex } );
if ( cWorld.x ) cWorld.x.setValue( position.x );
if ( cWorld.y ) cWorld.y.setValue( position.y );
if ( cWorld.z ) cWorld.z.setValue( position.z );
//Возможно не надо делать такую глубокую проверку. Не тестировал
if(
mesh.userData.player &&
mesh.userData.player.arrayFuncs
) {
const selectedPoint = mesh.userData.player.arrayFuncs[mesh.userData.myObject.guiPoints.seletedIndex(selectedPointIndex)];
if(
selectedPoint &&
selectedPoint.controllers
) {
const controllers = mesh.userData.player.arrayFuncs[selectedPointIndex].controllers;
if ( controllers.x && controllers.x.controller ) controllers.x.controller.value = position.x;
if ( controllers.y && controllers.y.controller ) controllers.y.controller.value = position.y;
if ( controllers.z && controllers.z.controller ) controllers.z.controller.value = position.z;
}
}
}
this.exposePosition = exposePosition;
function setValue( controller, v ) {
if ( !controller )
return;
const input = getInputEl( controller ),//controller.domElement.querySelector( 'input' ),
readOnly = input.readOnly;
input.readOnly = false;
controller.object[controller.property] = v;
if ( controller.__onChange )
controller.__onChange.call( controller, v );
controller.initialValue = v;
controller.updateDisplay();
input.readOnly = readOnly;
return controller;
}
var wLimitsDefault;
/**
* Sets local position controllers to read-only
* @param {boolean} boReadOnly true is read-only
*/
this.setReadOnlyPosition = function ( boReadOnly ) {
readOnlyEl( cX, boReadOnly );
readOnlyEl( cY, boReadOnly );
readOnlyEl( cZ, boReadOnly );
readOnlyEl( cW, boReadOnly );
}
function setPosition(intersectionSelected) {
const player = intersectionSelected.object.userData.player;
var boDisplayFuncFolder = 'none';
if (player && player.arrayFuncs) {
const mesh = getMesh();
funcFolder.setFunction(player.arrayFuncs[mesh.userData.myObject.guiPoints.seletedIndex(intersectionSelected.index)]);
boDisplayFuncFolder = 'block';
}
funcFolder.displayFolder(boDisplayFuncFolder);
/*Для установки cCameraTarget после выбора точки. Если это оставить то неправильно учтанавливается галочка cCameraTarget если:
1 устанвить cCameraTarget для выбранной точки
2 запустить плеер
3 уброать cCameraTarget
4 запустить плеер. Снова установиться cCameraTarget
*/
if (cCameraTarget) {
options.playerOptions.cameraTarget.changeTarget(intersectionSelected.object, intersectionSelected.index);
cCameraTarget.updateDisplay();
}
const positionLocal = _this.getObjectLocalPosition(intersectionSelected);
setValue(cX, positionLocal.x);
setValue(cY, positionLocal.y);
setValue(cZ, positionLocal.z);
const gui = intersectionSelected.object.userData.gui;
if (gui) intersectionSelected.index = gui.hyperSphere.searchNearestEdgeVerticeId(intersectionSelected.index, intersectionSelected);
const intersectionSelectedIndex = intersectionSelected.index;
if (intersectionSelected.object.userData.gui) {
const guiPoints = intersectionSelected.object.userData.myObject.guiPoints;
guiPoints.getVerticeId(intersectionSelectedIndex);
gui.setValues(intersectionSelectedIndex, guiPoints.timeAngles);
}
const position = _this.getObjectPosition(intersectionSelected.object, intersectionSelected.index);
setValue(cWorld.x, position.x);
setValue(cWorld.y, position.y);
setValue(cWorld.z, position.z);
var displayControllerW, displayControllerColor;//, displayControllerOpacity;
if (intersection.object.userData.player && (typeof intersection.object.userData.player.arrayFuncs === "function")) {
//Сюда попадает когда пользователь выбироает точку в frustumPoints
//console.error( 'arrayFuncs === "function" under constraction' );
}
const func = player && player.arrayFuncs ?
player.arrayFuncs.intersection ?
player.arrayFuncs.intersection(intersectionSelected.index) :
player.arrayFuncs[intersectionSelected.index] :
undefined,
attributes = intersectionSelected.object.geometry.attributes;
if (!attributes.color) {
displayControllerW = none;
displayControllerColor = none;
} else {
const setColorControl = (color) => {
const strColor = '#' + color.getHexString();
//Сначала надо установить initialValue потому что для FrustumPoints я устанвил readOnly для cColor.
//В этом случае я не могу отобразить цвет следующей точки FrustumPoints потому что в режиме readOnly
//при изменении цвета восстанвливается старый цвет из initialValue.
cColor.initialValue = strColor;
cColor.userData = { intersection: intersectionSelected, };
cColor.setValue(strColor);
}
if (attributes.position.itemSize < 4) {
if (
intersectionSelected.object.material.vertexColors === true ||
(intersectionSelected.object.material instanceof THREE.ShaderMaterial === true)
) {
displayControllerW = none;
displayControllerColor = block;
setColorControl( new THREE.Color().fromBufferAttribute(attributes.color, intersectionSelected.index) );
}
} else {
function isWObject() { return (typeof func.w === 'object') && (func.w instanceof THREE.Color === false); }
const mesh = getMesh(), verticeColor = mesh.userData.myObject ?
mesh.userData.myObject.verticeColor(intersectionSelectedIndex) :
undefined;
var color = (func === undefined) || (!attributes.color && !attributes.ca) ?
undefined :
Array.isArray(func.w) || (typeof func.w === "function") ?
Player.execFunc(func, 'w', options.time, options) :
isWObject() ?
Player.execFunc(func.w, 'func', options.time, options) :
typeof func.w === "string" ?
Player.execFunc(func, 'w', options.time, options) :
verticeColor != undefined ?
verticeColor :
func.w;
if (Array.isArray(color)) color = new THREE.Color(color[0], color[1], color[2]);
if (color instanceof THREE.Color) {
displayControllerW = none;
displayControllerColor = block;
//color
if (intersectionSelected.object.userData.player.arrayFuncs === undefined) {
displayControllerColor = none;
} else {
setColorControl(color);
cOpacity.userData = { intersection: intersectionSelected, };
}
} else {
if ( cW === undefined )
displayControllerW = none;
else {
if ( color === undefined )
displayControllerW = none;
else {
if ( options.scales.w.isColor != false ) {
if (!wLimitsDefault) {
wLimitsDefault = {
min: cW.__min,
max: cW.__max,
}
}
if (isWObject()) {
cW.min(func.w.min !== 'undefined' ? func.w.min : wLimitsDefault.min);
cW.max(func.w.max !== 'undefined' ? func.w.max : wLimitsDefault.max);
if ((cW.__min !== 'undefined') && (cW.__max !== 'undefined'))
cW.step((cW.__max - cW.__min) / 100)
} else {
cW.min(wLimitsDefault.min);
cW.max(wLimitsDefault.max);
}
}
setValue(cW, color);
displayControllerW = block;
}
}
displayControllerColor = none;
}
}
}
dislayEl( cW, displayControllerW );
dislayEl( cColor, displayControllerColor );
const mesh = getMesh(), boReadOnly = intersectionSelected.object.userData.boFrustumPoints === true ? true : mesh.userData.gui && mesh.userData.gui.isLocalPositionReadOnly ? true : false,
attributeColor = mesh.geometry.attributes.color,
boOpacity = attributeColor ?
(
( attributeColor.itemSize === 4 ) &&
(
( mesh.userData.myObject && mesh.userData.myObject.isOpacity ) ||
( mesh.material.transparent && mesh.material.vertexColors )
)
) :
false;
dislayEl( cOpacity, boOpacity ? block : none );
if ( boOpacity ) {
if ( attributeColor.itemSize != 4 ) console.error( 'GuiSelectPoint.setPosition: Invalid mesh.geometry.attributes.color.itemSize = ' + attributeColor.itemSize);
cOpacity.initialValue = attributeColor.array[intersectionSelected.index * attributeColor.itemSize + 3];
cOpacity.setValue( cOpacity.initialValue );
}
_this.setReadOnlyPosition(boReadOnly);
readOnlyEl( cColor, boReadOnly );
readOnlyEl( cOpacity, boReadOnly );
funcFolder.displayFolder( !boReadOnly );
}
/**
* Specify a maximum, minimum and step value for [NumberController]{@link https://github.com/dataarts/dat.gui/blob/master/API.md#NumberController}.
*
* @param {String} axis axis. Currently 'w' axis is available only.
* @param {object} scale The following <b>NumberController</b> properties are available:
* @param {object} [scale.min] Minimum allowed value.
* @param {object} [scale.max] Maximum allowed value.
* @param {object} [scale.step] Increment by which to change value.
*/
this.setAxisControl = function ( axis, scale ) {
switch( axis ) {
case 'w':
if ( scale.min != undefined ) cW.min(scale.min);
if ( scale.max != undefined ) cW.max(scale.max);
if ( scale.step != undefined ) cW.step(scale.step);
break;
default: console.error( 'GuiSelectPoint.setAxisControl. Invalid axis: ' + axis);
}
}
/**
* sets axis name of the controllers
* @param {String} axis axis. 'x' or 'y' or 'z'.
* @param {String} name new axis name
*/
this.setAxisName = function ( axis, name ) {
//position
cPosition[axis].name( name );
folders.position[axis].name = name;
//scale
const cScale = axis === 'x' ? cScaleX : axis === 'y' ? cScaleY : axis === 'z' ? cScaleZ : undefined;
cScale.name( name );
//rotation
const cRotation = cRotations[axis];
if ( cRotation.name ) cRotation.name( name );
}
/**Sets controllers to position, scale and rotation of the mesh. If AxesHelper is exist, expose the mesh to the axes. */
this.setMesh = function () {
boSetMesh = true;
setScaleControllers();
setPositionControllers();
setRotationControllers();
exposePosition();
boSetMesh = false;
}
/**
* Sets controllers to position, scale and rotation of the mesh. If AxesHelper is exist, expose the mesh to the axes.
* @param intersectionSelected [THREE.Raycaster.intersectObject]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject}
*/
this.setPosition = function ( intersectionSelected ) {
for ( var i = 0; i < cMeshs.__select.length; i++ ) {
var option = cMeshs.__select[i];
if ( option.selected && Object.is( option.mesh, intersectionSelected.object ) ) setPosition( intersectionSelected );
}
}
/**
* update the values of the controllers of the world position
*/
this.update = function ( boSetInitialValue = false ) {
const selectedItem = cMeshs.__select.options[cMeshs.__select.options.selectedIndex];
if ( !selectedItem ) return;
const mesh = selectedItem.mesh;
if ( !mesh ) return;
const index = this.getSelectedPointIndex();
if ( index === -1 ) return;
const position = getObjectPosition( mesh, index );
if( cWorld.x ) cWorld.x.setValue( position.x );
if( cWorld.y ) cWorld.y.setValue( position.y );
if( cWorld.z ) cWorld.z.setValue( position.z );
if( cW && ( position instanceof THREE.Vector4 )) cW.setValue( position.w );
const positionLocal = getObjectLocalPosition( mesh, index );
if ( boSetInitialValue ) {
if( cX ) cX.initialValue = positionLocal.x;
if( cY ) cY.initialValue = positionLocal.y;
if( cZ ) cZ.initialValue = positionLocal.z;
if( cW ) cW.initialValue = positionLocal.w;
}
if( cX ) cX.setValue( positionLocal.x );
if( cY ) cY.setValue( positionLocal.y );
if( cZ ) cZ.setValue( positionLocal.z );
funcFolder.update( mesh.userData.player.arrayFuncs[index] );
}
/**
* get index of the mesh in the cMeshs controller
* @param {THREE.Mesh} mesh [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}
* @returns index of selectred mesh.
*/
this.getMeshIndex = function ( mesh ) {
if ( mesh === undefined )
return mesh;
var index;// = intersectionSelected.object.userData.index;
for ( index = 0; index < cMeshs.__select.options.length; index++ ) {
var option = cMeshs.__select.options[index];
if ( Object.is( option.mesh, mesh ) )
return index;
}
//Сюда попадает когда Mesh проверяется с помощью THREE.Raycaster находится ли он под мышью, но этот Mesh не внесен в список GuiSelectPoint
}
/**
* Set a mesh in the mesh's list control
* @param {number} index index of the mesh in the mesh's list control
* @param {THREE.Mesh} mesh new [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}
*/
this.setIndexMesh = function ( index, mesh ) {
if ( index === undefined )
return;
cMeshs.__select.options[index].mesh = mesh;
this.selectPoint( -1 );
}
/**
* Selects a point in the points list control
* @param {number} index index of the point in the points list control
*/
this.selectPoint = function ( index ) {
cPoints.__onChange( index );
cPoints.__select[index + 1].selected = true;
}
/**
* Removes a mesh from the select point GUI
* @param {THREE.Mesh} mesh [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} for removing.
* @param {boolean} [boHideF3DObjects=true] true - hide the 'Meshes' folder if no any mesh exists in the meshs dropdown menu.
*/
this.removeMesh = function ( mesh, boHideF3DObjects = true ) {
const index = this.getMeshIndex( mesh ),
selectedIndex = cMeshs.__select.selectedIndex;
if ( index === undefined ) return;
cMeshs.__select.remove( index );
if ( selectedIndex === index ) {
cPoints.__onChange( -1 );
_this.removePoints();
cMeshs.__onChange( -1 );
}
if ( ( cMeshs.__select.options.length < 2 ) && boHideF3DObjects ) f3DObjects.domElement.style.display = 'none';
}
const arrayMeshs = [];//сюда попадают все mesh в случае, когда this.addMesh вызывается до вызова this.add
//тоесть когда GuiSelectPoint еще не добавлен в dat.gui
/**
* Adds new mesh into select point GUI
* @param {THREE.Mesh} mesh new [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh}.
*/
this.addMesh = function ( mesh ) {
if ( !mesh.parent ) {
console.error( 'GuiSelectPoint.addMesh: Add mesh into scene first.' );
return;
}
//mesh.userData.myObject ||= uncompatible with myThree.js → ./build/myThree.js, ./ build / myThree.module.js...
if (!mesh.userData.myObject) mesh.userData.myObject = {
verticeColor: () => {},
verticeOpacity: () => {},
};
//mesh.userData.myObject.guiPoints ||= uncompatible with myThree.js → ./build/myThree.js, ./ build / myThree.module.js...
if (!mesh.userData.myObject.guiPoints) mesh.userData.myObject.guiPoints = {
seletedIndex: (guiIndexStr) => { return guiIndexStr; },
setControllers: (index) => {},
getPositionId: (index) => { return index; },
getVerticeId: (index) => { return index; },
create: (fPoints, cPoints, cTrace, cTraceAll, count) => {
for ( var iPosition = mesh.geometry.drawRange.start; iPosition < count; iPosition++ ) {
const opt = document.createElement( 'option' ),
name = mesh.userData.player && mesh.userData.player.arrayFuncs ? mesh.userData.player.arrayFuncs[iPosition].name : '';
opt.innerHTML = iPosition + ( name === undefined ? '' : ' ' + name );
opt.setAttribute( 'value', iPosition );//Эта строка нужна в случае когда пользователь отменил выбор точки. Иначе при движении камеры будут появляться пунктирные линии, указвающие на несуществующую точку
cPoints.__select.appendChild( opt );
}
},
getValue: (cPoints) => { return cPoints.__select.selectedOptions[0].index - 1; },
onChangeAngle: (verticeId, angleId, angle) => { }
};
if ( !f3DObjects ) this.add();
f3DObjects.domElement.style.display = 'block';
if ( !cMeshs ) {
//Test for duplicate item
for ( var i = 0; i < arrayMeshs.length; i++ ) {
if ( arrayMeshs[i].uuid === mesh.uuid ) {
console.error( 'guiSelectPoint.addMesh: Duplicate mesh: ' + mesh.name );
return;
}
}
arrayMeshs.push( mesh );
return;
}
//Test for duplicate item
for ( var i = 0; i < cMeshs.__select.options.length; i++ ) {
var option = cMeshs.__select.options[i];
if ( mesh.userData.boFrustumPoints && ( option.mesh !== undefined ) && option.mesh.userData.boFrustumPoints )
return;//duplicate FrustumPoints. Сюда попадает когда пользователь меняет количество слоев или Y точек в FrustumPoints.
if (
( option.mesh !== undefined ) &&
( option.mesh.uuid === mesh.uuid )
) {
console.error( 'guiSelectPoint.addMesh(...): Duplicate mesh.' );
return;
}
}
const opt = document.createElement( 'option' );
opt.innerHTML = cMeshs.__select.length + ' ' + ( mesh.name === '' ? mesh.constructor.name : mesh.name );
opt.mesh = mesh;
mesh.userData.default = mesh.userData.default || {
scale: new THREE.Vector3().copy( mesh.scale ),
position: mesh.position instanceof THREE.Vector3 ?
new THREE.Vector3().copy( mesh.position ) :
mesh.position instanceof THREE.Vector4 ? new THREE.Vector4().copy( mesh.position ) : undefined,
rotation: new THREE.Euler().copy( mesh.rotation ),
}
cMeshs.__select.appendChild( opt );
displayVerticeID( mesh );
}
this.getObjectLocalPosition = (intersectionSelected) => { return getObjectLocalPosition( intersectionSelected.object, intersectionSelected.index ); }
this.getObjectPosition = (object, index) => { return getObjectPosition( object, index); }
/**
* User has selected a point
* @param {Object} intersectionSelected See [intersectObject]{@link https://threejs.org/docs/index.html#api/en/core/Raycaster.intersectObject}
*/
this.select = function ( intersectionSelected ) {
intersection = undefined;
let intersectionSelectedIndex = intersectionSelected.index;
if ( (intersectionSelectedIndex === undefined) || isNaN( intersectionSelectedIndex ) ) {
//Пользователь нажал мышкой на LineSegments
const geometryIndex = intersectionSelected.object.geometry.index;
intersectionSelectedIndex = geometryIndex.getX(intersectionSelected.indexNew);
intersectionSelected.object.userData.myObject.guiPoints.verticeId = intersectionSelectedIndex;
intersectionSelected.index = intersectionSelectedIndex;
}
const myObject = intersectionSelected.object.userData.myObject;
if ( !myObject.getPositionData )
myObject.getPositionData = ( index ) => { return { verticeId: index, positionId: index * intersectionSelected.object.geometry.attributes.position.itemSize } }
//f3DObjects.close();//если тут не закрывать папку, то ингода прорпадает скроллинг окна dat.GUI
//for testing:
//Open https://raw.githack.com/anhr/myThreejs/master/Examples/html/
//Set browser window height about 500 pixels.
//Click Full Screen button.
//Open Controls
//Click a point.The "Meshes" folder opens and you can see the scrolling of the dat.gui window.
//select mesh
const index = this.getMeshIndex( intersectionSelected.object );
if ( !index )
return;//Сюда попадает когда Mesh проверяется с помощью THREE.Raycaster находится ли он под мышью, но этот Mesh не внесен в список GuiSelectPoint
if ( cMeshs.__select[index].selected === false ) {
cMeshs.__select[index].selected = true;
cMeshs.__onChange( index - 1, intersectionSelected );
} else {
if (myObject.guiPoints.setIntersection) myObject.guiPoints.setIntersection(intersectionSelected);
if (myObject.guiPoints.changeControllers) myObject.guiPoints.changeControllers();
if ( cCustom ) {
const mesh = getMesh();
cCustom.object(intersectionSelected.event && intersectionSelected.event.button === 0 ?
mesh :
undefined,//Пользователь нажал не левую кнопку мыши. Надо восстановить выбранный nD объект
dat, options);
}
}
this.selectPoint2 = function ( selectedMesh ) {
//сделал эту проверку потому что не могу придумать как удалить intersectionSelectedIndex когда пользователь вручную сменил mesh
if ( ( selectedMesh !== undefined ) && !Object.is( intersectionSelected.object, selectedMesh ) )
return;//Сначала пользователь выбрал точку с помошщью мыши
//Потом сменил Meshes/Select
if ( !intersectionSelected.object.userData.boFrustumPoints ) {
//fPoints.open();много времени на открытие когда много точек
const myObject = intersectionSelected.object.userData.myObject, guiPoints = myObject ? myObject.guiPoints : undefined, verticeId = guiPoints ? guiPoints.verticeId : undefined,
point = cPoints.__select[(verticeId? verticeId : intersectionSelectedIndex) + 1];
if ( point ) point.selected = true;
} else {//FrustumPoints
cFrustumPoints.pointIndexes( intersectionSelected.object.userData.pointIndexes( intersectionSelectedIndex ) );
}
const block = 'block';
displayPointControllers( block );
intersection = intersectionSelected;
if ( guiParams.setIntersection )
guiParams.setIntersection( intersectionSelected );
setPosition( intersectionSelected );
const mesh = getMesh(), arrayFuncs = mesh.userData.player.arrayFuncs,
line = !mesh.userData.player ||
( mesh.userData.player.arrayFuncs === undefined ) ||
( typeof intersection.object.userData.player.arrayFuncs === "function" ) ?
undefined :
intersectionSelectedIndex != undefined ?
( arrayFuncs.intersection ? arrayFuncs.intersection( intersectionSelectedIndex ) : arrayFuncs[intersectionSelectedIndex] ).line ://You can not trace points if you do not defined the mesh.userData.player.arrayFuncs
undefined;
if ( cTrace )
cTrace.setValue( ( line === undefined ) ?
false : line.isVisible() )
cRestoreDefaultLocalPosition.domElement.parentElement.parentElement.style.display =
!intersection.object.userData.player || ( intersection.object.userData.player.arrayFuncs === undefined ) ?
'none' : block;
}
this.selectPoint2( undefined );
}
/**
* Is mesh selected in the GuiSelectPoint?
* @param {THREE.Mesh} meshCur [Mech]{@link https://threejs.org/docs/index.html#api/en/objects/Mesh} to be tested
* @returns true if <b>meshCur</b> is selected.
*/
this.isSelectedMesh = function ( meshCur ) { return getMesh() === meshCur }
/**
* @returns index of the selected point or -1 if point is not selected.
*/
this.getSelectedPointIndexShort = () => { return cPoints.__select.selectedIndex - 1 }
/**
* @returns index of the selected point or -1 if mesh is not selected or if point is not selected.
*/
this.getSelectedPointIndex = function () {
if ( ( cFrustumPoints !== undefined ) &&
cFrustumPoints.isDisplay() &&//FrustumPoints folder is visible
options.frustumPoints &&
options.frustumPoints.isDisplay()//The cDisplay checkbox of the frustumPoints' is checked
) {
var selectedIndex = cFrustumPoints.getSelectedIndex();
return selectedIndex === null ? - 1 : selectedIndex;
}
if ( cPoints === undefined ) {
if ( selectedPointIndex === undefined )
console.error( 'myThreejs.create.onloadScripts.init.guiSelectPoint.getSelectedPointIndex: selectedPointIndex = ' + selectedPointIndex );
return selectedPointIndex;//options.dat !== true and gui === undefined. Do not use dat.gui
}
const mesh = getMesh();
if ( !mesh ) return -1; //не выбран 3d объект.
return mesh.userData.myObject.guiPoints.getValue(cPoints);
}
function getMesh() {
if ( !cMeshs ) {
console.error( 'GuiSelectPoint().getSelectedPointIndex().getMesh(): call GuiSelectPoint.add(); first.' );
return undefined;
}
const selectedIndex = cMeshs.__select.options.selectedIndex;
if ( selectedIndex !== -1 )
return cMeshs.__select.options[cMeshs.__select.options.selectedIndex].mesh;
return undefined;
}
function isNotSetControllers() {
return ( getMesh() === undefined );
}
function setScaleControllers() {
if ( isNotSetControllers() )
return;
const mesh = getMesh();
if ( cScaleX ) cScaleX.setValue( mesh.scale.x );
if ( cScaleY ) cScaleY.setValue( mesh.scale.y );
if ( cScaleZ ) cScaleZ.setValue( mesh.scale.z );
}
function setPositionControllers() {
if ( isNotSetControllers() )
return;
const mesh = getMesh();
if ( cPosition.x ) cPosition.x.setValue( mesh.position.x );
if ( cPosition.y ) cPosition.y.setValue( mesh.position.y );
if ( cPosition.z ) cPosition.z.setValue( mesh.position.z );
}
function setRotationControllers() {
if ( isNotSetControllers() )
return;
const mesh = getMesh();
const setValue = ( controller, angle ) => {
if ( ( angle < controller.__min ) || ( angle > controller.__max ) ) console.error( 'GuiSelectPoint.setRotationControllers(): Invalid angle = ' + angle + ' range. Available range from ' + controller.__min + ' to ' + controller.__max )
controller.setValue( angle );
}
if ( cRotations.x ) setValue( cRotations.x, mesh.rotation.x );
if ( cRotations.y ) setValue( cRotations.y, mesh.rotation.y );
if ( cRotations.z ) setValue( cRotations.z, mesh.rotation.z );
}
function visibleTraceLine( intersection, value, getMesh ) {
if ( !intersection || !intersection.object.userData.player || intersection.object.userData.player.arrayFuncs === undefined )
return;
const arrayFuncs = intersection.object.userData.player.arrayFuncs;
var index = intersection.index || 0,
point = arrayFuncs.intersection ? arrayFuncs.intersection(index) : arrayFuncs[index],
line = point === undefined ? undefined : point.line;
if ( ( line !== undefined ) )
line.visible( value );
if ( !value )
return;
if ( point.line !== undefined )
return;
point.line = new Player.traceLine( options );
//color
var color = intersection.object.geometry.attributes.color;
if ( color === undefined )
color = new THREE.Color( 0xffffff );//white
else {
var vector = new THREE.Vector3().fromArray( color.array, index * color.itemSize )
color = new THREE.Color( vector.x, vector.y, vector.z );
}
point.line.addPoint(
getMesh(), index,
color
);
}
this.updateScale = function ( axisName ) {
const none = 'none', block = 'block', display = options.scales[axisName].isAxis() ? block : none;
//Rotation
const boX = options.scales['x'].isAxis(),
boY = options.scales['y'].isAxis(),
boZ = options.scales['z'].isAxis();
var n = 0;//space dimension
if ( boX ) n++;
if ( boY ) n++;
if ( boZ ) n++;
switch ( n ) {
case 1:
if ( fRotation ) fRotation.domElement.style.display = none;
break;
case 2:
fRotation.domElement.style.display = block;
if ( boX && cRotations.x.domElement ) cRotations.x.domElement.parentElement.parentElement.style.display = none;
if ( boY && cRotations.y.domElement ) cRotations.y.domElement.parentElement.parentElement.style.display = none;
if ( boZ && cRotations.z.domElement ) cRotations.z.domElement.parentElement.parentElement.style.display = none;
break;
default: console.error( 'GuiSelectPoint.updateScale: Invalid space dimension = ' + n );
return;
}
if ( !folders.position[axisName] ) {
if ( options.scales[axisName].isAxis() ) console.error( 'GuiSelectPoint.updateScale: Under constraction.' );
return;
}
//position
folders.position[axisName].domElement.style.display = display;
cWorld[axisName].domElement.parentElement.parentElement.style.display = display;
//Scale, point local position
var c, cScale;
switch(axisName) {
case 'x':
c = cX;
cScale = cScaleX;
break;
case 'y':
c = cY;
cScale = cScaleY;
break;
case 'z':
c = cZ;
cScale = cScaleZ;
break;
default: console.error('GuiSelectPoint.updateScale: Invalid axis name: ' + axisName);
return;
}
c.domElement.parentElement.parentElement.style.display = display;
cScale.domElement.parentElement.parentElement.style.display = display;
}
function displayVerticeID( object ) {
if ( object.userData.boFrustumPoints ) return;
if ( !options.dat.guiSelectPoint.boDisplayVerticeID ) {
for ( var i = object.children.length - 1; i >= 0; i-- ) {
const child = object.children[i];
if ( child.type === 'Sprite' ) object.remove( child );
}
return;
}
if (!object.geometry) return;//Probably this is Group
let gp = object.geometry.attributes.position;
object.updateMatrixWorld();
const drawRange = object.geometry.drawRange, count = (drawRange.count === Infinity) ? gp.count : drawRange.start + drawRange.count;
for ( let i = drawRange.start; i < count; i++ ) {
let p = new THREE.Vector3().fromBufferAttribute( gp, i ); // set p from `position`
// object.localToWorld(p); // p has wordl coords
const spriteText = new SpriteText( i, p, { group: object } );
spriteText.userData.pointID = i;
}
}
function addPoints( mesh, intersectionSelected ) {
//https://threejs.org/docs/index.html?q=buffer#api/en/core/BufferGeometry.setDrawRange
const count = mesh.geometry.userData.drawRange ?
mesh.geometry.userData.drawRange().count ://Во вселенной задал диапазон видимых вершин
mesh.geometry.index === null ?
//геометрия не индексирована. Значит mesh.geometry.drawRange.count указывает на количество видимых вершин
(mesh.geometry.drawRange.count + mesh.geometry.drawRange.start) > mesh.geometry.attributes.position.count ?
mesh.geometry.attributes.position.count ://диапазон вершин больше количества вершин
mesh.geometry.drawRange.count + mesh.geometry.drawRange.start//добавлять только вершины, которые вошли в заданный диапазон
:
//геометрия индексирована. mesh.geometry.drawRange.count указывает на количество индексов, которые нужно рисовать
mesh.geometry.attributes.position.count;//По умолчанию все вершины видно
mesh.userData.myObject.guiPoints.create( fPoints, cPoints, cTrace, cTraceAll, count, intersectionSelected );
}
function createPlayerArrayFuncs( mesh ) {
if ( !mesh || mesh.userData.boFrustumPoints || ( mesh.userData.player && mesh.userData.player.boArrayFuncs === false ) ) return;
if ( !mesh.userData.player ) mesh.userData.player = {};
if ( !mesh.userData.player.arrayFuncs && mesh.geometry ) {
const position = mesh.geometry.attributes.position;
mesh.userData.player.arrayFuncs = [];
for ( var i = 0; i < position.count; i++ ) {
const vector = new THREE.Vector4().fromArray( mesh.geometry.attributes.position.array, i * position.itemSize );
mesh.userData.player.arrayFuncs.push( vector );
}
}
}
/**
* Adds select point GUI into dat.gui folder
* @param {GUI} [folder] [dat.gui]{@link https://github.com/anhr/dat.gui} folder.
*/
this.add = function ( folder ) {
folder = folder || options.dat.gui;
if ( !folder )
return;//gui не создан
f3DObjects = folder.addFolder( lang.meshs );
f3DObjects.domElement.style.display = 'none';
var mesh, buttonScaleDefault, buttonPositionDefault, buttonRotationDefault;
cMeshs = f3DObjects.add( { Meshs: lang.notSelected }, 'Meshs', { [lang.notSelected]: -1 } ).onChange( function ( value, intersectionSelected ) {
value = parseInt( value );
cPoints.__onChange( -1 );//сбросить настройки точки
if (mesh && mesh.userData.myObject.guiPoints.resetControllers) mesh.userData.myObject.guiPoints.resetControllers();
mesh = getMesh();
const none = 'none', block = 'block';
if (fPoint.fCustomPoint) {
fPoint.removeFolder(fPoint.fCustomPoint);
delete fPoint.fCustomPoint;
}
if ( mesh && mesh.userData.gui ) { fPoint.fCustomPoint = mesh.userData.gui.addControllers( fPoint, {
readOnlyEl: readOnlyEl,
isReadOnlyController: isReadOnlyController,
} ); }
if ( cCustom ) cCustom.object( mesh, dat, value === -1 );
createPlayerArrayFuncs( mesh );
var display;
if ( mesh === undefined ) {
display = none;
mesh = undefined;
if (( options.axesHelper !== undefined ) && ( options.axesHelper !== false ))
options.axesHelper.exposePosition( getObjectPosition( getMesh(), value ) );
} else {
const displayDefaultButtons = mesh.userData.default === undefined ? none : block;
buttonScaleDefault.domElement.parentElement.parentElement.style.display = displayDefaultButtons;
buttonPositionDefault.domElement.parentElement.parentElement.style.display = displayDefaultButtons;
if ( buttonRotationDefault ) buttonRotationDefault.domElement.parentElement.parentElement.style.display = displayDefaultButtons;
display = block;
var displayPoints = none, displayfPoints = none, displayFrustumPoints = block;
cPoints.__onChange( -1 );
_this.removePoints();
if ( mesh.userData.controllers !== undefined ) {
//FrustumPoints
mesh.userData.controllers();
} else {
displayPoints = block;
displayFrustumPoints = none;
if ( mesh.geometry && mesh.geometry.attributes ) {
displayfPoints = block;
addPoints( mesh, intersectionSelected );
if (mesh.userData.myObject && mesh.userData.myObject.guiPoints && mesh.userData.myObject.guiPoints.pointsStyleDisplay)
displayPoints = mesh.userData.myObject.guiPoints.pointsStyleDisplay;
}
}
dislayEl( cPoints, displayPoints );
if ( cTraceAll )
dislayEl( cTraceAll, (cTraceAll.userData && cTraceAll.userData.display) ? cTraceAll.userData.display : options.player ? displayPoints : false );
if ( cFrustumPoints !== undefined )
cFrustumPoints.display( displayFrustumPoints );
setScaleControllers();
setPositionControllers();
setRotationControllers();
orbitControlsOptions = undefined;
}
fMesh.domElement.style.display = display;
if ( ( mesh !== undefined ) && ( mesh.userData.traceAll !== undefined ) )
cTraceAll.setValue( mesh.userData.traceAll );
} );
dat.controllerNameAndTitle( cMeshs, lang.select );
fMesh = f3DObjects.addFolder( lang.mesh );
fMesh.domElement.style.display = 'none';
fMesh.open();
//Scale
const fScale = fMesh.addFolder( lang.scale );
fScale.add( new ScaleController( function ( customController, action ) {
const zoom = customController.controller.getValue();
mesh.scale.x = action( mesh.scale.x, zoom );
mesh.scale.y = action( mesh.scale.y, zoom );
mesh.scale.z = action( mesh.scale.z, zoom );
mesh.needsUpdate = true;
setScaleControllers();
exposePosition();
if ( options.frustumPoints )
options.frustumPoints.updateCloudPoint( mesh );
},
{
settings: { zoomMultiplier: 1.1, },
getLanguageCode: getLanguageCode,
newBool: true,
} ) );
const scale = new THREE.Vector3();
function setScale( axisName, value ) {
mesh.scale[axisName] = value;
mesh.needsUpdate = true;
exposePosition();
if ( options.frustumPoints )
options.frustumPoints.updateCloudPoint( mesh );
}
if ( options.scales.x.isAxis() ) {
cScaleX = dat.controllerZeroStep( fScale, scale, 'x', function ( value ) { setScale( 'x', value ); } );
dat.controllerNameAndTitle( cScaleX, options.scales.x.name );
}
if ( options.scales.y.isAxis() ) {
cScaleY = dat.controllerZeroStep( fScale, scale, 'y', function ( value ) { setScale( 'y', value ); } );
dat.controllerNameAndTitle( cScaleY, options.scales.y.name );
}
if ( options.scales.z.isAxis() ) {
cScaleZ = dat.controllerZeroStep( fScale, scale, 'z', function ( value ) { setScale( 'z', value ); } );
dat.controllerNameAndTitle( cScaleZ, options.scales.z.name );
}
//Default scale button
buttonScaleDefault = fScale.add( {
defaultF: function ( value ) {
mesh.scale.copy( mesh.userData.default.scale );
mesh.needsUpdate = true;
setScaleControllers();
exposePosition();
},
}, 'defaultF' );
dat.controllerNameAndTitle( buttonScaleDefault, lang.defaultButton, lang.defaultScaleTitle );
//Position
const fPosition = fMesh.addFolder( lang.position );
function addAxisControllers( name ) {
const scale = options.scales[name];
if ( !scale.isAxis() )
return;
const axisName = scale.name,
f = fPosition.addFolder( axisName );
folders.position = folders.position || {};
folders.position[axisName] = f;
f.add( new PositionController( function ( shift ) {
mesh.position[name] += shift;
mesh.needsUpdate = true;
setPositionControllers();
exposePosition();
if ( options.frustumPoints )
options.frustumPoints.updateCloudPoint( mesh );
}, { getLanguageCode: getLanguageCode, } ) );
function setPosition( value ) {
mesh.position[name] = value;
mesh.needsUpdate = true;
exposePosition();
}
const position = new THREE.Vector3();
cPosition[name] = dat.controllerZeroStep( f, position, name, function ( value ) { setPosition( value ); } );
dat.controllerNameAndTitle( cPosition[name], axisName );
}
addAxisControllers( 'x' );
addAxisControllers( 'y' );
addAxisControllers( 'z' );
//Restore default position.
buttonPositionDefault = fPosition.add( {
defaultF: function ( value ) {
mesh.position.copy( mesh.userData.default.position );
mesh.needsUpdate = true;
setPositionControllers();
exposePosition();
},
}, 'defaultF' );
dat.controllerNameAndTitle( buttonPositionDefault, lang.defaultButton, lang.defaultPositionTitle );
//rotation
function addRotationFolder() {
const boX = options.scales.x.isAxis(),
boY = options.scales.y.isAxis(),
boZ = options.scales.z.isAxis();
var n = 0;//space dimension
if ( boX ) n++;
if ( boY ) n++;
if ( boZ ) n++;
if ( n === 1 ) return;
fRotation = fMesh.addFolder( lang.rotation );
function addRotationControllers( name ) {
const scale = options.scales[name];
cRotations[name] = fRotation.add( new THREE.Vector3(), name, 0, Math.PI * 2, 0.01 ).
onChange( function ( value ) {
const mesh = getMesh();
if ( !mesh.userData.boFrustumPoints ) {
mesh.rotation[name] = value;
mesh.needsUpdate = true;
}
if ( !boSetMesh )
exposePosition();
if ( options.frustumPoints !== undefined )
options.frustumPoints.updateCloudPoint( mesh );
} );
dat.controllerNameAndTitle( cRotations[name], scale.name );
}
switch ( n ) {
case 2:
addRotationControllers( !boX ? 'x' : !boY ? 'y' : 'z' );
break;
case 3:
addRotationControllers( 'x' );
addRotationControllers( 'y' );
addRotationControllers( 'z' );
break;
default: console.error( 'GuiSelectPoint.updateScale: Invalid space dimension = ' + n );
return;
}
//Default rotation button
buttonRotationDefault = fRotation.add( {
defaultF: function ( value ) {
mesh.rotation.copy( mesh.userData.default.rotation );
mesh.needsUpdate = true;
setRotationControllers();
exposePosition();
},
}, 'defaultF' );
dat.controllerNameAndTitle( buttonRotationDefault, lang.defaultButton, lang.defaultRotationTitle );
}
addRotationFolder();
//Points
fPoints = fMesh.addFolder( lang.points );
let oldMesh;//Нужен когда пользователь сменил выбранный графический объект и когда надо сбрость настройки вершины предыдущего горафического объекта
cPoints = fPoints.add( { Points: lang.notSelected }, 'Points', { [lang.notSelected]: -1 } );
cPoints.onChange( function ( pointId ) {
pointId = parseInt( pointId );
if (isNaN(pointId)){
const onchange = this.__select[this.__select.selectedIndex].onchange;
if(onchange) onchange();
else console.error('GuiSelectPoint: cPoints.onChange. Under constraction');//во вселенной выбрать все точки для текущего времени проигрывателя
return;
}
var display, position;
let mesh = getMesh();
if ( mesh && mesh.userData.gui && mesh.userData.gui.reset ) oldMesh = mesh;
if ( pointId === -1 ) display = 'none';
else {
display = 'block';
const userData = mesh.userData.myObject.bufferGeometry.userData, oldTimeId = userData.timeId;
userData.timeId = mesh.userData.myObject.guiPoints.timeId;
const point = userData.position[pointId];
pointId = userData.positionOffsetId(pointId);
userData.timeId = oldTimeId;
/*
const attributesPosition = mesh.geometry.attributes.position;
point = new THREE.Vector3().fromBufferAttribute(attributesPosition, pointId);
*/
const intersection = {
object: mesh,
index: pointId,
point: point,
nearestEdgeVerticeId: pointId,//если не задать это значение, то index будет интерпретироваться как индекс ребра и программа в ребре будет искать индекс вершины, ближайшей к point
//Для проверки открыть http://localhost/anhr/commonNodeJS/master/HyperSphere/Examples/hyperSphere.html
//С помошю gui выбрать вершину
//С помошю gui поменять углы вершины
}
const setIntersectionProperties = mesh.userData.myObject.guiPoints.setIntersectionProperties;
if (setIntersectionProperties) setIntersectionProperties(intersection);
_this.select(intersection);
}
if ( ( options.axesHelper !== false ) && ( options.axesHelper !== undefined ) )
options.axesHelper.exposePosition( getObjectPosition(
mesh, pointId//((pointId != -1) ? mesh.userData.myObject.guiPoints.positionOffset : 0) + pointId
) );
displayPointControllers( display );
if ( !mesh || !mesh.userData.gui || !mesh.userData.gui.reset) mesh = oldMesh;
if ( mesh && mesh.userData.gui && mesh.userData.gui.reset ) mesh.userData.gui.reset( pointId );
} );
cPoints.__select[0].selected = true;
dat.controllerNameAndTitle( cPoints, lang.select );
if ( cFrustumPoints !== undefined )
cFrustumPoints.create( fPoints, getLanguageCode() );
if ( guiParams.myThreejs )
guiParams.myThreejs.cFrustumPoints = cFrustumPoints
//display vertice ID
options.dat.guiSelectPoint = options.dat.guiSelectPoint || {};
options.dat.guiSelectPoint.boDisplayVerticeID = options.dat.guiSelectPoint.boDisplayVerticeID || false;
const cDisplayVerticeID = f3DObjects.add( options.dat.guiSelectPoint, 'boDisplayVerticeID' ).onChange( function ( value ) {
for ( var i = 1; i < cMeshs.__select.options.length; i++ ) {
var option = cMeshs.__select.options[i];
if ( option.mesh === undefined ) {
console.error( 'guiSelectPoint: cDisplayVerticeID.onChange. Invalud option.mesh' );
continue;
}
displayVerticeID( option.mesh );//getMesh() );
}
} );
dat.controllerNameAndTitle( cDisplayVerticeID, lang.displayVerticeID, lang.displayVerticeIDTitle );
//Custom point controllers
if ( options.dat && options.dat.guiSelectPoint && options.dat.guiSelectPoint.point ) cCustom = options.dat.guiSelectPoint.point( options, dat, fMesh );
//Camera target
// Может устанвливаться только если создан проигрыватель options.player,
//потому что при установке птички 'Look' - Следить, вызывается options.player.selectScene()
if ( options.player ) {
var orbitControlsOptions;
if ( guiParams.cameraTarget ) options.playerOptions.cameraTarget.init( guiParams.cameraTarget, options );
const playerCameraTarget = options.playerOptions.cameraTarget.get( options );
if ( playerCameraTarget ) {
cCameraTarget = fPoints.add( playerCameraTarget, 'boLook' ).onChange( function ( boLook ) {
const mesh = getMesh();
if ( mesh.userData.boFrustumPoints ) {
if ( boLook ) {
console.warn( 'guiSelectPoint.cCameraTarget.onChange(...). The camera can not look at the frustum point.' );
cCameraTarget.setValue( false );
}
return;
}
if ( !mesh.userData.player ) {
mesh.userData.player = { arrayFuncs: [] }
for ( var i = 0; i < mesh.geometry.attributes.position.count; i++ ) {
mesh.userData.player.arrayFuncs.push( new THREE.Vector3().fromArray( mesh.geometry.attributes.position.array,
i * mesh.geometry.attributes.position.itemSize ) );
}
}
const index = mesh.userData.boFrustumPoints ? cFrustumPoints.getSelectedIndex() : cPoints.__select.options.selectedIndex - 1,
point = typeof mesh.userData.player.arrayFuncs === "function" ?
new THREE.Vector3().fromArray( mesh.userData.player.arrayFuncs().attributes.position.array, index * 3 ) :
mesh.userData.player.arrayFuncs !== undefined ? mesh.userData.player.arrayFuncs[index] :
new THREE.Vector3().fromArray( mesh.geometry.attributes.position.array, index * 3 );
//remove boLook from all points
for ( var i = 0; i < cMeshs.__select.options.length; i++ ) {
const mesh = cMeshs.__select.options[i].mesh;
if ( !mesh || !mesh.userData.player || !mesh.userData.player.arrayFuncs )
continue;
const arrayFuncs = mesh.userData.player.arrayFuncs;
for ( var j = 0; j < arrayFuncs.length; j++ )
if ( arrayFuncs[j].cameraTarget ) arrayFuncs[j].cameraTarget.boLook = false;
}
if ( point.cameraTarget ) point.cameraTarget.boLook = boLook;
if ( options.player ) options.player.selectScene();
if ( options.cameraGui ) options.cameraGui.look( boLook );
if ( boLook ) {
if ( !point.cameraTarget ) {
if ( playerCameraTarget.boLook === undefined ) Player.cameraTarget2.boLook = false;
if ( !orbitControlsOptions ) orbitControlsOptions = {}
if ( !orbitControlsOptions.target )
orbitControlsOptions.target = new THREE.Vector3();
if ( options.orbitControls )
orbitControlsOptions.target.copy( options.orbitControls.target );
options.playerOptions.cameraTarget.changeTarget( mesh, index );
}
return;
}
//Если точка имеет индивидуальную cameraTarget, то камера будет следить по этим настройкам
if ( guiParams.cameraTarget ) guiParams.cameraTarget.camera.userData.cameraTargetPoint = point.cameraTarget;
if ( options.orbitControls ) options.orbitControls.reset();
if ( orbitControlsOptions ) {
if ( getCameraTarget() )
return;
if ( Player.orbitControls )
Player.orbitControls.target.copy( orbitControlsOptions.target );
guiParams.cameraTarget.camera.lookAt( orbitControlsOptions.target );
point.cameraTarget = undefined;
}
} );
dat.controllerNameAndTitle( cCameraTarget, lang.cameraTarget, lang.cameraTargetTitle );
}
}
//Points attribute position
fPoint = fPoints.addFolder( lang.point );
dat.folderNameAndTitle( fPoint, lang.point, lang.pointTitle );
//Points world position
fPointWorld = fPoints.addFolder( lang.pointWorld );
dat.folderNameAndTitle( fPointWorld, lang.pointWorld, lang.pointWorldTitle );
//fPointWorld.open();
displayPointControllers( 'none' );
if ( guiParams.pointsControls ) {
guiParams.pointsControls( fPoints, dislayEl, getMesh );
}
//options.traces ||= uncompatible with myThree.js → ./build/myThree.js, ./ build / myThree.module.js...
if (!options.traces) options.traces = {
boTraces: false,
onChange: ( value ) => {
var mesh = getMesh();
mesh.userData.traceAll = value;
for ( var i = 0; i < mesh.geometry.attributes.position.count; i++ ) visibleTraceLine( { object: mesh, index: i }, value, getMesh );
cTrace.setValue( value );
},
}
cTraceAll = fPoints.add( options.traces, 'boTraces' ).onChange( ( value ) => { options.traces.onChange( value ); } );
dat.controllerNameAndTitle( cTraceAll, lang.trace, lang.traceAllTitle );
dislayEl( cTraceAll, options.player );
//Restore default settings of all 3d objects button.
dat.controllerNameAndTitle( f3DObjects.add( {
defaultF: function ( value ) {
for ( var i = 0; i < cMeshs.__select.options.length; i++ ) {
const mesh = cMeshs.__select.options[i].mesh;
if ( !mesh )
continue;
mesh.scale.copy( mesh.userData.default.scale );
mesh.position.copy( mesh.userData.default.position );
mesh.rotation.copy( mesh.userData.default.rotation );
mesh.needsUpdate = true;
}
setScaleControllers();
setPositionControllers();
setRotationControllers();
exposePosition();
if ( options.frustumPoints )
options.frustumPoints.onChangeControls();
},
}, 'defaultF' ), lang.defaultButton, lang.default3DObjectTitle );
addPointControllers();
while ( arrayMeshs.length > 0 ) {
this.addMesh( arrayMeshs[arrayMeshs.length - 1] );
arrayMeshs.pop();
}
}
/**Removes all points from points list control. */
this.removePoints = function () {
//thanks to https://stackoverflow.com/a/48780352/5175935
cPoints.domElement.querySelectorAll( 'select option' ).forEach( option => option.remove() );
const opt = document.createElement( 'option' );
opt.innerHTML = lang.notSelected;
opt.setAttribute( 'value', -1 );//Эта строка нужна в случае когда пользователь отменил выбор точки. Иначе при движении камеры будут появляться пунктирные линии, указвающие на несуществующую точку
cPoints.__select.appendChild( opt );
}
/**Updates points in the points list control. */
this.updatePoints = function () {
const boDisplayVerticeID = options.dat.guiSelectPoint.boDisplayVerticeID,
mesh = getMesh();
//убрать с холста идентификаторы точек
if ( boDisplayVerticeID ) {
options.dat.guiSelectPoint.boDisplayVerticeID = false;
displayVerticeID( mesh );
}
cPoints.__onChange( -1 );
this.removePoints();
mesh.userData.player.arrayFuncs.length = 0;
delete mesh.userData.player.arrayFuncs;
createPlayerArrayFuncs( mesh );
addPoints( mesh );
//восстановить идентификаторы точек
if ( boDisplayVerticeID ) {
options.dat.guiSelectPoint.boDisplayVerticeID = true;
displayVerticeID( mesh );
}
if ( mesh.userData.nd ) mesh.userData.nd.update();
}
function addPointControllers() {
//Point's attribute position axes controllers
function axesGui( axisName ) {
var scale, controller;
if ( axisName === 'w' ) {
//W axis
options.scales.setW();
scale = options.scales.w;
function onChange( value ) {
const attributes = intersection.object.geometry.attributes,
i = intersection.index;
if ( attributes.position.itemSize < 4 ) {
console.error( 'guiSelectPoint.addPointControllers().axesGui().controller.onChange(): attributes.position.itemSize = ' + attributes.position.itemSize )
return;//Точка не имеет цвета. Например это вершина куба. Надо скрыть оган управления координатой w
}
if ( options.palette ) Player.setColorAttribute( attributes, i, options.palette.toColor( value, controller.__min, controller.__max ) );
if ( options.scales.w.isColor != false )
attributes.position.setW( i, value );
if ( options.frustumPoints )
options.frustumPoints.updateCloudPointItem( intersection.object, intersection.index );
}
if ( ( scale.min !== undefined ) && ( scale.max !== undefined ) ) {
controller = fPoint.add( { value: scale.min, }, 'value', scale.min, scale.max, ( scale.max - scale.min ) / 100 ).onChange( function ( value ) {
if ( isReadOnlyController( controller ) ) return;
onChange( value );
} );
if ( ( options.scales.w.isColor != false ) && ( options.palette instanceof ColorPicker.palette ) ) {
controller.domElement.querySelector( '.slider-fg' ).style.height = '40%';
const elSlider = controller.domElement.querySelector( '.slider' );
ColorPicker.create( elSlider, {
palette: options.palette,
style: {
//border: '2px solid #303030',
width: '65%',//Непонятно почему без этой строи не видно палиры в http://localhost/anhr/egocentricUniverse/master/Examples/3D.html
//height: elSlider.offsetHeight / 2,//'50%',
},
//onError: function ( message ) { alert( 'Colorpicker error: ' + message ); }
} );
}
} else
controller = fPoint.add( { value: 0, }, 'value' ).onChange( function ( value ) { onChange( value ); } );
} else {
scale = ( options.axesHelper === undefined ) || ( options.axesHelper === false ) ? options.scales[axisName] : //если я буду использовать эту строку то экстремумы шкал буду устанавливатся по умолчанию а не текущие
options.axesHelper.options ? options.axesHelper.options.scales[axisName] : undefined;
if ( scale.isAxis() )
controller = fPoint.add( { value: scale.min, }, 'value', scale.min, scale.max, ( scale.max - scale.min ) / 1000 ).onChange( function ( value ) {
const points = intersection.object;
if ( isReadOnlyController( controller ) ) return;
const axesId = axisName === 'x' ? 0 : axisName === 'y' ? 1 : axisName === 'z' ? 2 : axisName === 'w' ? 3 : console.error('axisName:' + axisName);
points.geometry.attributes.position.array[axesId + points.userData.myObject.getPositionData(intersection.index).positionId] = value;
points.geometry.attributes.position.needsUpdate = true;
exposePosition( intersection.index );
if ( options.frustumPoints )
options.frustumPoints.updateCloudPointItem( points, intersection.index );
} );
}
if ( controller )
dat.controllerNameAndTitle( controller, scale.name );
return controller;
}
cX = axesGui( 'x' );
cY = axesGui( 'y' );
cZ = axesGui( 'z' );
cW = axesGui( 'w' );
cColor = fPoint.addColor( { color: '#FFFFFF', }, 'color' ).
onChange( function ( value ) {
//for testing
//Go to http://localhost/anhr/commonNodeJS/master/player/Examples/
//Select 3 Ponts' 3D object
//Select a point 2
//Open 'Point's local position'
//Change 'color'
if ( isReadOnlyController( cColor ) ) return;
if ( cColor.userData === undefined ) return;
var intersection = cColor.userData.intersection;
Player.setColorAttribute( intersection.object.geometry.attributes, intersection.index, value );
} );
dat.controllerNameAndTitle( cColor, options.scales.w ? options.scales.w.name : lang.color );
cOpacity = fPoint.add( { opasity: 1, }, 'opasity', 0, 1, 0.01 ).onChange( function ( opasity ) {
if ( isReadOnlyController( cOpacity ) )
return;
const mesh = getMesh();
if (mesh.userData.myObject) {
mesh.userData.myObject.verticeOpacity(intersection.index, true, opasity);
return;
}
if (!mesh.material.transparent) {
console.error( 'GuiSelectPoint: cOpacity.onChange. Invalid mesh.material.transparent = ' + mesh.material.transparent);
return;
}
if (!mesh.material.vertexColors) {
console.error( 'GuiSelectPoint: cOpacity.onChange. Invalid mesh.material.vertexColors = ' + mesh.material.vertexColors);
return;
}
const color = mesh.geometry.attributes.color;
if (color.itemSize < 4) return;
color.array[3 + intersection.index * color.itemSize] = opasity;
color.needsUpdate = true;
} );
dat.controllerNameAndTitle( cOpacity, lang.opacity, lang.opacityTitle );
//options.trace ||= uncompatible with myThree.js → ./build/myThree.js, ./ build / myThree.module.js...
if (!options.trace) options.trace = { onChange: (value) => { visibleTraceLine(intersection, value, getMesh); }, }
cTrace = fPoint.add( { boTrace: false, }, 'boTrace' ).onChange( function ( value ) { options.trace.onChange( value, cPoints.__select.selectedIndex - 1 ); } );
dat.controllerNameAndTitle( cTrace, lang.trace, lang.traceTitle ); //guiParams
dislayEl( cTrace, options.player );
if ( guiParams.pointControls ) guiParams.pointControls( fPoint, dislayEl, getMesh, intersection );
//Point's world position axes controllers
function axesWorldGui( axisName ) {
const scale = ( options.axesHelper === undefined ) || ( options.axesHelper === false ) ? options.scales[axisName] :
options.axesHelper.options ? options.axesHelper.options.scales[axisName] : undefined;
if ( !scale.isAxis() )
return;
const controller = dat.controllerZeroStep( fPointWorld, { value: scale.min, }, 'value' );
readOnlyEl( controller, true );
dat.controllerNameAndTitle( controller, scale.name );
return controller;
}
cWorld.x = axesWorldGui( 'x' );
cWorld.y = axesWorldGui( 'y' );
cWorld.z = axesWorldGui( 'z' );
//Restore default local position.
cRestoreDefaultLocalPosition = fPoint.add( {
defaultF: function () {
const positionDefault = intersection.object.userData.player.arrayFuncs[intersection.index],
t = options.time,
setDefaultValue = ( control, value ) => {
if ( !control ) return;
control.setValue(
typeof value === "function" ?
value( t, options.a, options.b ) :
typeof value === "string" ?
Player.execFunc( { w: value } , 'w', options.time, options ) :
value
);
};
setDefaultValue( cX, positionDefault.x );
setDefaultValue( cY, positionDefault.y );
setDefaultValue( cZ, positionDefault.z === undefined ? 0 ://default Z axis of 2D point is 0
positionDefault.z );
if ( isDislayEl( cOpacity ) ) cOpacity.setValue( cOpacity.initialValue );
if ( isDislayEl( cColor ) && !isReadOnlyEl( cColor ) ) cColor.setValue( cColor.initialValue );
if ( positionDefault.w !== undefined ) {
if ( positionDefault.w.r !== undefined )
cColor.setValue( '#' +
new THREE.Color( positionDefault.w.r, positionDefault.w.g, positionDefault.w.b ).getHexString() );
else if ( typeof positionDefault.w === "function" ) {
setValue( cW, positionDefault.w( t ) );
return;
} else if ( positionDefault.w.func ) {
setValue( cW, positionDefault.w.func( t ) );
return;
}
const float = parseFloat( positionDefault.w );
if ( float === positionDefault.w ) {
if ( isDislayEl( cW ) ) setValue( cW, positionDefault.w );
} else if ( typeof positionDefault.w === "string") {
setValue( cW, Player.execFunc( positionDefault , 'w', options.time, options ) );
return;
} else console.error( 'Restore default local position: Invalid W axis.' );
} else cColor.setValue( cColor.initialValue );
},
}, 'defaultF' );
dat.controllerNameAndTitle( cRestoreDefaultLocalPosition, lang.defaultButton, lang.defaultLocalPositionTitle );
funcFolder = new functionsFolder( fPoint, function ( func, axisName, value ) {
const mesh = getMesh(),
index = cPoints.__select.options.selectedIndex - 1,
funcs = mesh.userData.player.arrayFuncs[index];
funcs[axisName] = func;
var parent = mesh.parent, t = 0;
while ( parent ) {
if ( parent.userData.t ) {
t = parent.userData.t;
break;
}
parent = parent.parent;
}
var controller;
switch ( axisName ) {
case 'x':
controller = cX;
break;
case 'y':
controller = cY;
break;
case 'z':
controller = cZ;
break;
case 'w':
if ( func instanceof THREE.Color ) {
cColor.setValue( '#' + func.getHexString() );
return;
}
controller = cW;
break;
default: console.error( 'GuiSelectPoint new functionsFolder onFinishChange: axisName = ' + axisName );
return;
}
setValue( controller, Player.execFunc( funcs, axisName, t, options ) );
if ( funcs.controllers ) {
//обновить органы управления на веб странице
const controllerObject = funcs.controllers[axisName];
if ( controllerObject && controllerObject.func && controllerObject.func.controller )
controllerObject.func.controller.value = value;
}
}, options, { x: '', y: '', z: '', w: '' } );
}
/**
* get frustum points.
*/
this.getFrustumPoints = function () { return cFrustumPoints; }
return this;
}
}
export default GuiSelectPoint;