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