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