2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
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.
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.
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
26 #include "debugging/debugging.h"
29 #include "iselection.h"
34 #include "stream/stringstream.h"
35 #include "signal/isignal.h"
36 #include "shaderlib.h"
39 #include "gtkutil/idledraw.h"
40 #include "gtkutil/dialog.h"
41 #include "gtkutil/widget.h"
42 #include "brushmanip.h"
44 #include "patchmanip.h"
45 #include "patchdialog.h"
46 #include "selection.h"
47 #include "texwindow.h"
49 #include "mainframe.h"
52 #include "entityinspector.h"
55 select_workzone_t g_select_workzone;
59 Loops over all selected brushes and stores their
60 world AABBs in the specified array.
62 class CollectSelectedBrushesBounds : public SelectionSystem::Visitor {
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
68 CollectSelectedBrushesBounds(AABB *bounds, Unsigned max, Unsigned &count)
76 void visit(scene::Instance &instance) const
78 ASSERT_MESSAGE(m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds");
80 // stop if the array is already full
81 if (m_count == m_max) {
85 Selectable *selectable = Instance_getSelectable(instance);
87 && instance.isSelected()) {
89 if (Instance_getBrush(instance) != 0) {
90 m_bounds[m_count] = instance.worldAABB();
98 Selects all objects that intersect one of the bounding AABBs.
99 The exact intersection-method is specified through TSelectionPolicy
101 template<class TSelectionPolicy>
102 class SelectByBounds : public scene::Graph::Walker {
103 AABB *m_aabbs; // selection aabbs
104 Unsigned m_count; // number of aabbs in m_aabbs
105 TSelectionPolicy policy; // type that contains a custom intersection method aabb<->aabb
108 SelectByBounds(AABB *aabbs, Unsigned count)
114 bool pre(const scene::Path &path, scene::Instance &instance) const
116 Selectable *selectable = Instance_getSelectable(instance);
119 Entity *entity = Node_getEntity(path.top());
121 if (string_equal(entity->getKeyValue("classname"), "worldspawn")) {
126 if ((path.size() > 1) &&
127 (!path.top().get().isRoot()) &&
130 for (Unsigned i = 0; i < m_count; ++i) {
131 if (policy.Evaluate(m_aabbs[i], instance)) {
132 selectable->setSelected(true);
141 Performs selection operation on the global scenegraph.
142 If delete_bounds_src is true, then the objects which were
143 used as source for the selection aabbs will be deleted.
145 static void DoSelection(bool delete_bounds_src = true)
147 if (GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
148 // we may not need all AABBs since not all selected objects have to be brushes
149 const Unsigned max = (Unsigned) GlobalSelectionSystem().countSelected();
150 AABB *aabbs = new AABB[max];
153 CollectSelectedBrushesBounds collector(aabbs, max, count);
154 GlobalSelectionSystem().foreachSelected(collector);
156 // nothing usable in selection
162 // delete selected objects
163 if (delete_bounds_src) { // see deleteSelection
164 UndoableCommand undo("deleteSelected");
168 // select objects with bounds
169 GlobalSceneGraph().traverse(SelectByBounds<TSelectionPolicy>(aabbs, count));
178 SelectionPolicy for SelectByBounds
179 Returns true if box and the AABB of instance intersect
181 class SelectionPolicy_Touching {
183 bool Evaluate(const AABB &box, scene::Instance &instance) const
185 const AABB &other(instance.worldAABB());
186 for (Unsigned i = 0; i < 3; ++i) {
187 if (fabsf(box.origin[i] - other.origin[i]) > (box.extents[i] + other.extents[i])) {
196 SelectionPolicy for SelectByBounds
197 Returns true if the AABB of instance is inside box
199 class SelectionPolicy_Inside {
201 bool Evaluate(const AABB &box, scene::Instance &instance) const
203 const AABB &other(instance.worldAABB());
204 for (Unsigned i = 0; i < 3; ++i) {
205 if (fabsf(box.origin[i] - other.origin[i]) > (box.extents[i] - other.extents[i])) {
213 class DeleteSelected : public scene::Graph::Walker {
214 mutable bool m_remove;
215 mutable bool m_removedChild;
218 : m_remove(false), m_removedChild(false)
222 bool pre(const scene::Path &path, scene::Instance &instance) const
224 m_removedChild = false;
226 Selectable *selectable = Instance_getSelectable(instance);
228 && selectable->isSelected()
230 && !path.top().get().isRoot()) {
233 return false; // dont traverse into child elements
238 void post(const scene::Path &path, scene::Instance &instance) const
241 if (m_removedChild) {
242 m_removedChild = false;
244 // delete empty entities
245 Entity *entity = Node_getEntity(path.top());
247 && path.top().get_pointer() != Map_FindWorldspawn(g_map)
248 && Node_getTraversable(path.top())->empty()) {
249 Path_deleteTop(path);
253 // node should be removed
255 if (Node_isEntity(path.parent()) != 0) {
256 m_removedChild = true;
260 Path_deleteTop(path);
265 void Scene_DeleteSelected(scene::Graph &graph)
267 graph.traverse(DeleteSelected());
271 void Select_Delete(void)
273 Scene_DeleteSelected(GlobalSceneGraph());
276 class InvertSelectionWalker : public scene::Graph::Walker {
277 SelectionSystem::EMode m_mode;
278 mutable Selectable *m_selectable;
280 InvertSelectionWalker(SelectionSystem::EMode mode)
281 : m_mode(mode), m_selectable(0)
285 bool pre(const scene::Path &path, scene::Instance &instance) const
287 Selectable *selectable = Instance_getSelectable(instance);
290 case SelectionSystem::eEntity:
291 if (Node_isEntity(path.top()) != 0) {
292 m_selectable = path.top().get().visible() ? selectable : 0;
295 case SelectionSystem::ePrimitive:
296 m_selectable = path.top().get().visible() ? selectable : 0;
298 case SelectionSystem::eComponent:
305 void post(const scene::Path &path, scene::Instance &instance) const
307 if (m_selectable != 0) {
308 m_selectable->setSelected(!m_selectable->isSelected());
314 void Scene_Invert_Selection(scene::Graph &graph)
316 graph.traverse(InvertSelectionWalker(GlobalSelectionSystem().Mode()));
321 Scene_Invert_Selection(GlobalSceneGraph());
324 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker {
325 mutable std::size_t m_depth;
326 NodeSmartReference worldspawn;
328 ExpandSelectionToEntitiesWalker() : m_depth(0), worldspawn(Map_FindOrInsertWorldspawn(g_map))
332 bool pre(const scene::Path &path, scene::Instance &instance) const
337 NodeSmartReference me(path.top().get());
338 if (me == worldspawn) {
342 if (m_depth == 2) { // entity depth
343 // traverse and select children if any one is selected
344 if (instance.childSelected()) {
345 Instance_setSelected(instance, true);
347 return Node_getEntity(path.top())->isContainer() && instance.isSelected();
348 } else if (m_depth == 3) { // primitive depth
349 Instance_setSelected(instance, true);
355 void post(const scene::Path &path, scene::Instance &instance) const
361 void Scene_ExpandSelectionToEntities()
363 GlobalSceneGraph().traverse(ExpandSelectionToEntitiesWalker());
368 void Selection_UpdateWorkzone()
370 if (GlobalSelectionSystem().countSelected() != 0) {
371 Select_GetBounds(g_select_workzone.d_work_min, g_select_workzone.d_work_max);
375 typedef FreeCaller<void(), Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
377 IdleDraw g_idleWorkzone = IdleDraw(SelectionUpdateWorkzoneCaller());
380 const select_workzone_t &Select_getWorkZone()
382 g_idleWorkzone.flush();
383 return g_select_workzone;
386 void UpdateWorkzone_ForSelection()
388 g_idleWorkzone.queueDraw();
391 // update the workzone to the current selection
392 void UpdateWorkzone_ForSelectionChanged(const Selectable &selectable)
394 if (selectable.isSelected()) {
395 UpdateWorkzone_ForSelection();
399 void Select_SetShader(const char *shader)
401 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
402 Scene_BrushSetShader_Selected(GlobalSceneGraph(), shader);
403 Scene_PatchSetShader_Selected(GlobalSceneGraph(), shader);
405 Scene_BrushSetShader_Component_Selected(GlobalSceneGraph(), shader);
408 void Select_SetTexdef(const TextureProjection &projection)
410 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
411 Scene_BrushSetTexdef_Selected(GlobalSceneGraph(), projection);
413 Scene_BrushSetTexdef_Component_Selected(GlobalSceneGraph(), projection);
416 void Select_SetFlags(const ContentsFlagsValue &flags)
418 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
419 Scene_BrushSetFlags_Selected(GlobalSceneGraph(), flags);
421 Scene_BrushSetFlags_Component_Selected(GlobalSceneGraph(), flags);
424 void Select_GetBounds(Vector3 &mins, Vector3 &maxs)
427 Scene_BoundsSelected(GlobalSceneGraph(), bounds);
428 maxs = vector3_added(bounds.origin, bounds.extents);
429 mins = vector3_subtracted(bounds.origin, bounds.extents);
432 void Select_GetMid(Vector3 &mid)
435 Scene_BoundsSelected(GlobalSceneGraph(), bounds);
436 mid = vector3_snapped(bounds.origin);
440 void Select_FlipAxis(int axis)
442 Vector3 flip(1, 1, 1);
444 GlobalSelectionSystem().scaleSelected(flip);
448 void Select_Scale(float x, float y, float z)
450 GlobalSelectionSystem().scaleSelected(Vector3(x, y, z));
464 inline Matrix4 matrix4_rotation_for_axis90(axis_t axis, sign_t sign)
468 if (sign == eSignPositive) {
469 return matrix4_rotation_for_sincos_x(1, 0);
471 return matrix4_rotation_for_sincos_x(-1, 0);
474 if (sign == eSignPositive) {
475 return matrix4_rotation_for_sincos_y(1, 0);
477 return matrix4_rotation_for_sincos_y(-1, 0);
479 default: //case eAxisZ:
480 if (sign == eSignPositive) {
481 return matrix4_rotation_for_sincos_z(1, 0);
483 return matrix4_rotation_for_sincos_z(-1, 0);
488 inline void matrix4_rotate_by_axis90(Matrix4 &matrix, axis_t axis, sign_t sign)
490 matrix4_multiply_by_matrix4(matrix, matrix4_rotation_for_axis90(axis, sign));
493 inline void matrix4_pivoted_rotate_by_axis90(Matrix4 &matrix, axis_t axis, sign_t sign, const Vector3 &pivotpoint)
495 matrix4_translate_by_vec3(matrix, pivotpoint);
496 matrix4_rotate_by_axis90(matrix, axis, sign);
497 matrix4_translate_by_vec3(matrix, vector3_negated(pivotpoint));
500 inline Quaternion quaternion_for_axis90(axis_t axis, sign_t sign)
505 if (sign == eSignPositive) {
506 return Quaternion(c_half_sqrt2f, 0, 0, c_half_sqrt2f);
508 return Quaternion(-c_half_sqrt2f, 0, 0, -c_half_sqrt2f);
511 if (sign == eSignPositive) {
512 return Quaternion(0, c_half_sqrt2f, 0, c_half_sqrt2f);
514 return Quaternion(0, -c_half_sqrt2f, 0, -c_half_sqrt2f);
516 default: //case eAxisZ:
517 if (sign == eSignPositive) {
518 return Quaternion(0, 0, c_half_sqrt2f, c_half_sqrt2f);
520 return Quaternion(0, 0, -c_half_sqrt2f, -c_half_sqrt2f);
524 quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
528 void Select_RotateAxis(int axis, float deg)
530 if (fabs(deg) == 90.f) {
531 GlobalSelectionSystem().rotateSelected(
532 quaternion_for_axis90((axis_t) axis, (deg > 0) ? eSignPositive : eSignNegative));
536 GlobalSelectionSystem().rotateSelected(
537 quaternion_for_matrix4_rotation(matrix4_rotation_for_x_degrees(deg)));
540 GlobalSelectionSystem().rotateSelected(
541 quaternion_for_matrix4_rotation(matrix4_rotation_for_y_degrees(deg)));
544 GlobalSelectionSystem().rotateSelected(
545 quaternion_for_matrix4_rotation(matrix4_rotation_for_z_degrees(deg)));
552 void Select_ShiftTexture(float x, float y)
554 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
555 Scene_BrushShiftTexdef_Selected(GlobalSceneGraph(), x, y);
556 Scene_PatchTranslateTexture_Selected(GlobalSceneGraph(), x, y);
558 //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
559 Scene_BrushShiftTexdef_Component_Selected(GlobalSceneGraph(), x, y);
562 void Select_ScaleTexture(float x, float y)
564 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
565 Scene_BrushScaleTexdef_Selected(GlobalSceneGraph(), x, y);
566 Scene_PatchScaleTexture_Selected(GlobalSceneGraph(), x, y);
568 Scene_BrushScaleTexdef_Component_Selected(GlobalSceneGraph(), x, y);
571 void Select_RotateTexture(float amt)
573 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
574 Scene_BrushRotateTexdef_Selected(GlobalSceneGraph(), amt);
575 Scene_PatchRotateTexture_Selected(GlobalSceneGraph(), amt);
577 Scene_BrushRotateTexdef_Component_Selected(GlobalSceneGraph(), amt);
580 // TTimo modified to handle shader architecture:
581 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
582 void FindReplaceTextures(const char *pFind, const char *pReplace, bool bSelected)
584 if (!texdef_name_valid(pFind)) {
585 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
588 if (!texdef_name_valid(pReplace)) {
589 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
593 StringOutputStream command;
594 command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
595 UndoableCommand undo(command.c_str());
598 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
599 Scene_BrushFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace);
600 Scene_PatchFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace);
602 Scene_BrushFindReplaceShader_Component_Selected(GlobalSceneGraph(), pFind, pReplace);
604 Scene_BrushFindReplaceShader(GlobalSceneGraph(), pFind, pReplace);
605 Scene_PatchFindReplaceShader(GlobalSceneGraph(), pFind, pReplace);
609 typedef std::vector<const char *> PropertyValues;
611 bool propertyvalues_contain(const PropertyValues &propertyvalues, const char *str)
613 for (PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i) {
614 if (string_equal(str, *i)) {
621 class EntityFindByPropertyValueWalker : public scene::Graph::Walker {
622 const PropertyValues &m_propertyvalues;
625 EntityFindByPropertyValueWalker(const char *prop, const PropertyValues &propertyvalues)
626 : m_propertyvalues(propertyvalues), m_prop(prop)
630 bool pre(const scene::Path &path, scene::Instance &instance) const
632 Entity *entity = Node_getEntity(path.top());
634 && propertyvalues_contain(m_propertyvalues, entity->getKeyValue(m_prop))) {
635 Instance_getSelectable(instance)->setSelected(true);
641 void Scene_EntitySelectByPropertyValues(scene::Graph &graph, const char *prop, const PropertyValues &propertyvalues)
643 graph.traverse(EntityFindByPropertyValueWalker(prop, propertyvalues));
646 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker {
647 PropertyValues &m_propertyvalues;
650 EntityGetSelectedPropertyValuesWalker(const char *prop, PropertyValues &propertyvalues)
651 : m_propertyvalues(propertyvalues), m_prop(prop)
655 bool pre(const scene::Path &path, scene::Instance &instance) const
657 Selectable *selectable = Instance_getSelectable(instance);
659 && selectable->isSelected()) {
660 Entity *entity = Node_getEntity(path.top());
662 if (!propertyvalues_contain(m_propertyvalues, entity->getKeyValue(m_prop))) {
663 m_propertyvalues.push_back(entity->getKeyValue(m_prop));
671 void Scene_EntityGetPropertyValues(scene::Graph &graph, const char *prop, PropertyValues &propertyvalues)
673 graph.traverse(EntityGetSelectedPropertyValuesWalker(prop, propertyvalues));
676 void Select_AllOfType()
678 if (GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) {
679 if (GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace) {
680 GlobalSelectionSystem().setSelectedAllComponents(false);
681 Scene_BrushSelectByShader_Component(GlobalSceneGraph(),
682 TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
685 PropertyValues propertyvalues;
686 const char *prop = EntityInspector_getCurrentKey();
687 if (!prop || !*prop) {
690 Scene_EntityGetPropertyValues(GlobalSceneGraph(), prop, propertyvalues);
691 GlobalSelectionSystem().setSelectedAll(false);
692 if (!propertyvalues.empty()) {
693 Scene_EntitySelectByPropertyValues(GlobalSceneGraph(), prop, propertyvalues);
695 Scene_BrushSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
696 Scene_PatchSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
701 void Select_Inside(void)
703 SelectByBounds<SelectionPolicy_Inside>::DoSelection();
706 void Select_Touching(void)
708 SelectByBounds<SelectionPolicy_Touching>::DoSelection(false);
711 void Select_FitTexture(float horizontal, float vertical)
713 if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) {
714 Scene_BrushFitTexture_Selected(GlobalSceneGraph(), horizontal, vertical);
716 Scene_BrushFitTexture_Component_Selected(GlobalSceneGraph(), horizontal, vertical);
721 inline void hide_node(scene::Node &node, bool hide)
724 ? node.enable(scene::Node::eHidden)
725 : node.disable(scene::Node::eHidden);
728 class HideSelectedWalker : public scene::Graph::Walker {
731 HideSelectedWalker(bool hide)
736 bool pre(const scene::Path &path, scene::Instance &instance) const
738 Selectable *selectable = Instance_getSelectable(instance);
740 && selectable->isSelected()) {
741 hide_node(path.top(), m_hide);
747 void Scene_Hide_Selected(bool hide)
749 GlobalSceneGraph().traverse(HideSelectedWalker(hide));
754 Scene_Hide_Selected(true);
761 GlobalSelectionSystem().setSelectedAll(false);
765 class HideAllWalker : public scene::Graph::Walker {
768 HideAllWalker(bool hide)
773 bool pre(const scene::Path &path, scene::Instance &instance) const
775 hide_node(path.top(), m_hide);
780 void Scene_Hide_All(bool hide)
782 GlobalSceneGraph().traverse(HideAllWalker(hide));
785 void Select_ShowAllHidden()
787 Scene_Hide_All(false);
792 void Selection_Flipx()
794 UndoableCommand undo("mirrorSelected -axis x");
798 void Selection_Flipy()
800 UndoableCommand undo("mirrorSelected -axis y");
804 void Selection_Flipz()
806 UndoableCommand undo("mirrorSelected -axis z");
810 void Selection_Rotatex()
812 UndoableCommand undo("rotateSelected -axis x -angle -90");
813 Select_RotateAxis(0, -90);
816 void Selection_Rotatey()
818 UndoableCommand undo("rotateSelected -axis y -angle 90");
819 Select_RotateAxis(1, 90);
822 void Selection_Rotatez()
824 UndoableCommand undo("rotateSelected -axis z -angle -90");
825 Select_RotateAxis(2, -90);
829 void Nudge(int nDim, float fNudge)
831 Vector3 translate(0, 0, 0);
832 translate[nDim] = fNudge;
834 GlobalSelectionSystem().translateSelected(translate);
837 void Selection_NudgeZ(float amount)
839 StringOutputStream command;
840 command << "nudgeSelected -axis z -amount " << amount;
841 UndoableCommand undo(command.c_str());
846 void Selection_MoveDown()
848 Selection_NudgeZ(-GetGridSize());
851 void Selection_MoveUp()
853 Selection_NudgeZ(GetGridSize());
856 void SceneSelectionChange(const Selectable &selectable)
861 SignalHandlerId Selection_boundsChanged;
863 void Selection_construct()
865 typedef FreeCaller<void(const Selectable &), SceneSelectionChange> SceneSelectionChangeCaller;
866 GlobalSelectionSystem().addSelectionChangeCallback(SceneSelectionChangeCaller());
867 typedef FreeCaller<void(
868 const Selectable &), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
869 GlobalSelectionSystem().addSelectionChangeCallback(UpdateWorkzoneForSelectionChangedCaller());
870 typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
871 Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback(UpdateWorkzoneForSelectionCaller());
874 void Selection_destroy()
876 GlobalSceneGraph().removeBoundsChangedCallback(Selection_boundsChanged);
881 #include <gdk/gdkkeysyms.h>
884 inline Quaternion quaternion_for_euler_xyz_degrees(const Vector3 &eulerXYZ)
887 return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
889 return quaternion_multiplied_by_quaternion(
890 quaternion_multiplied_by_quaternion(
891 quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
892 quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
894 quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
897 double cx = cos(degrees_to_radians(eulerXYZ[0] * 0.5));
898 double sx = sin(degrees_to_radians(eulerXYZ[0] * 0.5));
899 double cy = cos(degrees_to_radians(eulerXYZ[1] * 0.5));
900 double sy = sin(degrees_to_radians(eulerXYZ[1] * 0.5));
901 double cz = cos(degrees_to_radians(eulerXYZ[2] * 0.5));
902 double sz = sin(degrees_to_radians(eulerXYZ[2] * 0.5));
905 cz * cy * sx - sz * sy * cx,
906 cz * sy * cx + sz * cy * sx,
907 sz * cy * cx - cz * sy * sx,
908 cz * cy * cx + sz * sy * sx
913 struct RotateDialog {
914 ui::SpinButton x{ui::null};
915 ui::SpinButton y{ui::null};
916 ui::SpinButton z{ui::null};
917 ui::Window window{ui::null};
920 static gboolean rotatedlg_apply(ui::Widget widget, RotateDialog *rotateDialog)
924 eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value(rotateDialog->x));
925 eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value(rotateDialog->y));
926 eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value(rotateDialog->z));
928 StringOutputStream command;
929 command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
930 UndoableCommand undo(command.c_str());
932 GlobalSelectionSystem().rotateSelected(quaternion_for_euler_xyz_degrees(eulerXYZ));
936 static gboolean rotatedlg_cancel(ui::Widget widget, RotateDialog *rotateDialog)
938 rotateDialog->window.hide();
940 gtk_spin_button_set_value(rotateDialog->x, 0.0f); // reset to 0 on close
941 gtk_spin_button_set_value(rotateDialog->y, 0.0f);
942 gtk_spin_button_set_value(rotateDialog->z, 0.0f);
947 static gboolean rotatedlg_ok(ui::Widget widget, RotateDialog *rotateDialog)
949 rotatedlg_apply(widget, rotateDialog);
950 rotateDialog->window.hide();
954 static gboolean rotatedlg_delete(ui::Widget widget, GdkEventAny *event, RotateDialog *rotateDialog)
956 rotatedlg_cancel(widget, rotateDialog);
960 RotateDialog g_rotate_dialog;
964 if (!g_rotate_dialog.window) {
965 g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation",
966 G_CALLBACK(rotatedlg_delete),
969 auto accel = ui::AccelGroup(ui::New);
970 g_rotate_dialog.window.add_accel_group(accel);
973 auto hbox = create_dialog_hbox(4, 4);
974 g_rotate_dialog.window.add(hbox);
976 auto table = create_dialog_table(3, 2, 4, 4);
977 hbox.pack_start(table, TRUE, TRUE, 0);
979 ui::Widget label = ui::Label(" X ");
981 table.attach(label, {0, 1, 0, 1}, {0, 0});
984 ui::Widget label = ui::Label(" Y ");
986 table.attach(label, {0, 1, 1, 2}, {0, 0});
989 ui::Widget label = ui::Label(" Z ");
991 table.attach(label, {0, 1, 2, 3}, {0, 0});
994 auto adj = ui::Adjustment(0, -359, 359, 1, 10, 0);
995 auto spin = ui::SpinButton(adj, 1, 0);
997 table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
998 spin.dimensions(64, -1);
999 gtk_spin_button_set_wrap(spin, TRUE);
1001 gtk_widget_grab_focus(spin);
1003 g_rotate_dialog.x = spin;
1006 auto adj = ui::Adjustment(0, -359, 359, 1, 10, 0);
1007 auto spin = ui::SpinButton(adj, 1, 0);
1009 table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1010 spin.dimensions(64, -1);
1011 gtk_spin_button_set_wrap(spin, TRUE);
1013 g_rotate_dialog.y = spin;
1016 auto adj = ui::Adjustment(0, -359, 359, 1, 10, 0);
1017 auto spin = ui::SpinButton(adj, 1, 0);
1019 table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1020 spin.dimensions(64, -1);
1021 gtk_spin_button_set_wrap(spin, TRUE);
1023 g_rotate_dialog.z = spin;
1027 auto vbox = create_dialog_vbox(4);
1028 hbox.pack_start(vbox, TRUE, TRUE, 0);
1030 auto button = create_dialog_button("OK", G_CALLBACK(rotatedlg_ok), &g_rotate_dialog);
1031 vbox.pack_start(button, FALSE, FALSE, 0);
1032 widget_make_default(button);
1033 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
1037 auto button = create_dialog_button("Cancel", G_CALLBACK(rotatedlg_cancel), &g_rotate_dialog);
1038 vbox.pack_start(button, FALSE, FALSE, 0);
1039 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
1043 auto button = create_dialog_button("Apply", G_CALLBACK(rotatedlg_apply), &g_rotate_dialog);
1044 vbox.pack_start(button, FALSE, FALSE, 0);
1050 g_rotate_dialog.window.show();
1054 struct ScaleDialog {
1055 ui::Entry x{ui::null};
1056 ui::Entry y{ui::null};
1057 ui::Entry z{ui::null};
1058 ui::Window window{ui::null};
1061 static gboolean scaledlg_apply(ui::Widget widget, ScaleDialog *scaleDialog)
1065 sx = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(scaleDialog->x))));
1066 sy = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(scaleDialog->y))));
1067 sz = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(scaleDialog->z))));
1069 StringOutputStream command;
1070 command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1071 UndoableCommand undo(command.c_str());
1073 Select_Scale(sx, sy, sz);
1078 static gboolean scaledlg_cancel(ui::Widget widget, ScaleDialog *scaleDialog)
1080 scaleDialog->window.hide();
1082 scaleDialog->x.text("1.0");
1083 scaleDialog->y.text("1.0");
1084 scaleDialog->z.text("1.0");
1089 static gboolean scaledlg_ok(ui::Widget widget, ScaleDialog *scaleDialog)
1091 scaledlg_apply(widget, scaleDialog);
1092 scaleDialog->window.hide();
1096 static gboolean scaledlg_delete(ui::Widget widget, GdkEventAny *event, ScaleDialog *scaleDialog)
1098 scaledlg_cancel(widget, scaleDialog);
1102 ScaleDialog g_scale_dialog;
1106 if (!g_scale_dialog.window) {
1107 g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale",
1108 G_CALLBACK(scaledlg_delete),
1111 auto accel = ui::AccelGroup(ui::New);
1112 g_scale_dialog.window.add_accel_group(accel);
1115 auto hbox = create_dialog_hbox(4, 4);
1116 g_scale_dialog.window.add(hbox);
1118 auto table = create_dialog_table(3, 2, 4, 4);
1119 hbox.pack_start(table, TRUE, TRUE, 0);
1121 ui::Widget label = ui::Label(" X ");
1123 table.attach(label, {0, 1, 0, 1}, {0, 0});
1126 ui::Widget label = ui::Label(" Y ");
1128 table.attach(label, {0, 1, 1, 2}, {0, 0});
1131 ui::Widget label = ui::Label(" Z ");
1133 table.attach(label, {0, 1, 2, 3}, {0, 0});
1136 auto entry = ui::Entry(ui::New);
1139 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1141 g_scale_dialog.x = entry;
1144 auto entry = ui::Entry(ui::New);
1147 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1149 g_scale_dialog.y = entry;
1152 auto entry = ui::Entry(ui::New);
1155 table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1157 g_scale_dialog.z = entry;
1161 auto vbox = create_dialog_vbox(4);
1162 hbox.pack_start(vbox, TRUE, TRUE, 0);
1164 auto button = create_dialog_button("OK", G_CALLBACK(scaledlg_ok), &g_scale_dialog);
1165 vbox.pack_start(button, FALSE, FALSE, 0);
1166 widget_make_default(button);
1167 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
1171 auto button = create_dialog_button("Cancel", G_CALLBACK(scaledlg_cancel), &g_scale_dialog);
1172 vbox.pack_start(button, FALSE, FALSE, 0);
1173 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
1177 auto button = create_dialog_button("Apply", G_CALLBACK(scaledlg_apply), &g_scale_dialog);
1178 vbox.pack_start(button, FALSE, FALSE, 0);
1184 g_scale_dialog.window.show();