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