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