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