2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 #include "debugging/debugging.h"
29 #include "iselection.h"
34 #include "stream/stringstream.h"
35 #include "signal/isignal.h"
36 #include "shaderlib.h"
39 #include "gtkutil/idledraw.h"
40 #include "gtkutil/dialog.h"
41 #include "gtkutil/widget.h"
42 #include "brushmanip.h"
45 #include "patchmanip.h"
46 #include "patchdialog.h"
47 #include "selection.h"
48 #include "texwindow.h"
50 #include "mainframe.h"
53 #include "entityinspector.h"
57 select_workzone_t g_select_workzone;
61 Loops over all selected brushes and stores their
62 world AABBs in the specified array.
64 class CollectSelectedBrushesBounds : public SelectionSystem::Visitor
66 AABB* m_bounds; // array of AABBs
67 Unsigned m_max; // max AABB-elements in array
68 Unsigned& m_count; // count of valid AABBs stored in array
71 CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count )
78 void visit( scene::Instance& instance ) const {
79 ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" );
81 // stop if the array is already full
82 if ( m_count == m_max ) {
86 Selectable* selectable = Instance_getSelectable( instance );
87 if ( ( selectable != 0 )
88 && instance.isSelected() ) {
90 if ( Instance_getBrush( instance ) != 0 ) {
91 m_bounds[m_count] = instance.worldAABB();
99 Selects all objects that intersect one of the bounding AABBs.
100 The exact intersection-method is specified through TSelectionPolicy
102 template<class TSelectionPolicy>
103 class SelectByBounds : public scene::Graph::Walker
105 AABB* m_aabbs; // selection aabbs
106 Unsigned m_count; // number of aabbs in m_aabbs
107 TSelectionPolicy policy; // type that contains a custom intersection method aabb<->aabb
110 SelectByBounds( AABB* aabbs, Unsigned count )
115 bool pre( const scene::Path& path, scene::Instance& instance ) const {
116 if( path.top().get().visible() ){
117 Selectable* selectable = Instance_getSelectable( instance );
120 Entity* entity = Node_getEntity( path.top() );
122 if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
127 if ( ( path.size() > 1 ) &&
128 ( !path.top().get().isRoot() ) &&
129 ( selectable != 0 ) &&
130 ( !node_is_group( path.top() ) )
132 for ( Unsigned i = 0; i < m_count; ++i )
134 if ( policy.Evaluate( m_aabbs[i], instance ) ) {
135 selectable->setSelected( true );
148 Performs selection operation on the global scenegraph.
149 If delete_bounds_src is true, then the objects which were
150 used as source for the selection aabbs will be deleted.
152 static void DoSelection( bool delete_bounds_src = true ){
153 if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
154 // we may not need all AABBs since not all selected objects have to be brushes
155 const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
156 AABB* aabbs = new AABB[max];
159 CollectSelectedBrushesBounds collector( aabbs, max, count );
160 GlobalSelectionSystem().foreachSelected( collector );
162 // nothing usable in selection
168 // delete selected objects
169 if ( delete_bounds_src ) { // see deleteSelection
170 UndoableCommand undo( "deleteSelected" );
174 // select objects with bounds
175 GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
184 SelectionPolicy for SelectByBounds
185 Returns true if box and the AABB of instance intersect
187 class SelectionPolicy_Touching
190 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
191 const AABB& other( instance.worldAABB() );
192 for ( Unsigned i = 0; i < 3; ++i )
194 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
203 SelectionPolicy for SelectByBounds
204 Returns true if the AABB of instance is inside box
206 class SelectionPolicy_Inside
209 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
210 const AABB& other( instance.worldAABB() );
211 for ( Unsigned i = 0; i < 3; ++i )
213 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
221 class DeleteSelected : public scene::Graph::Walker
223 mutable bool m_remove;
224 mutable bool m_removedChild;
227 : m_remove( false ), m_removedChild( false ){
229 bool pre( const scene::Path& path, scene::Instance& instance ) const {
230 m_removedChild = false;
232 Selectable* selectable = Instance_getSelectable( instance );
234 && selectable->isSelected()
236 && !path.top().get().isRoot() ) {
239 return false; // dont traverse into child elements
243 void post( const scene::Path& path, scene::Instance& instance ) const {
245 if ( m_removedChild ) {
246 m_removedChild = false;
248 // delete empty entities
249 Entity* entity = Node_getEntity( path.top() );
251 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
252 && Node_getTraversable( path.top() )->empty() ) {
253 Path_deleteTop( path );
257 // node should be removed
259 if ( Node_isEntity( path.parent() ) != 0 ) {
260 m_removedChild = true;
264 Path_deleteTop( path );
269 void Scene_DeleteSelected( scene::Graph& graph ){
270 graph.traverse( DeleteSelected() );
274 void Select_Delete( void ){
275 Scene_DeleteSelected( GlobalSceneGraph() );
278 class InvertSelectionWalker : public scene::Graph::Walker
280 SelectionSystem::EMode m_mode;
281 SelectionSystem::EComponentMode m_compmode;
282 mutable Selectable* m_selectable;
284 InvertSelectionWalker( SelectionSystem::EMode mode, SelectionSystem::EComponentMode compmode )
285 : m_mode( mode ), m_compmode( compmode ), m_selectable( 0 ){
287 bool pre( const scene::Path& path, scene::Instance& instance ) const {
288 if( !path.top().get().visible() ){
292 Selectable* selectable = Instance_getSelectable( instance );
296 case SelectionSystem::eEntity:
297 if ( Node_isEntity( path.top() ) != 0 ) {
298 m_selectable = path.top().get().visible() ? selectable : 0;
301 case SelectionSystem::ePrimitive:
302 m_selectable = path.top().get().visible() ? selectable : 0;
304 case SelectionSystem::eComponent:
305 BrushInstance* brushinstance = Instance_getBrush( instance );
306 if( brushinstance != 0 ){
307 if( brushinstance->isSelected() )
308 brushinstance->invertComponentSelection( m_compmode );
311 PatchInstance* patchinstance = Instance_getPatch( instance );
312 if( patchinstance != 0 && m_compmode == SelectionSystem::eVertex ){
313 if( patchinstance->isSelected() )
314 patchinstance->invertComponentSelection();
322 void post( const scene::Path& path, scene::Instance& instance ) const {
323 if ( m_selectable != 0 ) {
324 m_selectable->setSelected( !m_selectable->isSelected() );
330 void Scene_Invert_Selection( scene::Graph& graph ){
331 graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode(), GlobalSelectionSystem().ComponentMode() ) );
334 void Select_Invert(){
335 Scene_Invert_Selection( GlobalSceneGraph() );
338 //interesting printings
339 class ExpandSelectionToEntitiesWalker_dbg : public scene::Graph::Walker
341 mutable std::size_t m_depth;
342 NodeSmartReference worldspawn;
344 ExpandSelectionToEntitiesWalker_dbg() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
346 bool pre( const scene::Path& path, scene::Instance& instance ) const {
348 globalOutputStream() << "pre depth_" << m_depth;
349 globalOutputStream() << " path.size()_" << path.size();
350 if ( path.top().get() == worldspawn )
351 globalOutputStream() << " worldspawn";
352 if( path.top().get().isRoot() )
353 globalOutputStream() << " path.top().get().isRoot()";
354 Entity* entity = Node_getEntity( path.top() );
356 globalOutputStream() << " entity!=0";
357 if( entity->isContainer() ){
358 globalOutputStream() << " entity->isContainer()";
360 globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
362 globalOutputStream() << "\n";
363 // globalOutputStream() << "" << ;
364 // globalOutputStream() << "" << ;
365 // globalOutputStream() << "" << ;
366 // globalOutputStream() << "" << ;
369 void post( const scene::Path& path, scene::Instance& instance ) const {
370 globalOutputStream() << "post depth_" << m_depth;
371 globalOutputStream() << " path.size()_" << path.size();
372 if ( path.top().get() == worldspawn )
373 globalOutputStream() << " worldspawn";
374 if( path.top().get().isRoot() )
375 globalOutputStream() << " path.top().get().isRoot()";
376 Entity* entity = Node_getEntity( path.top() );
378 globalOutputStream() << " entity!=0";
379 if( entity->isContainer() ){
380 globalOutputStream() << " entity->isContainer()";
382 globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
384 globalOutputStream() << "\n";
389 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
391 mutable std::size_t m_depth;
392 NodeSmartReference worldspawn;
394 ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
396 bool pre( const scene::Path& path, scene::Instance& instance ) const {
400 // NodeSmartReference me( path.top().get() );
401 // if ( me == worldspawn ) {
405 if ( m_depth == 2 ) { // entity depth
406 // traverse and select children if any one is selected
407 bool beselected = false;
408 if ( instance.childSelected() || instance.isSelected() ) {
410 if( path.top().get() != worldspawn ){
411 Instance_setSelected( instance, true );
414 return Node_getEntity( path.top() )->isContainer() && beselected;
416 else if ( m_depth == 3 ) { // primitive depth
417 Instance_setSelected( instance, true );
422 void post( const scene::Path& path, scene::Instance& instance ) const {
427 void Scene_ExpandSelectionToEntities(){
428 GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
434 void Selection_UpdateWorkzone(){
435 if ( GlobalSelectionSystem().countSelected() != 0 ) {
436 Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
439 typedef FreeCaller<void(), Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
441 IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
444 const select_workzone_t& Select_getWorkZone(){
445 g_idleWorkzone.flush();
446 return g_select_workzone;
449 void UpdateWorkzone_ForSelection(){
450 g_idleWorkzone.queueDraw();
453 // update the workzone to the current selection
454 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
455 //if ( selectable.isSelected() ) {
456 UpdateWorkzone_ForSelection();
460 void Select_SetShader( const char* shader ){
461 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
462 Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
463 Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
465 Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
468 void Select_SetShader_Undo( const char* shader ){
469 if ( GlobalSelectionSystem().countSelectedComponents() != 0 || GlobalSelectionSystem().countSelected() != 0 ) {
470 UndoableCommand undo( "textureNameSetSelected" );
471 Select_SetShader( shader );
475 void Select_SetTexdef( const TextureProjection& projection ){
476 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
477 Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
479 Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
482 void Select_SetFlags( const ContentsFlagsValue& flags ){
483 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
484 Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
486 Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
489 void Select_GetBounds( Vector3& mins, Vector3& maxs ){
491 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
492 maxs = vector3_added( bounds.origin, bounds.extents );
493 mins = vector3_subtracted( bounds.origin, bounds.extents );
496 void Select_GetMid( Vector3& mid ){
498 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
499 mid = vector3_snapped( bounds.origin );
503 void Select_FlipAxis( int axis ){
504 Vector3 flip( 1, 1, 1 );
506 GlobalSelectionSystem().scaleSelected( flip );
510 void Select_Scale( float x, float y, float z ){
511 GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
527 inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
531 if ( sign == eSignPositive ) {
532 return matrix4_rotation_for_sincos_x( 1, 0 );
536 return matrix4_rotation_for_sincos_x( -1, 0 );
539 if ( sign == eSignPositive ) {
540 return matrix4_rotation_for_sincos_y( 1, 0 );
544 return matrix4_rotation_for_sincos_y( -1, 0 );
546 default: //case eAxisZ:
547 if ( sign == eSignPositive ) {
548 return matrix4_rotation_for_sincos_z( 1, 0 );
552 return matrix4_rotation_for_sincos_z( -1, 0 );
557 inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
558 matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
561 inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
562 matrix4_translate_by_vec3( matrix, pivotpoint );
563 matrix4_rotate_by_axis90( matrix, axis, sign );
564 matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
567 inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
572 if ( sign == eSignPositive ) {
573 return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
577 return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
580 if ( sign == eSignPositive ) {
581 return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
585 return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
587 default: //case eAxisZ:
588 if ( sign == eSignPositive ) {
589 return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
593 return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
597 quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
601 void Select_RotateAxis( int axis, float deg ){
602 if ( fabs( deg ) == 90.f ) {
603 GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ), true );
610 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ), false );
613 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ), false );
616 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ), false );
623 void Select_ShiftTexture( float x, float y ){
624 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
625 Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
626 Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
628 //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
629 Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
632 void Select_ScaleTexture( float x, float y ){
633 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
634 Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
635 Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
637 Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
640 void Select_RotateTexture( float amt ){
641 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
642 Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
643 Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
645 Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
648 // TTimo modified to handle shader architecture:
649 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
650 void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
651 if ( !texdef_name_valid( pFind ) ) {
652 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
655 if ( !texdef_name_valid( pReplace ) ) {
656 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
660 StringOutputStream command;
661 command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
662 UndoableCommand undo( command.c_str() );
665 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
666 Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
667 Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
669 Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
673 Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
674 Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
678 typedef std::vector<const char*> PropertyValues;
680 bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
681 for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
683 if ( string_equal( str, *i ) ) {
690 class EntityFindByPropertyValueWalker : public scene::Graph::Walker
692 const PropertyValues& m_propertyvalues;
694 const NodeSmartReference worldspawn;
696 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
697 : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
699 bool pre( const scene::Path& path, scene::Instance& instance ) const {
700 if( !path.top().get().visible() ){
704 if ( path.top().get() == worldspawn ) {
708 Entity* entity = Node_getEntity( path.top() );
710 if( propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
711 Instance_getSelectable( instance )->setSelected( true );
716 else if( path.size() > 2 && !path.top().get().isRoot() ){
717 Selectable* selectable = Instance_getSelectable( instance );
718 if( selectable != 0 )
719 selectable->setSelected( true );
725 void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
726 graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
729 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
731 PropertyValues& m_propertyvalues;
733 const NodeSmartReference worldspawn;
735 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
736 : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
738 bool pre( const scene::Path& path, scene::Instance& instance ) const {
739 Entity* entity = Node_getEntity( path.top() );
741 if( path.top().get() != worldspawn ){
742 Selectable* selectable = Instance_getSelectable( instance );
743 if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
744 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
745 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
755 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
757 PropertyValues& m_propertyvalues;
759 mutable bool m_selected_children;
760 const NodeSmartReference worldspawn;
762 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
763 : m_propertyvalues( propertyvalues ), m_prop( prop ), m_selected_children( false ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
765 bool pre( const scene::Path& path, scene::Instance& instance ) const {
766 Selectable* selectable = Instance_getSelectable( instance );
768 && selectable->isSelected() ) {
769 Entity* entity = Node_getEntity( path.top() );
771 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
772 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
777 m_selected_children = true;
782 void post( const scene::Path& path, scene::Instance& instance ) const {
783 Entity* entity = Node_getEntity( path.top() );
784 if( entity != 0 && m_selected_children ){
785 m_selected_children = false;
786 if( path.top().get() == worldspawn )
788 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
789 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
795 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
796 graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
799 void Select_AllOfType(){
800 if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
801 if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
802 GlobalSelectionSystem().setSelectedAllComponents( false );
803 Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
808 PropertyValues propertyvalues;
809 const char *prop = EntityInspector_getCurrentKey();
810 if ( !prop || !*prop ) {
813 Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
814 GlobalSelectionSystem().setSelectedAll( false );
815 if ( !propertyvalues.empty() ) {
816 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
820 Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
821 Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
826 void Select_Inside( void ){
827 SelectByBounds<SelectionPolicy_Inside>::DoSelection();
830 void Select_Touching( void ){
831 SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
834 void Select_FitTexture( float horizontal, float vertical ){
835 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
836 Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
838 Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
844 #include "commands.h"
847 inline void hide_node( scene::Node& node, bool hide ){
849 ? node.enable( scene::Node::eHidden )
850 : node.disable( scene::Node::eHidden );
853 bool g_nodes_be_hidden = false;
855 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_hidden_caller( g_nodes_be_hidden );
857 ToggleItem g_hidden_item( g_hidden_caller );
859 class HideSelectedWalker : public scene::Graph::Walker
863 HideSelectedWalker( bool hide )
866 bool pre( const scene::Path& path, scene::Instance& instance ) const {
867 Selectable* selectable = Instance_getSelectable( instance );
869 && selectable->isSelected() ) {
870 g_nodes_be_hidden = m_hide;
871 hide_node( path.top(), m_hide );
877 void Scene_Hide_Selected( bool hide ){
878 GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
882 Scene_Hide_Selected( true );
888 GlobalSelectionSystem().setSelectedAll( false );
889 g_hidden_item.update();
893 class HideAllWalker : public scene::Graph::Walker
897 HideAllWalker( bool hide )
900 bool pre( const scene::Path& path, scene::Instance& instance ) const {
901 hide_node( path.top(), m_hide );
906 void Scene_Hide_All( bool hide ){
907 GlobalSceneGraph().traverse( HideAllWalker( hide ) );
910 void Select_ShowAllHidden(){
911 Scene_Hide_All( false );
913 g_nodes_be_hidden = false;
914 g_hidden_item.update();
918 void Selection_Flipx(){
919 UndoableCommand undo( "mirrorSelected -axis x" );
920 Select_FlipAxis( 0 );
923 void Selection_Flipy(){
924 UndoableCommand undo( "mirrorSelected -axis y" );
925 Select_FlipAxis( 1 );
928 void Selection_Flipz(){
929 UndoableCommand undo( "mirrorSelected -axis z" );
930 Select_FlipAxis( 2 );
933 void Selection_Rotatex(){
934 UndoableCommand undo( "rotateSelected -axis x -angle -90" );
935 Select_RotateAxis( 0,-90 );
938 void Selection_Rotatey(){
939 UndoableCommand undo( "rotateSelected -axis y -angle 90" );
940 Select_RotateAxis( 1, 90 );
943 void Selection_Rotatez(){
944 UndoableCommand undo( "rotateSelected -axis z -angle -90" );
945 Select_RotateAxis( 2,-90 );
947 #include "xywindow.h"
948 void Selection_FlipHorizontally(){
949 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
962 void Selection_FlipVertically(){
963 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
976 void Selection_RotateClockwise(){
977 UndoableCommand undo( "rotateSelected Clockwise 90" );
978 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
982 Select_RotateAxis( 2, -90 );
985 Select_RotateAxis( 1, 90 );
988 Select_RotateAxis( 0, -90 );
993 void Selection_RotateAnticlockwise(){
994 UndoableCommand undo( "rotateSelected Anticlockwise 90" );
995 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
999 Select_RotateAxis( 2, 90 );
1002 Select_RotateAxis( 1, -90 );
1005 Select_RotateAxis( 0, 90 );
1013 void Select_registerCommands(){
1014 GlobalCommands_insert( "ShowHidden", makeCallbackF( Select_ShowAllHidden ), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
1015 GlobalToggles_insert( "HideSelected", makeCallbackF( HideSelected ), ToggleItem::AddCallbackCaller( g_hidden_item ), Accelerator( 'H' ) );
1017 GlobalCommands_insert( "MirrorSelectionX", makeCallbackF( Selection_Flipx ) );
1018 GlobalCommands_insert( "RotateSelectionX", makeCallbackF( Selection_Rotatex ) );
1019 GlobalCommands_insert( "MirrorSelectionY", makeCallbackF( Selection_Flipy ) );
1020 GlobalCommands_insert( "RotateSelectionY", makeCallbackF( Selection_Rotatey ) );
1021 GlobalCommands_insert( "MirrorSelectionZ", makeCallbackF( Selection_Flipz ) );
1022 GlobalCommands_insert( "RotateSelectionZ", makeCallbackF( Selection_Rotatez ) );
1024 GlobalCommands_insert( "MirrorSelectionHorizontally", makeCallbackF( Selection_FlipHorizontally ) );
1025 GlobalCommands_insert( "MirrorSelectionVertically", makeCallbackF( Selection_FlipVertically ) );
1027 GlobalCommands_insert( "RotateSelectionClockwise", makeCallbackF( Selection_RotateClockwise ) );
1028 GlobalCommands_insert( "RotateSelectionAnticlockwise", makeCallbackF( Selection_RotateAnticlockwise ) );
1032 void Nudge( int nDim, float fNudge ){
1033 Vector3 translate( 0, 0, 0 );
1034 translate[nDim] = fNudge;
1036 GlobalSelectionSystem().translateSelected( translate );
1039 void Selection_NudgeZ( float amount ){
1040 StringOutputStream command;
1041 command << "nudgeSelected -axis z -amount " << amount;
1042 UndoableCommand undo( command.c_str() );
1047 void Selection_MoveDown(){
1048 Selection_NudgeZ( -GetGridSize() );
1051 void Selection_MoveUp(){
1052 Selection_NudgeZ( GetGridSize() );
1055 void SceneSelectionChange( const Selectable& selectable ){
1056 SceneChangeNotify();
1059 SignalHandlerId Selection_boundsChanged;
1061 void Selection_construct(){
1062 typedef FreeCaller<void(const Selectable&), SceneSelectionChange> SceneSelectionChangeCaller;
1063 GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
1064 typedef FreeCaller<void(const Selectable&), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
1065 GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
1066 typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
1067 Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
1070 void Selection_destroy(){
1071 GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
1075 #include "gtkdlgs.h"
1076 #include <gdk/gdkkeysyms.h>
1079 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
1081 return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
1083 return quaternion_multiplied_by_quaternion(
1084 quaternion_multiplied_by_quaternion(
1085 quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
1086 quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
1088 quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
1091 double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
1092 double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
1093 double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
1094 double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
1095 double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
1096 double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
1099 cz * cy * sx - sz * sy * cx,
1100 cz * sy * cx + sz * cy * sx,
1101 sz * cy * cx - cz * sy * sx,
1102 cz * cy * cx + sz * sy * sx
1109 ui::SpinButton x{ui::null};
1110 ui::SpinButton y{ui::null};
1111 ui::SpinButton z{ui::null};
1112 ui::Window window{ui::null};
1115 static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
1118 gtk_spin_button_update ( rotateDialog->x );
1119 gtk_spin_button_update ( rotateDialog->y );
1120 gtk_spin_button_update ( rotateDialog->z );
1121 eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
1122 eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
1123 eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
1125 StringOutputStream command;
1126 command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
1127 UndoableCommand undo( command.c_str() );
1129 GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ), false );
1133 static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog ){
1134 rotateDialog->window.hide();
1136 gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
1137 gtk_spin_button_set_value( rotateDialog->y, 0.0f );
1138 gtk_spin_button_set_value( rotateDialog->z, 0.0f );
1143 static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
1144 rotatedlg_apply( widget, rotateDialog );
1145 // rotatedlg_cancel( widget, rotateDialog );
1146 rotateDialog->window.hide();
1150 static gboolean rotatedlg_delete( ui::Widget widget, GdkEventAny *event, RotateDialog* rotateDialog ){
1151 rotatedlg_cancel( widget, rotateDialog );
1155 RotateDialog g_rotate_dialog;
1157 if ( !g_rotate_dialog.window ) {
1158 g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
1160 auto accel = ui::AccelGroup(ui::New);
1161 g_rotate_dialog.window.add_accel_group( accel );
1164 auto hbox = create_dialog_hbox( 4, 4 );
1165 g_rotate_dialog.window.add(hbox);
1167 auto table = create_dialog_table( 3, 2, 4, 4 );
1168 hbox.pack_start( table, TRUE, TRUE, 0 );
1170 ui::Widget label = ui::Label( " X " );
1172 table.attach(label, {0, 1, 0, 1}, {0, 0});
1175 ui::Widget label = ui::Label( " Y " );
1177 table.attach(label, {0, 1, 1, 2}, {0, 0});
1180 ui::Widget label = ui::Label( " Z " );
1182 table.attach(label, {0, 1, 2, 3}, {0, 0});
1185 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1186 auto spin = ui::SpinButton( adj, 1, 1 );
1188 table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1189 spin.dimensions(64, -1);
1190 gtk_spin_button_set_wrap( spin, TRUE );
1192 gtk_widget_grab_focus( spin );
1194 g_rotate_dialog.x = spin;
1197 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1198 auto spin = ui::SpinButton( adj, 1, 1 );
1200 table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1201 spin.dimensions(64, -1);
1202 gtk_spin_button_set_wrap( spin, TRUE );
1204 g_rotate_dialog.y = spin;
1207 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1208 auto spin = ui::SpinButton( adj, 1, 1 );
1210 table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1211 spin.dimensions(64, -1);
1212 gtk_spin_button_set_wrap( spin, TRUE );
1214 g_rotate_dialog.z = spin;
1218 auto vbox = create_dialog_vbox( 4 );
1219 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1221 auto button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
1222 vbox.pack_start( button, FALSE, FALSE, 0 );
1223 widget_make_default( button );
1224 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1227 auto button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
1228 vbox.pack_start( button, FALSE, FALSE, 0 );
1229 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1232 auto button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
1233 vbox.pack_start( button, FALSE, FALSE, 0 );
1239 g_rotate_dialog.window.show();
1252 ui::Entry x{ui::null};
1253 ui::Entry y{ui::null};
1254 ui::Entry z{ui::null};
1255 ui::Window window{ui::null};
1258 static gboolean scaledlg_apply( ui::Widget widget, ScaleDialog* scaleDialog ){
1261 sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1262 sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1263 sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1265 StringOutputStream command;
1266 command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1267 UndoableCommand undo( command.c_str() );
1269 Select_Scale( sx, sy, sz );
1274 static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
1275 scaleDialog->window.hide();
1277 scaleDialog->x.text("1.0");
1278 scaleDialog->y.text("1.0");
1279 scaleDialog->z.text("1.0");
1284 static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
1285 scaledlg_apply( widget, scaleDialog );
1286 //scaledlg_cancel( widget, scaleDialog );
1287 scaleDialog->window.hide();
1291 static gboolean scaledlg_delete( ui::Widget widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1292 scaledlg_cancel( widget, scaleDialog );
1296 ScaleDialog g_scale_dialog;
1299 if ( !g_scale_dialog.window ) {
1300 g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", G_CALLBACK(scaledlg_delete ), &g_scale_dialog );
1302 auto accel = ui::AccelGroup(ui::New);
1303 g_scale_dialog.window.add_accel_group( accel );
1306 auto hbox = create_dialog_hbox( 4, 4 );
1307 g_scale_dialog.window.add(hbox);
1309 auto table = create_dialog_table( 3, 2, 4, 4 );
1310 hbox.pack_start( table, TRUE, TRUE, 0 );
1312 ui::Widget label = ui::Label( " X " );
1314 table.attach(label, {0, 1, 0, 1}, {0, 0});
1317 ui::Widget label = ui::Label( " Y " );
1319 table.attach(label, {0, 1, 1, 2}, {0, 0});
1322 ui::Widget label = ui::Label( " Z " );
1324 table.attach(label, {0, 1, 2, 3}, {0, 0});
1327 auto entry = ui::Entry(ui::New);
1330 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1332 g_scale_dialog.x = entry;
1335 auto entry = ui::Entry(ui::New);
1338 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1340 g_scale_dialog.y = entry;
1343 auto entry = ui::Entry(ui::New);
1346 table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1348 g_scale_dialog.z = entry;
1352 auto vbox = create_dialog_vbox( 4 );
1353 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1355 auto button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1356 vbox.pack_start( button, FALSE, FALSE, 0 );
1357 widget_make_default( button );
1358 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1361 auto button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1362 vbox.pack_start( button, FALSE, FALSE, 0 );
1363 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1366 auto button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1367 vbox.pack_start( button, FALSE, FALSE, 0 );
1373 g_scale_dialog.window.show();
1377 class EntityGetSelectedPropertyValuesWalker_nonEmpty : public scene::Graph::Walker
1379 PropertyValues& m_propertyvalues;
1381 const NodeSmartReference worldspawn;
1383 EntityGetSelectedPropertyValuesWalker_nonEmpty( const char *prop, PropertyValues& propertyvalues )
1384 : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
1386 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1387 Entity* entity = Node_getEntity( path.top() );
1389 if( path.top().get() != worldspawn ){
1390 Selectable* selectable = Instance_getSelectable( instance );
1391 if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
1392 const char* keyvalue = entity->getKeyValue( m_prop );
1393 if ( !string_empty( keyvalue ) && !propertyvalues_contain( m_propertyvalues, keyvalue ) ) {
1394 m_propertyvalues.push_back( keyvalue );
1404 void Scene_EntityGetPropertyValues_nonEmpty( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
1405 graph.traverse( EntityGetSelectedPropertyValuesWalker_nonEmpty( prop, propertyvalues ) );
1408 #include "preferences.h"
1410 void Select_ConnectedEntities( bool targeting, bool targets, bool focus ){
1411 PropertyValues target_propertyvalues;
1412 PropertyValues targetname_propertyvalues;
1413 const char *target_prop = "target";
1414 const char *targetname_prop;
1415 if ( g_pGameDescription->mGameType == "doom3" ) {
1416 targetname_prop = "name";
1419 targetname_prop = "targetname";
1423 Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), targetname_prop, targetname_propertyvalues );
1426 Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), target_prop, target_propertyvalues );
1429 if( target_propertyvalues.empty() && targetname_propertyvalues.empty() ){
1430 globalErrorStream() << "SelectConnectedEntities: nothing found\n";
1434 if( !targeting || !targets ){
1435 GlobalSelectionSystem().setSelectedAll( false );
1437 if ( targeting && !targetname_propertyvalues.empty() ) {
1438 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), target_prop, targetname_propertyvalues );
1440 if ( targets && !target_propertyvalues.empty() ) {
1441 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), targetname_prop, target_propertyvalues );
1448 void SelectConnectedEntities(){
1449 Select_ConnectedEntities( true, true, false );