]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/select.cpp
really filter notex (filter fallback)
[xonotic/netradiant.git] / radiant / select.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
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.
11
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.
16
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
20  */
21
22 #include "select.h"
23
24 #include "debugging/debugging.h"
25
26 #include "ientity.h"
27 #include "iselection.h"
28 #include "iundo.h"
29
30 #include <vector>
31
32 #include "stream/stringstream.h"
33 #include "signal/isignal.h"
34 #include "shaderlib.h"
35 #include "scenelib.h"
36
37 #include "gtkutil/idledraw.h"
38 #include "gtkutil/dialog.h"
39 #include "gtkutil/widget.h"
40 #include "brushmanip.h"
41 #include "brush.h"
42 #include "patchmanip.h"
43 #include "patchdialog.h"
44 #include "selection.h"
45 #include "texwindow.h"
46 #include "gtkmisc.h"
47 #include "mainframe.h"
48 #include "grid.h"
49 #include "map.h"
50 #include "entityinspector.h"
51
52
53
54 select_workzone_t g_select_workzone;
55
56
57 /**
58    Loops over all selected brushes and stores their
59    world AABBs in the specified array.
60  */
61 class CollectSelectedBrushesBounds : public SelectionSystem::Visitor
62 {
63 AABB* m_bounds;     // array of AABBs
64 Unsigned m_max;     // max AABB-elements in array
65 Unsigned& m_count;  // count of valid AABBs stored in array
66
67 public:
68 CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count )
69         : m_bounds( bounds ),
70         m_max( max ),
71         m_count( count ){
72         m_count = 0;
73 }
74
75 void visit( scene::Instance& instance ) const {
76         ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" );
77
78         // stop if the array is already full
79         if ( m_count == m_max ) {
80                 return;
81         }
82
83         Selectable* selectable = Instance_getSelectable( instance );
84         if ( ( selectable != 0 )
85                  && instance.isSelected() ) {
86                 // brushes only
87                 if ( Instance_getBrush( instance ) != 0 ) {
88                         m_bounds[m_count] = instance.worldAABB();
89                         ++m_count;
90                 }
91         }
92 }
93 };
94
95 /**
96    Selects all objects that intersect one of the bounding AABBs.
97    The exact intersection-method is specified through TSelectionPolicy
98  */
99 template<class TSelectionPolicy>
100 class SelectByBounds : public scene::Graph::Walker
101 {
102 AABB* m_aabbs;             // selection aabbs
103 Unsigned m_count;          // number of aabbs in m_aabbs
104 TSelectionPolicy policy;   // type that contains a custom intersection method aabb<->aabb
105
106 public:
107 SelectByBounds( AABB* aabbs, Unsigned count )
108         : m_aabbs( aabbs ),
109         m_count( count ){
110 }
111
112 bool pre( const scene::Path& path, scene::Instance& instance ) const {
113         Selectable* selectable = Instance_getSelectable( instance );
114
115         // ignore worldspawn
116         Entity* entity = Node_getEntity( path.top() );
117         if ( entity ) {
118                 if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
119                         return true;
120                 }
121         }
122
123         if ( ( path.size() > 1 ) &&
124                  ( !path.top().get().isRoot() ) &&
125                  ( selectable != 0 )
126                  ) {
127                 for ( Unsigned i = 0; i < m_count; ++i )
128                 {
129                         if ( policy.Evaluate( m_aabbs[i], instance ) ) {
130                                 selectable->setSelected( true );
131                         }
132                 }
133         }
134
135         return true;
136 }
137
138 /**
139    Performs selection operation on the global scenegraph.
140    If delete_bounds_src is true, then the objects which were
141    used as source for the selection aabbs will be deleted.
142  */
143 static void DoSelection( bool delete_bounds_src = true ){
144         if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
145                 // we may not need all AABBs since not all selected objects have to be brushes
146                 const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
147                 AABB* aabbs = new AABB[max];
148
149                 Unsigned count;
150                 CollectSelectedBrushesBounds collector( aabbs, max, count );
151                 GlobalSelectionSystem().foreachSelected( collector );
152
153                 // nothing usable in selection
154                 if ( !count ) {
155                         delete[] aabbs;
156                         return;
157                 }
158
159                 // delete selected objects
160                 if ( delete_bounds_src ) { // see deleteSelection
161                         UndoableCommand undo( "deleteSelected" );
162                         Select_Delete();
163                 }
164
165                 // select objects with bounds
166                 GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
167
168                 SceneChangeNotify();
169                 delete[] aabbs;
170         }
171 }
172 };
173
174 /**
175    SelectionPolicy for SelectByBounds
176    Returns true if box and the AABB of instance intersect
177  */
178 class SelectionPolicy_Touching
179 {
180 public:
181 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
182         const AABB& other( instance.worldAABB() );
183         for ( Unsigned i = 0; i < 3; ++i )
184         {
185                 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
186                         return false;
187                 }
188         }
189         return true;
190 }
191 };
192
193 /**
194    SelectionPolicy for SelectByBounds
195    Returns true if the AABB of instance is inside box
196  */
197 class SelectionPolicy_Inside
198 {
199 public:
200 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
201         const AABB& other( instance.worldAABB() );
202         for ( Unsigned i = 0; i < 3; ++i )
203         {
204                 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
205                         return false;
206                 }
207         }
208         return true;
209 }
210 };
211
212 class DeleteSelected : public scene::Graph::Walker
213 {
214 mutable bool m_remove;
215 mutable bool m_removedChild;
216 public:
217 DeleteSelected()
218         : m_remove( false ), m_removedChild( false ){
219 }
220 bool pre( const scene::Path& path, scene::Instance& instance ) const {
221         m_removedChild = false;
222
223         Selectable* selectable = Instance_getSelectable( instance );
224         if ( selectable != 0
225                  && selectable->isSelected()
226                  && path.size() > 1
227                  && !path.top().get().isRoot() ) {
228                 m_remove = true;
229
230                 return false; // dont traverse into child elements
231         }
232         return true;
233 }
234 void post( const scene::Path& path, scene::Instance& instance ) const {
235
236         if ( m_removedChild ) {
237                 m_removedChild = false;
238
239                 // delete empty entities
240                 Entity* entity = Node_getEntity( path.top() );
241                 if ( entity != 0
242                          && path.top().get_pointer() != Map_FindWorldspawn( g_map )
243                          && Node_getTraversable( path.top() )->empty() ) {
244                         Path_deleteTop( path );
245                 }
246         }
247
248         // node should be removed
249         if ( m_remove ) {
250                 if ( Node_isEntity( path.parent() ) != 0 ) {
251                         m_removedChild = true;
252                 }
253
254                 m_remove = false;
255                 Path_deleteTop( path );
256         }
257 }
258 };
259
260 void Scene_DeleteSelected( scene::Graph& graph ){
261         graph.traverse( DeleteSelected() );
262         SceneChangeNotify();
263 }
264
265 void Select_Delete( void ){
266         Scene_DeleteSelected( GlobalSceneGraph() );
267 }
268
269 class InvertSelectionWalker : public scene::Graph::Walker
270 {
271 SelectionSystem::EMode m_mode;
272 mutable Selectable* m_selectable;
273 public:
274 InvertSelectionWalker( SelectionSystem::EMode mode )
275         : m_mode( mode ), m_selectable( 0 ){
276 }
277 bool pre( const scene::Path& path, scene::Instance& instance ) const {
278         Selectable* selectable = Instance_getSelectable( instance );
279         if ( selectable ) {
280                 switch ( m_mode )
281                 {
282                 case SelectionSystem::eEntity:
283                         if ( Node_isEntity( path.top() ) != 0 ) {
284                                 m_selectable = path.top().get().visible() ? selectable : 0;
285                         }
286                         break;
287                 case SelectionSystem::ePrimitive:
288                         m_selectable = path.top().get().visible() ? selectable : 0;
289                         break;
290                 case SelectionSystem::eComponent:
291                         break;
292                 }
293         }
294         return true;
295 }
296 void post( const scene::Path& path, scene::Instance& instance ) const {
297         if ( m_selectable != 0 ) {
298                 m_selectable->setSelected( !m_selectable->isSelected() );
299                 m_selectable = 0;
300         }
301 }
302 };
303
304 void Scene_Invert_Selection( scene::Graph& graph ){
305         graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) );
306 }
307
308 void Select_Invert(){
309         Scene_Invert_Selection( GlobalSceneGraph() );
310 }
311
312 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
313 {
314 mutable std::size_t m_depth;
315 NodeSmartReference worldspawn;
316 public:
317 ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
318 }
319 bool pre( const scene::Path& path, scene::Instance& instance ) const {
320         ++m_depth;
321
322         // ignore worldspawn
323         NodeSmartReference me( path.top().get() );
324         if ( me == worldspawn ) {
325                 return false;
326         }
327
328         if ( m_depth == 2 ) { // entity depth
329                 // traverse and select children if any one is selected
330                 if ( instance.childSelected() ) {
331                         Instance_setSelected( instance, true );
332                 }
333                 return Node_getEntity( path.top() )->isContainer() && instance.isSelected();
334         }
335         else if ( m_depth == 3 ) { // primitive depth
336                 Instance_setSelected( instance, true );
337                 return false;
338         }
339         return true;
340 }
341 void post( const scene::Path& path, scene::Instance& instance ) const {
342         --m_depth;
343 }
344 };
345
346 void Scene_ExpandSelectionToEntities(){
347         GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
348 }
349
350
351 namespace
352 {
353 void Selection_UpdateWorkzone(){
354         if ( GlobalSelectionSystem().countSelected() != 0 ) {
355                 Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
356         }
357 }
358 typedef FreeCaller<Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
359
360 IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
361 }
362
363 const select_workzone_t& Select_getWorkZone(){
364         g_idleWorkzone.flush();
365         return g_select_workzone;
366 }
367
368 void UpdateWorkzone_ForSelection(){
369         g_idleWorkzone.queueDraw();
370 }
371
372 // update the workzone to the current selection
373 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
374         if ( selectable.isSelected() ) {
375                 UpdateWorkzone_ForSelection();
376         }
377 }
378
379 void Select_SetShader( const char* shader ){
380         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
381                 Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
382                 Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
383         }
384         Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
385 }
386
387 void Select_SetTexdef( const TextureProjection& projection ){
388         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
389                 Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
390         }
391         Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
392 }
393
394 void Select_SetFlags( const ContentsFlagsValue& flags ){
395         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
396                 Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
397         }
398         Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
399 }
400
401 void Select_GetBounds( Vector3& mins, Vector3& maxs ){
402         AABB bounds;
403         Scene_BoundsSelected( GlobalSceneGraph(), bounds );
404         maxs = vector3_added( bounds.origin, bounds.extents );
405         mins = vector3_subtracted( bounds.origin, bounds.extents );
406 }
407
408 void Select_GetMid( Vector3& mid ){
409         AABB bounds;
410         Scene_BoundsSelected( GlobalSceneGraph(), bounds );
411         mid = vector3_snapped( bounds.origin );
412 }
413
414
415 void Select_FlipAxis( int axis ){
416         Vector3 flip( 1, 1, 1 );
417         flip[axis] = -1;
418         GlobalSelectionSystem().scaleSelected( flip );
419 }
420
421
422 void Select_Scale( float x, float y, float z ){
423         GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
424 }
425
426 enum axis_t
427 {
428         eAxisX = 0,
429         eAxisY = 1,
430         eAxisZ = 2,
431 };
432
433 enum sign_t
434 {
435         eSignPositive = 1,
436         eSignNegative = -1,
437 };
438
439 inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
440         switch ( axis )
441         {
442         case eAxisX:
443                 if ( sign == eSignPositive ) {
444                         return matrix4_rotation_for_sincos_x( 1, 0 );
445                 }
446                 else
447                 {
448                         return matrix4_rotation_for_sincos_x( -1, 0 );
449                 }
450         case eAxisY:
451                 if ( sign == eSignPositive ) {
452                         return matrix4_rotation_for_sincos_y( 1, 0 );
453                 }
454                 else
455                 {
456                         return matrix4_rotation_for_sincos_y( -1, 0 );
457                 }
458         default: //case eAxisZ:
459                 if ( sign == eSignPositive ) {
460                         return matrix4_rotation_for_sincos_z( 1, 0 );
461                 }
462                 else
463                 {
464                         return matrix4_rotation_for_sincos_z( -1, 0 );
465                 }
466         }
467 }
468
469 inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
470         matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
471 }
472
473 inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
474         matrix4_translate_by_vec3( matrix, pivotpoint );
475         matrix4_rotate_by_axis90( matrix, axis, sign );
476         matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
477 }
478
479 inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
480 #if 1
481         switch ( axis )
482         {
483         case eAxisX:
484                 if ( sign == eSignPositive ) {
485                         return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
486                 }
487                 else
488                 {
489                         return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
490                 }
491         case eAxisY:
492                 if ( sign == eSignPositive ) {
493                         return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
494                 }
495                 else
496                 {
497                         return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
498                 }
499         default: //case eAxisZ:
500                 if ( sign == eSignPositive ) {
501                         return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
502                 }
503                 else
504                 {
505                         return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
506                 }
507         }
508 #else
509         quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
510 #endif
511 }
512
513 void Select_RotateAxis( int axis, float deg ){
514         if ( fabs( deg ) == 90.f ) {
515                 GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
516         }
517         else
518         {
519                 switch ( axis )
520                 {
521                 case 0:
522                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ) );
523                         break;
524                 case 1:
525                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ) );
526                         break;
527                 case 2:
528                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ) );
529                         break;
530                 }
531         }
532 }
533
534
535 void Select_ShiftTexture( float x, float y ){
536         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
537                 Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
538                 Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
539         }
540         //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
541         Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
542 }
543
544 void Select_ScaleTexture( float x, float y ){
545         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
546                 Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
547                 Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
548         }
549         Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
550 }
551
552 void Select_RotateTexture( float amt ){
553         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
554                 Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
555                 Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
556         }
557         Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
558 }
559
560 // TTimo modified to handle shader architecture:
561 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
562 void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
563         if ( !texdef_name_valid( pFind ) ) {
564                 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
565                 return;
566         }
567         if ( !texdef_name_valid( pReplace ) ) {
568                 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
569                 return;
570         }
571
572         StringOutputStream command;
573         command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
574         UndoableCommand undo( command.c_str() );
575
576         if ( bSelected ) {
577                 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
578                         Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
579                         Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
580                 }
581                 Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
582         }
583         else
584         {
585                 Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
586                 Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
587         }
588 }
589
590 typedef std::vector<const char*> PropertyValues;
591
592 bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
593         for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
594         {
595                 if ( string_equal( str, *i ) ) {
596                         return true;
597                 }
598         }
599         return false;
600 }
601
602 class EntityFindByPropertyValueWalker : public scene::Graph::Walker
603 {
604 const PropertyValues& m_propertyvalues;
605 const char *m_prop;
606 public:
607 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
608         : m_propertyvalues( propertyvalues ), m_prop( prop ){
609 }
610 bool pre( const scene::Path& path, scene::Instance& instance ) const {
611         Entity* entity = Node_getEntity( path.top() );
612         if ( entity != 0
613                  && propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
614                 Instance_getSelectable( instance )->setSelected( true );
615         }
616         return true;
617 }
618 };
619
620 void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
621         graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
622 }
623
624 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
625 {
626 PropertyValues& m_propertyvalues;
627 const char *m_prop;
628 public:
629 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
630         : m_propertyvalues( propertyvalues ), m_prop( prop ){
631 }
632 bool pre( const scene::Path& path, scene::Instance& instance ) const {
633         Selectable* selectable = Instance_getSelectable( instance );
634         if ( selectable != 0
635                  && selectable->isSelected() ) {
636                 Entity* entity = Node_getEntity( path.top() );
637                 if ( entity != 0 ) {
638                         if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
639                                 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
640                         }
641                 }
642         }
643         return true;
644 }
645 };
646
647 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
648         graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
649 }
650
651 void Select_AllOfType(){
652         if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
653                 if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
654                         GlobalSelectionSystem().setSelectedAllComponents( false );
655                         Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
656                 }
657         }
658         else
659         {
660                 PropertyValues propertyvalues;
661                 const char *prop = EntityInspector_getCurrentKey();
662                 if ( !prop || !*prop ) {
663                         prop = "classname";
664                 }
665                 Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
666                 GlobalSelectionSystem().setSelectedAll( false );
667                 if ( !propertyvalues.empty() ) {
668                         Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
669                 }
670                 else
671                 {
672                         Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
673                         Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
674                 }
675         }
676 }
677
678 void Select_Inside( void ){
679         SelectByBounds<SelectionPolicy_Inside>::DoSelection();
680 }
681
682 void Select_Touching( void ){
683         SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
684 }
685
686 void Select_FitTexture( float horizontal, float vertical ){
687         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
688                 Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
689         }
690         Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
691
692         SceneChangeNotify();
693 }
694
695 inline void hide_node( scene::Node& node, bool hide ){
696         hide
697         ? node.enable( scene::Node::eHidden )
698         : node.disable( scene::Node::eHidden );
699 }
700
701 class HideSelectedWalker : public scene::Graph::Walker
702 {
703 bool m_hide;
704 public:
705 HideSelectedWalker( bool hide )
706         : m_hide( hide ){
707 }
708 bool pre( const scene::Path& path, scene::Instance& instance ) const {
709         Selectable* selectable = Instance_getSelectable( instance );
710         if ( selectable != 0
711                  && selectable->isSelected() ) {
712                 hide_node( path.top(), m_hide );
713         }
714         return true;
715 }
716 };
717
718 void Scene_Hide_Selected( bool hide ){
719         GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
720 }
721
722 void Select_Hide(){
723         Scene_Hide_Selected( true );
724         SceneChangeNotify();
725 }
726
727 void HideSelected(){
728         Select_Hide();
729         GlobalSelectionSystem().setSelectedAll( false );
730 }
731
732
733 class HideAllWalker : public scene::Graph::Walker
734 {
735 bool m_hide;
736 public:
737 HideAllWalker( bool hide )
738         : m_hide( hide ){
739 }
740 bool pre( const scene::Path& path, scene::Instance& instance ) const {
741         hide_node( path.top(), m_hide );
742         return true;
743 }
744 };
745
746 void Scene_Hide_All( bool hide ){
747         GlobalSceneGraph().traverse( HideAllWalker( hide ) );
748 }
749
750 void Select_ShowAllHidden(){
751         Scene_Hide_All( false );
752         SceneChangeNotify();
753 }
754
755
756
757 void Selection_Flipx(){
758         UndoableCommand undo( "mirrorSelected -axis x" );
759         Select_FlipAxis( 0 );
760 }
761
762 void Selection_Flipy(){
763         UndoableCommand undo( "mirrorSelected -axis y" );
764         Select_FlipAxis( 1 );
765 }
766
767 void Selection_Flipz(){
768         UndoableCommand undo( "mirrorSelected -axis z" );
769         Select_FlipAxis( 2 );
770 }
771
772 void Selection_Rotatex(){
773         UndoableCommand undo( "rotateSelected -axis x -angle -90" );
774         Select_RotateAxis( 0,-90 );
775 }
776
777 void Selection_Rotatey(){
778         UndoableCommand undo( "rotateSelected -axis y -angle 90" );
779         Select_RotateAxis( 1, 90 );
780 }
781
782 void Selection_Rotatez(){
783         UndoableCommand undo( "rotateSelected -axis z -angle -90" );
784         Select_RotateAxis( 2,-90 );
785 }
786
787
788
789 void Nudge( int nDim, float fNudge ){
790         Vector3 translate( 0, 0, 0 );
791         translate[nDim] = fNudge;
792
793         GlobalSelectionSystem().translateSelected( translate );
794 }
795
796 void Selection_NudgeZ( float amount ){
797         StringOutputStream command;
798         command << "nudgeSelected -axis z -amount " << amount;
799         UndoableCommand undo( command.c_str() );
800
801         Nudge( 2, amount );
802 }
803
804 void Selection_MoveDown(){
805         Selection_NudgeZ( -GetGridSize() );
806 }
807
808 void Selection_MoveUp(){
809         Selection_NudgeZ( GetGridSize() );
810 }
811
812 void SceneSelectionChange( const Selectable& selectable ){
813         SceneChangeNotify();
814 }
815
816 SignalHandlerId Selection_boundsChanged;
817
818 void Selection_construct(){
819         typedef FreeCaller1<const Selectable&, SceneSelectionChange> SceneSelectionChangeCaller;
820         GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
821         typedef FreeCaller1<const Selectable&, UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
822         GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
823         typedef FreeCaller<UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
824         Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
825 }
826
827 void Selection_destroy(){
828         GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
829 }
830
831
832 #include "gtkdlgs.h"
833 #include <gtk/gtkbox.h>
834 #include <gtk/gtkspinbutton.h>
835 #include <gtk/gtktable.h>
836 #include <gtk/gtklabel.h>
837 #include <gdk/gdkkeysyms.h>
838
839
840 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
841 #if 0
842         return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
843 #elif 0
844         return quaternion_multiplied_by_quaternion(
845                            quaternion_multiplied_by_quaternion(
846                                    quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
847                                    quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
848                                    ),
849                            quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
850                            );
851 #elif 1
852         double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
853         double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
854         double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
855         double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
856         double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
857         double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
858
859         return Quaternion(
860                            cz * cy * sx - sz * sy * cx,
861                            cz * sy * cx + sz * cy * sx,
862                            sz * cy * cx - cz * sy * sx,
863                            cz * cy * cx + sz * sy * sx
864                            );
865 #endif
866 }
867
868 struct RotateDialog
869 {
870         GtkSpinButton* x;
871         GtkSpinButton* y;
872         GtkSpinButton* z;
873         GtkWindow *window;
874 };
875
876 static gboolean rotatedlg_apply( GtkWidget *widget, RotateDialog* rotateDialog ){
877         Vector3 eulerXYZ;
878
879         eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
880         eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
881         eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
882
883         StringOutputStream command;
884         command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
885         UndoableCommand undo( command.c_str() );
886
887         GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) );
888         return TRUE;
889 }
890
891 static gboolean rotatedlg_cancel( GtkWidget *widget, RotateDialog* rotateDialog ){
892         gtk_widget_hide( GTK_WIDGET( rotateDialog->window ) );
893
894         gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
895         gtk_spin_button_set_value( rotateDialog->y, 0.0f );
896         gtk_spin_button_set_value( rotateDialog->z, 0.0f );
897
898         return TRUE;
899 }
900
901 static gboolean rotatedlg_ok( GtkWidget *widget, RotateDialog* rotateDialog ){
902         rotatedlg_apply( widget, rotateDialog );
903         gtk_widget_hide( GTK_WIDGET( rotateDialog->window ) );
904         return TRUE;
905 }
906
907 static gboolean rotatedlg_delete( GtkWidget *widget, GdkEventAny *event, RotateDialog* rotateDialog ){
908         rotatedlg_cancel( widget, rotateDialog );
909         return TRUE;
910 }
911
912 RotateDialog g_rotate_dialog;
913 void DoRotateDlg(){
914         if ( g_rotate_dialog.window == NULL ) {
915                 g_rotate_dialog.window = create_dialog_window( MainFrame_getWindow(), "Arbitrary rotation", G_CALLBACK( rotatedlg_delete ), &g_rotate_dialog );
916
917                 GtkAccelGroup* accel = gtk_accel_group_new();
918                 gtk_window_add_accel_group( g_rotate_dialog.window, accel );
919
920                 {
921                         GtkHBox* hbox = create_dialog_hbox( 4, 4 );
922                         gtk_container_add( GTK_CONTAINER( g_rotate_dialog.window ), GTK_WIDGET( hbox ) );
923                         {
924                                 GtkTable* table = create_dialog_table( 3, 2, 4, 4 );
925                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
926                                 {
927                                         GtkWidget* label = gtk_label_new( "  X  " );
928                                         gtk_widget_show( label );
929                                         gtk_table_attach( table, label, 0, 1, 0, 1,
930                                                                           (GtkAttachOptions) ( 0 ),
931                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
932                                 }
933                                 {
934                                         GtkWidget* label = gtk_label_new( "  Y  " );
935                                         gtk_widget_show( label );
936                                         gtk_table_attach( table, label, 0, 1, 1, 2,
937                                                                           (GtkAttachOptions) ( 0 ),
938                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
939                                 }
940                                 {
941                                         GtkWidget* label = gtk_label_new( "  Z  " );
942                                         gtk_widget_show( label );
943                                         gtk_table_attach( table, label, 0, 1, 2, 3,
944                                                                           (GtkAttachOptions) ( 0 ),
945                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
946                                 }
947                                 {
948                                         GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
949                                         GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) );
950                                         gtk_widget_show( GTK_WIDGET( spin ) );
951                                         gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
952                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
953                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
954                                         gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
955                                         gtk_spin_button_set_wrap( spin, TRUE );
956
957                                         gtk_widget_grab_focus( GTK_WIDGET( spin ) );
958
959                                         g_rotate_dialog.x = spin;
960                                 }
961                                 {
962                                         GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
963                                         GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) );
964                                         gtk_widget_show( GTK_WIDGET( spin ) );
965                                         gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 1, 2,
966                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
967                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
968                                         gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
969                                         gtk_spin_button_set_wrap( spin, TRUE );
970
971                                         g_rotate_dialog.y = spin;
972                                 }
973                                 {
974                                         GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
975                                         GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) );
976                                         gtk_widget_show( GTK_WIDGET( spin ) );
977                                         gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 2, 3,
978                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
979                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
980                                         gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
981                                         gtk_spin_button_set_wrap( spin, TRUE );
982
983                                         g_rotate_dialog.z = spin;
984                                 }
985                         }
986                         {
987                                 GtkVBox* vbox = create_dialog_vbox( 4 );
988                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
989                                 {
990                                         GtkButton* button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
991                                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
992                                         widget_make_default( GTK_WIDGET( button ) );
993                                         gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
994                                 }
995                                 {
996                                         GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
997                                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
998                                         gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
999                                 }
1000                                 {
1001                                         GtkButton* button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
1002                                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1003                                 }
1004                         }
1005                 }
1006         }
1007
1008         gtk_widget_show( GTK_WIDGET( g_rotate_dialog.window ) );
1009 }
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019 struct ScaleDialog
1020 {
1021         GtkWidget* x;
1022         GtkWidget* y;
1023         GtkWidget* z;
1024         GtkWindow *window;
1025 };
1026
1027 static gboolean scaledlg_apply( GtkWidget *widget, ScaleDialog* scaleDialog ){
1028         float sx, sy, sz;
1029
1030         sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1031         sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1032         sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1033
1034         StringOutputStream command;
1035         command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1036         UndoableCommand undo( command.c_str() );
1037
1038         Select_Scale( sx, sy, sz );
1039
1040         return TRUE;
1041 }
1042
1043 static gboolean scaledlg_cancel( GtkWidget *widget, ScaleDialog* scaleDialog ){
1044         gtk_widget_hide( GTK_WIDGET( scaleDialog->window ) );
1045
1046         gtk_entry_set_text( GTK_ENTRY( scaleDialog->x ), "1.0" );
1047         gtk_entry_set_text( GTK_ENTRY( scaleDialog->y ), "1.0" );
1048         gtk_entry_set_text( GTK_ENTRY( scaleDialog->z ), "1.0" );
1049
1050         return TRUE;
1051 }
1052
1053 static gboolean scaledlg_ok( GtkWidget *widget, ScaleDialog* scaleDialog ){
1054         scaledlg_apply( widget, scaleDialog );
1055         gtk_widget_hide( GTK_WIDGET( scaleDialog->window ) );
1056         return TRUE;
1057 }
1058
1059 static gboolean scaledlg_delete( GtkWidget *widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1060         scaledlg_cancel( widget, scaleDialog );
1061         return TRUE;
1062 }
1063
1064 ScaleDialog g_scale_dialog;
1065
1066 void DoScaleDlg(){
1067         if ( g_scale_dialog.window == NULL ) {
1068                 g_scale_dialog.window = create_dialog_window( MainFrame_getWindow(), "Arbitrary scale", G_CALLBACK( scaledlg_delete ), &g_scale_dialog );
1069
1070                 GtkAccelGroup* accel = gtk_accel_group_new();
1071                 gtk_window_add_accel_group( g_scale_dialog.window, accel );
1072
1073                 {
1074                         GtkHBox* hbox = create_dialog_hbox( 4, 4 );
1075                         gtk_container_add( GTK_CONTAINER( g_scale_dialog.window ), GTK_WIDGET( hbox ) );
1076                         {
1077                                 GtkTable* table = create_dialog_table( 3, 2, 4, 4 );
1078                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1079                                 {
1080                                         GtkWidget* label = gtk_label_new( "  X  " );
1081                                         gtk_widget_show( label );
1082                                         gtk_table_attach( table, label, 0, 1, 0, 1,
1083                                                                           (GtkAttachOptions) ( 0 ),
1084                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1085                                 }
1086                                 {
1087                                         GtkWidget* label = gtk_label_new( "  Y  " );
1088                                         gtk_widget_show( label );
1089                                         gtk_table_attach( table, label, 0, 1, 1, 2,
1090                                                                           (GtkAttachOptions) ( 0 ),
1091                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1092                                 }
1093                                 {
1094                                         GtkWidget* label = gtk_label_new( "  Z  " );
1095                                         gtk_widget_show( label );
1096                                         gtk_table_attach( table, label, 0, 1, 2, 3,
1097                                                                           (GtkAttachOptions) ( 0 ),
1098                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1099                                 }
1100                                 {
1101                                         GtkWidget* entry = gtk_entry_new();
1102                                         gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" );
1103                                         gtk_widget_show( entry );
1104                                         gtk_table_attach( table, entry, 1, 2, 0, 1,
1105                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1106                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1107
1108                                         g_scale_dialog.x = entry;
1109                                 }
1110                                 {
1111                                         GtkWidget* entry = gtk_entry_new();
1112                                         gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" );
1113                                         gtk_widget_show( entry );
1114                                         gtk_table_attach( table, entry, 1, 2, 1, 2,
1115                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1116                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1117
1118                                         g_scale_dialog.y = entry;
1119                                 }
1120                                 {
1121                                         GtkWidget* entry = gtk_entry_new();
1122                                         gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" );
1123                                         gtk_widget_show( entry );
1124                                         gtk_table_attach( table, entry, 1, 2, 2, 3,
1125                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1126                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1127
1128                                         g_scale_dialog.z = entry;
1129                                 }
1130                         }
1131                         {
1132                                 GtkVBox* vbox = create_dialog_vbox( 4 );
1133                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
1134                                 {
1135                                         GtkButton* button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1136                                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1137                                         widget_make_default( GTK_WIDGET( button ) );
1138                                         gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1139                                 }
1140                                 {
1141                                         GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1142                                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1143                                         gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1144                                 }
1145                                 {
1146                                         GtkButton* button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1147                                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1148                                 }
1149                         }
1150                 }
1151         }
1152
1153         gtk_widget_show( GTK_WIDGET( g_scale_dialog.window ) );
1154 }