Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion editor/js/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ function Config() {
'settings/shortcuts/undo': 'z',
'settings/shortcuts/focus': 'f',
'settings/shortcuts/perspective': 'p',
'settings/shortcuts/orthographic': 'o'
'settings/shortcuts/orthographic': 'o',
'settings/shortcuts/selectAll': 'a'
};

if ( window.localStorage[ name ] === undefined ) {
Expand Down
201 changes: 186 additions & 15 deletions editor/js/Selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import * as THREE from 'three';
const mouse = new THREE.Vector2();
const raycaster = new THREE.Raycaster();

const _box = new THREE.Box3();
const _vector = new THREE.Vector3();
const _deltaMatrix = new THREE.Matrix4();
const _objectMatrix = new THREE.Matrix4();
const _parentMatrixInverse = new THREE.Matrix4();

class Selector {

constructor( editor ) {
Expand All @@ -12,9 +18,18 @@ class Selector {
this.editor = editor;
this.signals = signals;

this.selection = [];

// an intermediate group used as pivot when transforming multiple objects

this.group = new THREE.Group();

this._groupMatrixWorldInverse = new THREE.Matrix4();
this._memberStates = [];

// signals

signals.intersectionsDetected.add( ( intersects ) => {
signals.intersectionsDetected.add( ( intersects, shiftKey ) => {

if ( intersects.length > 0 ) {

Expand All @@ -40,23 +55,53 @@ class Selector {

}

// Cycle through objects if the first one is already selected

const index = objects.indexOf( editor.selected );
if ( shiftKey === true && this.selection.length > 0 && editor.selected !== editor.scene && editor.selected !== editor.camera ) {

if ( index !== - 1 && index < objects.length - 1 ) {
// Shift-click toggles membership in the current selection

this.select( objects[ index + 1 ] );
this.toggle( objects[ 0 ] );

} else {

this.select( objects[ 0 ] );
// Cycle through objects if the first one is already selected

const index = objects.indexOf( editor.selected );

if ( index !== - 1 && index < objects.length - 1 ) {

this.select( objects[ index + 1 ] );

} else {

this.select( objects[ 0 ] );

}

}

} else {

this.select( null );
if ( shiftKey !== true ) this.select( null ); // keep the selection when shift-clicking empty space

}

} );

signals.objectChanged.add( ( object ) => {

if ( this.selection.length < 2 ) return;

if ( object === this.group ) {

// the group has been transformed (e.g. via TransformControls), sync its members

this.applyGroupTransform();

} else if ( this.selection.indexOf( object ) !== - 1 ) {

// a member has been changed independently (e.g. via undo/redo), re-anchor the pivot

this.updateGroup();

}

Expand Down Expand Up @@ -94,22 +139,132 @@ class Selector {

}

getSelectionBox( target ) {

target.makeEmpty();

for ( let i = 0; i < this.selection.length; i ++ ) {

const object = this.selection[ i ];

target.expandByObject( object, true );
target.expandByPoint( object.getWorldPosition( _vector ) ); // objects without geometry (e.g. lights)

}

return target;

}

select( object ) {

if ( this.editor.selected === object ) return;
this.setSelection( object === null ? [] : [ object ] );

let uuid = null;
}

if ( object !== null ) {
toggle( object ) {

uuid = object.uuid;
const selection = this.selection.slice();

const index = selection.indexOf( object );

if ( index === - 1 ) {

selection.push( object );

} else {

selection.splice( index, 1 );

}

this.editor.selected = object;
this.editor.config.setKey( 'selected', uuid );
this.setSelection( selection );

}

setSelection( objects ) {

const editor = this.editor;

const hadGroupSelection = this.group.parent !== null;

this.selection = objects.slice();

this.signals.objectSelected.dispatch( object );
if ( objects.length > 1 ) {

editor.sceneHelpers.add( this.group );
this.updateGroup();

editor.selected = null;
editor.config.setKey( 'selected', null );

this.signals.objectSelected.dispatch( null );

} else {

const object = ( objects.length === 1 ) ? objects[ 0 ] : null;

editor.sceneHelpers.remove( this.group );

if ( editor.selected === object && hadGroupSelection === false ) return;

editor.selected = object;
editor.config.setKey( 'selected', ( object !== null ) ? object.uuid : null );

this.signals.objectSelected.dispatch( object );

}

}

updateGroup() {

const group = this.group;

// use the center of the selection's AABB as pivot point

this.getSelectionBox( _box ).getCenter( group.position );
group.quaternion.identity();
group.scale.set( 1, 1, 1 );
group.updateWorldMatrix();

this._groupMatrixWorldInverse.copy( group.matrixWorld ).invert();

// members with a selected ancestor are updated by that ancestor's transform

const members = this.selection.filter( ( object ) => hasSelectedAncestor( object, this.selection ) === false );

this._memberStates = members.map( ( object ) => ( { object: object, matrixWorld: object.matrixWorld.clone() } ) );

}

applyGroupTransform() {

const group = this.group;

group.updateWorldMatrix();

_deltaMatrix.multiplyMatrices( group.matrixWorld, this._groupMatrixWorldInverse );

const states = this._memberStates;

for ( let i = 0; i < states.length; i ++ ) {

const object = states[ i ].object;
const parent = object.parent;

_objectMatrix.multiplyMatrices( _deltaMatrix, states[ i ].matrixWorld );
_parentMatrixInverse.copy( parent.matrixWorld ).invert();
_objectMatrix.premultiply( _parentMatrixInverse );
_objectMatrix.decompose( object.position, object.quaternion, object.scale );

object.updateWorldMatrix( false, true );

const helper = this.editor.helpers[ object.id ];

if ( helper !== undefined && helper.isSkeletonHelper !== true ) helper.update();

}

}

Expand All @@ -121,4 +276,20 @@ class Selector {

}

function hasSelectedAncestor( object, selection ) {

let parent = object.parent;

while ( parent !== null ) {

if ( selection.indexOf( parent ) !== - 1 ) return true;

parent = parent.parent;

}

return false;

}

export { Selector };
78 changes: 67 additions & 11 deletions editor/js/Sidebar.Scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,28 @@ function SidebarScene( editor ) {

const outliner = new UIOutliner( editor );
outliner.setId( 'outliner' );
outliner.onChange( function () {
outliner.onChange( function ( event ) {

const id = parseInt( outliner.getValue() );

if ( event.shiftKey === true && editor.selector.selection.length > 0 && editor.selected !== editor.scene && editor.selected !== editor.camera ) {

const object = ( id === editor.camera.id ) ? editor.camera : editor.scene.getObjectById( id );

// scene and main camera can't be part of a group selection

if ( object !== editor.scene && object !== editor.camera ) {

editor.selector.toggle( object );
return;

}

}

ignoreObjectSelectedSignal = true;

editor.selectById( parseInt( outliner.getValue() ) );
editor.selectById( id );

ignoreObjectSelectedSignal = false;

Expand Down Expand Up @@ -414,6 +431,18 @@ function SidebarScene( editor ) {

outliner.setValue( editor.selected.id );

} else {

const selection = editor.selector.selection;

if ( selection.length > 1 ) {

// restore highlights of a group selection (e.g. after a graph change)

outliner.setValues( selection.map( ( object ) => object.id ) );

}

}

backgroundType.setValue( editor.backgroundType );
Expand Down Expand Up @@ -542,30 +571,57 @@ function SidebarScene( editor ) {
} );


function expandAncestors( object ) {

let needsRefresh = false;
let parent = object.parent;

while ( parent !== editor.scene ) {

if ( nodeStates.get( parent ) !== true ) {

nodeStates.set( parent, true );
needsRefresh = true;

}

parent = parent.parent;

}

return needsRefresh;

}

signals.objectSelected.add( function ( object ) {

if ( ignoreObjectSelectedSignal === true ) return;

if ( object !== null && object.parent !== null ) {
const selection = editor.selector.selection;

let needsRefresh = false;
let parent = object.parent;
if ( selection.length > 1 ) {

while ( parent !== editor.scene ) {
// highlight all members of a group selection

if ( nodeStates.get( parent ) !== true ) {
let needsRefresh = false;

nodeStates.set( parent, true );
needsRefresh = true;
const values = [];

}
for ( let i = 0; i < selection.length; i ++ ) {

parent = parent.parent;
values.push( selection[ i ].id );
needsRefresh = expandAncestors( selection[ i ] ) || needsRefresh;

}

if ( needsRefresh ) refreshUI();

outliner.setValues( values );

} else if ( object !== null && object.parent !== null ) {

if ( expandAncestors( object ) ) refreshUI();

outliner.setValue( object.id );

} else {
Expand Down
Loading