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