Выделение нескольких объектов, клонирование

3+

Реализовано выделение нескольких объектов. При этом центр выделения устанавливается в геометрическом центре масс, так называемом барицентре, который определяется следующим образом

Барицентр определяется сложением векторов положения точек, входящих с группу и делится на количество точек.

function findBaryCenter( points) {

            if ( !Array.isArray( points) ) return null;

            let pointsCount = points.length;

            let resultVector = points.reduce( (sum, current) => sum.add(current) );
            let baryCenter = resultVector.divideScalar( pointsCount );

            return baryCenter;

        }

Для реализации множественного выделения создана группа в объекте currentselection и добавлена в сцену момент инициализации сцены

this.currentSelection = {
	object: null,
	objectOwnColor: null,
	multiselection: new Group()
};
scene.add( this.currentSelection.multiselection );

Для инициализации множественного выделения нужно удерживать нажатой клавишу Ctrl.

Сначала проверяем, выделен ли какой-то объект. Если выделен, то добавляем его в группу multiselection.

if ( event.ctrlKey ){

        if ( scope.currentSelection.object ) {

	taEntities.removeWireframeAndBoundingBox( scope.currentSelection.object );
	scope.currentSelection.multiselection.attach( scope.currentSelection.object );
	scope.currentSelection.object = null;

	}

Создаем массив из объектов, входящих в группу multiselection и проверяем, есть выделяемый объект в этой группе. Если нет — то добавляем его в группу, если да — то удаляем его из выделения:

let arrayObjectsInSelection = [];

arrayObjectsInSelection = arrayObjectsInSelection.concat( scope.currentSelection.multiselection.children );

if ( arrayObjectsInSelection.includes( objectToSelect )) {

	taEntities.removeWireframeAndBoundingBox( objectToSelect );

	arrayObjectsInSelection.splice( arrayObjectsInSelection.indexOf( objectToSelect), 1 );

}
else {

	arrayObjectsInSelection.push( objectToSelect );

}

Возвращаем все объекты из группы в сцену ,чтобы установить новое положение группы, переместив его в геометрический центр масс. Для этого определяем положение всех объектов выделения

scope.returnObjectsToScene();

let centerPoints = [];

arrayObjectsInSelection.forEach( element => {

      centerPoints.push( element.position.clone() )

})

let baryCenter = findBaryCenter( centerPoints ).clone();

scope.currentSelection.multiselection.position.set( baryCenter.x, baryCenter.y, baryCenter.z);

Возвращаем все объекты из сцены в группу

for (let i = arrayObjectsInSelection.length - 1; i >= 0; i--) {

	scope.currentSelection.multiselection.attach( arrayObjectsInSelection[i] );

	arrayObjectsInSelection[i].add( taEntities.createBoundingBox( arrayObjectsInSelection[i] ) );
		
}

При этом используем метод .attach, который, в отличие от .add, добавляет объекты в группу с учетом мировых координат. То есть, если у объекта координаты ( 0, 0, 0 ), а у группы ( 10, 10 , 10 ), то .add добавит в точку размещения группы, то есть в ( 10, 10 , 10 ), а .attach оставит объект в точке ( 0, 0, 0 ), но для группы в локальных координатах группы он окажется в с координатами ( -10, -10, -10 ).

Функции возврата объектов в сцену и сброса Multyselection выглядит следующим образом

this.returnObjectsToScene = function(){
	if (scope.currentSelection.multiselection.children.length > 0 ){

		let lengthArray = scope.currentSelection.multiselection.children.length;

		for (let i = lengthArray - 1; i >= 0; i--) {

			taEntities.removeWireframeAndBoundingBox( scope.currentSelection.multiselection.children[i] );

			scene.attach( scope.currentSelection.multiselection.children[i] )

		}

	}
     scope.transformControls.detach( scope.currentSelection.multiselection )
}

this.resetMultyselection = function() {

	scope.currentSelection.multiselection.children = [];
	scope.currentSelection.multiselection.position.set( 0, 0, 0);
	scope.currentSelection.multiselection.scale.set( 1, 1, 1);
	scope.currentSelection.multiselection.rotation.set( 0, 0, 0);

}

Клонирование нескольких выделенных объектов. Можно клонировать клавишей «с» или пунктом меню Edit

if ( ta_scene.currentSelection.multiselection.children.length > 0 ){

	let lengthArray = ta_scene.currentSelection.multiselection.children.length;

	let multuSelectionArray = ta_scene.currentSelection.multiselection.children;

	for (let i = lengthArray - 1; i >= 0; i--) {

		this.removeWireframeAndBoundingBox( multuSelectionArray[i] );

		let id = multuSelectionArray[i].id;

		let copiedObject = ta_scene.scene.getObjectById( id );

		ta_scene.scene.attach( multuSelectionArray[i]);

		let newObject = copiedObject.clone();

		ta_scene.scene.add( newObject );

		ta_scene.selectableObjects.push( newObject );

		ta_scene.currentSelection.multiselection.attach( copiedObject );

		copiedObject.add( this.createBoundingBox( copiedObject ) );

	}

}

Выявилась проблема, описанная мной на форуме three.js.

Изменил кнопки трансформации на radio, чтобы было видно, какой режим включен в данный момент. Кнопку Drag сделал checkbox, теперь режим перетаскивания может работать параллельно с другими режимами трансформации. При этом выявлена проблема, описанная мной в issue в GitHub репозитории three.js

Переключение режимов трансформации можно клавишами «m» — move, «r» — rotate, «s» — scale, «d» — drag.

Функции переключения режимов перенес в отдельный модуль Actions.js.

Три дня потратил на изучение Webpack, настроил проект, теперь он собирается WebPack.

3+