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"
56 select_workzone_t g_select_workzone;
60 Loops over all selected brushes and stores their
61 world AABBs in the specified array.
63 class CollectSelectedBrushesBounds : public SelectionSystem::Visitor
65 AABB* m_bounds; // array of AABBs
66 Unsigned m_max; // max AABB-elements in array
67 Unsigned& m_count; // count of valid AABBs stored in array
70 CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count )
77 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 );
86 if ( ( selectable != 0 )
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
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
109 SelectByBounds( AABB* aabbs, Unsigned count )
114 bool pre( const scene::Path& path, scene::Instance& instance ) const {
115 Selectable* selectable = Instance_getSelectable( instance );
118 Entity* entity = Node_getEntity( path.top() );
120 if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
125 if ( ( path.size() > 1 ) &&
126 ( !path.top().get().isRoot() ) &&
129 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 ){
146 if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
147 // we may not need all AABBs since not all selected objects have to be brushes
148 const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
149 AABB* aabbs = new AABB[max];
152 CollectSelectedBrushesBounds collector( aabbs, max, count );
153 GlobalSelectionSystem().foreachSelected( collector );
155 // nothing usable in selection
161 // delete selected objects
162 if ( delete_bounds_src ) { // see deleteSelection
163 UndoableCommand undo( "deleteSelected" );
167 // select objects with bounds
168 GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
177 SelectionPolicy for SelectByBounds
178 Returns true if box and the AABB of instance intersect
180 class SelectionPolicy_Touching
183 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
184 const AABB& other( instance.worldAABB() );
185 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
202 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
203 const AABB& other( instance.worldAABB() );
204 for ( Unsigned i = 0; i < 3; ++i )
206 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
214 class DeleteSelected : public scene::Graph::Walker
216 mutable bool m_remove;
217 mutable bool m_removedChild;
220 : m_remove( false ), m_removedChild( false ){
222 bool pre( const scene::Path& path, scene::Instance& instance ) const {
223 m_removedChild = false;
225 Selectable* selectable = Instance_getSelectable( instance );
227 && selectable->isSelected()
229 && !path.top().get().isRoot() ) {
232 return false; // dont traverse into child elements
236 void post( const scene::Path& path, scene::Instance& instance ) const {
238 if ( m_removedChild ) {
239 m_removedChild = false;
241 // delete empty entities
242 Entity* entity = Node_getEntity( path.top() );
244 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
245 && Node_getTraversable( path.top() )->empty() ) {
246 Path_deleteTop( path );
250 // node should be removed
252 if ( Node_isEntity( path.parent() ) != 0 ) {
253 m_removedChild = true;
257 Path_deleteTop( path );
262 void Scene_DeleteSelected( scene::Graph& graph ){
263 graph.traverse( DeleteSelected() );
267 void Select_Delete( void ){
268 Scene_DeleteSelected( GlobalSceneGraph() );
271 class InvertSelectionWalker : public scene::Graph::Walker
273 SelectionSystem::EMode m_mode;
274 mutable Selectable* m_selectable;
276 InvertSelectionWalker( SelectionSystem::EMode mode )
277 : m_mode( mode ), m_selectable( 0 ){
279 bool pre( const scene::Path& path, scene::Instance& instance ) const {
280 Selectable* selectable = Instance_getSelectable( instance );
284 case SelectionSystem::eEntity:
285 if ( Node_isEntity( path.top() ) != 0 ) {
286 m_selectable = path.top().get().visible() ? selectable : 0;
289 case SelectionSystem::ePrimitive:
290 m_selectable = path.top().get().visible() ? selectable : 0;
292 case SelectionSystem::eComponent:
298 void post( const scene::Path& path, scene::Instance& instance ) const {
299 if ( m_selectable != 0 ) {
300 m_selectable->setSelected( !m_selectable->isSelected() );
306 void Scene_Invert_Selection( scene::Graph& graph ){
307 graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) );
310 void Select_Invert(){
311 Scene_Invert_Selection( GlobalSceneGraph() );
314 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
316 mutable std::size_t m_depth;
317 NodeSmartReference worldspawn;
319 ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
321 bool pre( const scene::Path& path, scene::Instance& instance ) const {
325 NodeSmartReference me( path.top().get() );
326 if ( me == worldspawn ) {
330 if ( m_depth == 2 ) { // entity depth
331 // traverse and select children if any one is selected
332 if ( instance.childSelected() ) {
333 Instance_setSelected( instance, true );
335 return Node_getEntity( path.top() )->isContainer() && instance.isSelected();
337 else if ( m_depth == 3 ) { // primitive depth
338 Instance_setSelected( instance, true );
343 void post( const scene::Path& path, scene::Instance& instance ) const {
348 void Scene_ExpandSelectionToEntities(){
349 GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
355 void Selection_UpdateWorkzone(){
356 if ( GlobalSelectionSystem().countSelected() != 0 ) {
357 Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
360 typedef FreeCaller<void(), Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
362 IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
365 const select_workzone_t& Select_getWorkZone(){
366 g_idleWorkzone.flush();
367 return g_select_workzone;
370 void UpdateWorkzone_ForSelection(){
371 g_idleWorkzone.queueDraw();
374 // update the workzone to the current selection
375 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
376 if ( selectable.isSelected() ) {
377 UpdateWorkzone_ForSelection();
381 void Select_SetShader( const char* shader ){
382 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
383 Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
384 Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
386 Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
389 void Select_SetTexdef( const TextureProjection& projection ){
390 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
391 Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
393 Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
396 void Select_SetFlags( const ContentsFlagsValue& flags ){
397 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
398 Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
400 Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
403 void Select_GetBounds( Vector3& mins, Vector3& maxs ){
405 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
406 maxs = vector3_added( bounds.origin, bounds.extents );
407 mins = vector3_subtracted( bounds.origin, bounds.extents );
410 void Select_GetMid( Vector3& mid ){
412 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
413 mid = vector3_snapped( bounds.origin );
417 void Select_FlipAxis( int axis ){
418 Vector3 flip( 1, 1, 1 );
420 GlobalSelectionSystem().scaleSelected( flip );
424 void Select_Scale( float x, float y, float z ){
425 GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
441 inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
445 if ( sign == eSignPositive ) {
446 return matrix4_rotation_for_sincos_x( 1, 0 );
450 return matrix4_rotation_for_sincos_x( -1, 0 );
453 if ( sign == eSignPositive ) {
454 return matrix4_rotation_for_sincos_y( 1, 0 );
458 return matrix4_rotation_for_sincos_y( -1, 0 );
460 default: //case eAxisZ:
461 if ( sign == eSignPositive ) {
462 return matrix4_rotation_for_sincos_z( 1, 0 );
466 return matrix4_rotation_for_sincos_z( -1, 0 );
471 inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
472 matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
475 inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
476 matrix4_translate_by_vec3( matrix, pivotpoint );
477 matrix4_rotate_by_axis90( matrix, axis, sign );
478 matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
481 inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
486 if ( sign == eSignPositive ) {
487 return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
491 return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
494 if ( sign == eSignPositive ) {
495 return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
499 return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
501 default: //case eAxisZ:
502 if ( sign == eSignPositive ) {
503 return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
507 return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
511 quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
515 void Select_RotateAxis( int axis, float deg ){
516 if ( fabs( deg ) == 90.f ) {
517 GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
524 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ) );
527 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ) );
530 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ) );
537 void Select_ShiftTexture( float x, float y ){
538 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
539 Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
540 Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
542 //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
543 Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
546 void Select_ScaleTexture( float x, float y ){
547 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
548 Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
549 Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
551 Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
554 void Select_RotateTexture( float amt ){
555 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
556 Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
557 Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
559 Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
562 // TTimo modified to handle shader architecture:
563 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
564 void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
565 if ( !texdef_name_valid( pFind ) ) {
566 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
569 if ( !texdef_name_valid( pReplace ) ) {
570 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
574 StringOutputStream command;
575 command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
576 UndoableCommand undo( command.c_str() );
579 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
580 Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
581 Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
583 Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
587 Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
588 Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
592 typedef std::vector<const char*> PropertyValues;
594 bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
595 for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
597 if ( string_equal( str, *i ) ) {
604 class EntityFindByPropertyValueWalker : public scene::Graph::Walker
606 const PropertyValues& m_propertyvalues;
609 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
610 : m_propertyvalues( propertyvalues ), m_prop( prop ){
612 bool pre( const scene::Path& path, scene::Instance& instance ) const {
613 Entity* entity = Node_getEntity( path.top() );
615 && propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
616 Instance_getSelectable( instance )->setSelected( true );
622 void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
623 graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
626 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
628 PropertyValues& m_propertyvalues;
631 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
632 : m_propertyvalues( propertyvalues ), m_prop( prop ){
634 bool pre( const scene::Path& path, scene::Instance& instance ) const {
635 Selectable* selectable = Instance_getSelectable( instance );
637 && selectable->isSelected() ) {
638 Entity* entity = Node_getEntity( path.top() );
640 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
641 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
649 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
650 graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
653 void Select_AllOfType(){
654 if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
655 if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
656 GlobalSelectionSystem().setSelectedAllComponents( false );
657 Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
662 PropertyValues propertyvalues;
663 const char *prop = EntityInspector_getCurrentKey();
664 if ( !prop || !*prop ) {
667 Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
668 GlobalSelectionSystem().setSelectedAll( false );
669 if ( !propertyvalues.empty() ) {
670 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
674 Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
675 Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
680 void Select_Inside( void ){
681 SelectByBounds<SelectionPolicy_Inside>::DoSelection();
684 void Select_Touching( void ){
685 SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
688 void Select_FitTexture( float horizontal, float vertical ){
689 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
690 Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
692 Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
697 inline void hide_node( scene::Node& node, bool hide ){
699 ? node.enable( scene::Node::eHidden )
700 : node.disable( scene::Node::eHidden );
703 class HideSelectedWalker : public scene::Graph::Walker
707 HideSelectedWalker( bool hide )
710 bool pre( const scene::Path& path, scene::Instance& instance ) const {
711 Selectable* selectable = Instance_getSelectable( instance );
713 && selectable->isSelected() ) {
714 hide_node( path.top(), m_hide );
720 void Scene_Hide_Selected( bool hide ){
721 GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
725 Scene_Hide_Selected( true );
731 GlobalSelectionSystem().setSelectedAll( false );
735 class HideAllWalker : public scene::Graph::Walker
739 HideAllWalker( bool hide )
742 bool pre( const scene::Path& path, scene::Instance& instance ) const {
743 hide_node( path.top(), m_hide );
748 void Scene_Hide_All( bool hide ){
749 GlobalSceneGraph().traverse( HideAllWalker( hide ) );
752 void Select_ShowAllHidden(){
753 Scene_Hide_All( false );
759 void Selection_Flipx(){
760 UndoableCommand undo( "mirrorSelected -axis x" );
761 Select_FlipAxis( 0 );
764 void Selection_Flipy(){
765 UndoableCommand undo( "mirrorSelected -axis y" );
766 Select_FlipAxis( 1 );
769 void Selection_Flipz(){
770 UndoableCommand undo( "mirrorSelected -axis z" );
771 Select_FlipAxis( 2 );
774 void Selection_Rotatex(){
775 UndoableCommand undo( "rotateSelected -axis x -angle -90" );
776 Select_RotateAxis( 0,-90 );
779 void Selection_Rotatey(){
780 UndoableCommand undo( "rotateSelected -axis y -angle 90" );
781 Select_RotateAxis( 1, 90 );
784 void Selection_Rotatez(){
785 UndoableCommand undo( "rotateSelected -axis z -angle -90" );
786 Select_RotateAxis( 2,-90 );
791 void Nudge( int nDim, float fNudge ){
792 Vector3 translate( 0, 0, 0 );
793 translate[nDim] = fNudge;
795 GlobalSelectionSystem().translateSelected( translate );
798 void Selection_NudgeZ( float amount ){
799 StringOutputStream command;
800 command << "nudgeSelected -axis z -amount " << amount;
801 UndoableCommand undo( command.c_str() );
806 void Selection_MoveDown(){
807 Selection_NudgeZ( -GetGridSize() );
810 void Selection_MoveUp(){
811 Selection_NudgeZ( GetGridSize() );
814 void SceneSelectionChange( const Selectable& selectable ){
818 SignalHandlerId Selection_boundsChanged;
820 void Selection_construct(){
821 typedef FreeCaller<void(const Selectable&), SceneSelectionChange> SceneSelectionChangeCaller;
822 GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
823 typedef FreeCaller<void(const Selectable&), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
824 GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
825 typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
826 Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
829 void Selection_destroy(){
830 GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
835 #include <gdk/gdkkeysyms.h>
838 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
840 return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
842 return quaternion_multiplied_by_quaternion(
843 quaternion_multiplied_by_quaternion(
844 quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
845 quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
847 quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
850 double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
851 double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
852 double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
853 double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
854 double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
855 double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
858 cz * cy * sx - sz * sy * cx,
859 cz * sy * cx + sz * cy * sx,
860 sz * cy * cx - cz * sy * sx,
861 cz * cy * cx + sz * sy * sx
868 ui::SpinButton x{ui::null};
869 ui::SpinButton y{ui::null};
870 ui::SpinButton z{ui::null};
871 ui::Window window{ui::null};
874 static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
877 eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
878 eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
879 eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
881 StringOutputStream command;
882 command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
883 UndoableCommand undo( command.c_str() );
885 GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) );
889 static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog ){
890 rotateDialog->window.hide();
892 gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
893 gtk_spin_button_set_value( rotateDialog->y, 0.0f );
894 gtk_spin_button_set_value( rotateDialog->z, 0.0f );
899 static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
900 rotatedlg_apply( widget, rotateDialog );
901 rotateDialog->window.hide();
905 static gboolean rotatedlg_delete( ui::Widget widget, GdkEventAny *event, RotateDialog* rotateDialog ){
906 rotatedlg_cancel( widget, rotateDialog );
910 RotateDialog g_rotate_dialog;
912 if ( !g_rotate_dialog.window ) {
913 g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
915 auto accel = ui::AccelGroup(ui::New);
916 g_rotate_dialog.window.add_accel_group( accel );
919 auto hbox = create_dialog_hbox( 4, 4 );
920 g_rotate_dialog.window.add(hbox);
922 auto table = create_dialog_table( 3, 2, 4, 4 );
923 hbox.pack_start( table, TRUE, TRUE, 0 );
925 ui::Widget label = ui::Label( " X " );
927 table.attach(label, {0, 1, 0, 1}, {0, 0});
930 ui::Widget label = ui::Label( " Y " );
932 table.attach(label, {0, 1, 1, 2}, {0, 0});
935 ui::Widget label = ui::Label( " Z " );
937 table.attach(label, {0, 1, 2, 3}, {0, 0});
940 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
941 auto spin = ui::SpinButton( adj, 1, 0 );
943 table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
944 spin.dimensions(64, -1);
945 gtk_spin_button_set_wrap( spin, TRUE );
947 gtk_widget_grab_focus( spin );
949 g_rotate_dialog.x = spin;
952 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
953 auto spin = ui::SpinButton( adj, 1, 0 );
955 table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
956 spin.dimensions(64, -1);
957 gtk_spin_button_set_wrap( spin, TRUE );
959 g_rotate_dialog.y = spin;
962 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
963 auto spin = ui::SpinButton( adj, 1, 0 );
965 table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
966 spin.dimensions(64, -1);
967 gtk_spin_button_set_wrap( spin, TRUE );
969 g_rotate_dialog.z = spin;
973 auto vbox = create_dialog_vbox( 4 );
974 hbox.pack_start( vbox, TRUE, TRUE, 0 );
976 auto button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
977 vbox.pack_start( button, FALSE, FALSE, 0 );
978 widget_make_default( button );
979 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
982 auto button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
983 vbox.pack_start( button, FALSE, FALSE, 0 );
984 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
987 auto button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
988 vbox.pack_start( button, FALSE, FALSE, 0 );
994 g_rotate_dialog.window.show();
1007 ui::Entry x{ui::null};
1008 ui::Entry y{ui::null};
1009 ui::Entry z{ui::null};
1010 ui::Window window{ui::null};
1013 static gboolean scaledlg_apply( ui::Widget widget, ScaleDialog* scaleDialog ){
1016 sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1017 sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1018 sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1020 StringOutputStream command;
1021 command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1022 UndoableCommand undo( command.c_str() );
1024 Select_Scale( sx, sy, sz );
1029 static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
1030 scaleDialog->window.hide();
1032 scaleDialog->x.text("1.0");
1033 scaleDialog->y.text("1.0");
1034 scaleDialog->z.text("1.0");
1039 static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
1040 scaledlg_apply( widget, scaleDialog );
1041 scaleDialog->window.hide();
1045 static gboolean scaledlg_delete( ui::Widget widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1046 scaledlg_cancel( widget, scaleDialog );
1050 ScaleDialog g_scale_dialog;
1053 if ( !g_scale_dialog.window ) {
1054 g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", G_CALLBACK(scaledlg_delete ), &g_scale_dialog );
1056 auto accel = ui::AccelGroup(ui::New);
1057 g_scale_dialog.window.add_accel_group( accel );
1060 auto hbox = create_dialog_hbox( 4, 4 );
1061 g_scale_dialog.window.add(hbox);
1063 auto table = create_dialog_table( 3, 2, 4, 4 );
1064 hbox.pack_start( table, TRUE, TRUE, 0 );
1066 ui::Widget label = ui::Label( " X " );
1068 table.attach(label, {0, 1, 0, 1}, {0, 0});
1071 ui::Widget label = ui::Label( " Y " );
1073 table.attach(label, {0, 1, 1, 2}, {0, 0});
1076 ui::Widget label = ui::Label( " Z " );
1078 table.attach(label, {0, 1, 2, 3}, {0, 0});
1081 auto entry = ui::Entry(ui::New);
1084 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1086 g_scale_dialog.x = entry;
1089 auto entry = ui::Entry(ui::New);
1092 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1094 g_scale_dialog.y = entry;
1097 auto entry = ui::Entry(ui::New);
1100 table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1102 g_scale_dialog.z = entry;
1106 auto vbox = create_dialog_vbox( 4 );
1107 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1109 auto button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1110 vbox.pack_start( button, FALSE, FALSE, 0 );
1111 widget_make_default( button );
1112 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1115 auto button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1116 vbox.pack_start( button, FALSE, FALSE, 0 );
1117 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1120 auto button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1121 vbox.pack_start( button, FALSE, FALSE, 0 );
1127 g_scale_dialog.window.show();