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
24 #include "debugging/debugging.h"
27 #include "iselection.h"
32 #include "stream/stringstream.h"
33 #include "signal/isignal.h"
34 #include "shaderlib.h"
37 #include "gtkutil/idledraw.h"
38 #include "gtkutil/dialog.h"
39 #include "gtkutil/widget.h"
40 #include "brushmanip.h"
42 #include "patchmanip.h"
43 #include "patchdialog.h"
44 #include "selection.h"
45 #include "texwindow.h"
47 #include "mainframe.h"
50 #include "entityinspector.h"
54 select_workzone_t g_select_workzone;
58 Loops over all selected brushes and stores their
59 world AABBs in the specified array.
61 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 )
75 void visit( scene::Instance& instance ) const {
76 ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" );
78 // stop if the array is already full
79 if ( m_count == m_max ) {
83 Selectable* selectable = Instance_getSelectable( instance );
84 if ( ( selectable != 0 )
85 && instance.isSelected() ) {
87 if ( Instance_getBrush( instance ) != 0 ) {
88 m_bounds[m_count] = instance.worldAABB();
96 Selects all objects that intersect one of the bounding AABBs.
97 The exact intersection-method is specified through TSelectionPolicy
99 template<class TSelectionPolicy>
100 class SelectByBounds : public scene::Graph::Walker
102 AABB* m_aabbs; // selection aabbs
103 Unsigned m_count; // number of aabbs in m_aabbs
104 TSelectionPolicy policy; // type that contains a custom intersection method aabb<->aabb
107 SelectByBounds( AABB* aabbs, Unsigned count )
112 bool pre( const scene::Path& path, scene::Instance& instance ) const {
113 Selectable* selectable = Instance_getSelectable( instance );
116 Entity* entity = Node_getEntity( path.top() );
118 if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
123 if ( ( path.size() > 1 ) &&
124 ( !path.top().get().isRoot() ) &&
127 for ( Unsigned i = 0; i < m_count; ++i )
129 if ( policy.Evaluate( m_aabbs[i], instance ) ) {
130 selectable->setSelected( true );
139 Performs selection operation on the global scenegraph.
140 If delete_bounds_src is true, then the objects which were
141 used as source for the selection aabbs will be deleted.
143 static void DoSelection( bool delete_bounds_src = true ){
144 if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
145 // we may not need all AABBs since not all selected objects have to be brushes
146 const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
147 AABB* aabbs = new AABB[max];
150 CollectSelectedBrushesBounds collector( aabbs, max, count );
151 GlobalSelectionSystem().foreachSelected( collector );
153 // nothing usable in selection
159 // delete selected objects
160 if ( delete_bounds_src ) { // see deleteSelection
161 UndoableCommand undo( "deleteSelected" );
165 // select objects with bounds
166 GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
175 SelectionPolicy for SelectByBounds
176 Returns true if box and the AABB of instance intersect
178 class SelectionPolicy_Touching
181 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
182 const AABB& other( instance.worldAABB() );
183 for ( Unsigned i = 0; i < 3; ++i )
185 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
194 SelectionPolicy for SelectByBounds
195 Returns true if the AABB of instance is inside box
197 class SelectionPolicy_Inside
200 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
201 const AABB& other( instance.worldAABB() );
202 for ( Unsigned i = 0; i < 3; ++i )
204 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
212 class DeleteSelected : public scene::Graph::Walker
214 mutable bool m_remove;
215 mutable bool m_removedChild;
218 : m_remove( false ), m_removedChild( false ){
220 bool pre( const scene::Path& path, scene::Instance& instance ) const {
221 m_removedChild = false;
223 Selectable* selectable = Instance_getSelectable( instance );
225 && selectable->isSelected()
227 && !path.top().get().isRoot() ) {
230 return false; // dont traverse into child elements
234 void post( const scene::Path& path, scene::Instance& instance ) const {
236 if ( m_removedChild ) {
237 m_removedChild = false;
239 // delete empty entities
240 Entity* entity = Node_getEntity( path.top() );
242 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
243 && Node_getTraversable( path.top() )->empty() ) {
244 Path_deleteTop( path );
248 // node should be removed
250 if ( Node_isEntity( path.parent() ) != 0 ) {
251 m_removedChild = true;
255 Path_deleteTop( path );
260 void Scene_DeleteSelected( scene::Graph& graph ){
261 graph.traverse( DeleteSelected() );
265 void Select_Delete( void ){
266 Scene_DeleteSelected( GlobalSceneGraph() );
269 class InvertSelectionWalker : public scene::Graph::Walker
271 SelectionSystem::EMode m_mode;
272 mutable Selectable* m_selectable;
274 InvertSelectionWalker( SelectionSystem::EMode mode )
275 : m_mode( mode ), m_selectable( 0 ){
277 bool pre( const scene::Path& path, scene::Instance& instance ) const {
278 Selectable* selectable = Instance_getSelectable( instance );
282 case SelectionSystem::eEntity:
283 if ( Node_isEntity( path.top() ) != 0 ) {
284 m_selectable = path.top().get().visible() ? selectable : 0;
287 case SelectionSystem::ePrimitive:
288 m_selectable = path.top().get().visible() ? selectable : 0;
290 case SelectionSystem::eComponent:
296 void post( const scene::Path& path, scene::Instance& instance ) const {
297 if ( m_selectable != 0 ) {
298 m_selectable->setSelected( !m_selectable->isSelected() );
304 void Scene_Invert_Selection( scene::Graph& graph ){
305 graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) );
308 void Select_Invert(){
309 Scene_Invert_Selection( GlobalSceneGraph() );
312 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
314 mutable std::size_t m_depth;
315 NodeSmartReference worldspawn;
317 ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
319 bool pre( const scene::Path& path, scene::Instance& instance ) const {
323 NodeSmartReference me( path.top().get() );
324 if ( me == worldspawn ) {
328 if ( m_depth == 2 ) { // entity depth
329 // traverse and select children if any one is selected
330 if ( instance.childSelected() ) {
331 Instance_setSelected( instance, true );
333 return Node_getEntity( path.top() )->isContainer() && instance.isSelected();
335 else if ( m_depth == 3 ) { // primitive depth
336 Instance_setSelected( instance, true );
341 void post( const scene::Path& path, scene::Instance& instance ) const {
346 void Scene_ExpandSelectionToEntities(){
347 GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
353 void Selection_UpdateWorkzone(){
354 if ( GlobalSelectionSystem().countSelected() != 0 ) {
355 Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
358 typedef FreeCaller<Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
360 IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
363 const select_workzone_t& Select_getWorkZone(){
364 g_idleWorkzone.flush();
365 return g_select_workzone;
368 void UpdateWorkzone_ForSelection(){
369 g_idleWorkzone.queueDraw();
372 // update the workzone to the current selection
373 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
374 if ( selectable.isSelected() ) {
375 UpdateWorkzone_ForSelection();
379 void Select_SetShader( const char* shader ){
380 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
381 Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
382 Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
384 Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
387 void Select_SetTexdef( const TextureProjection& projection ){
388 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
389 Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
391 Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
394 void Select_SetFlags( const ContentsFlagsValue& flags ){
395 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
396 Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
398 Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
401 void Select_GetBounds( Vector3& mins, Vector3& maxs ){
403 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
404 maxs = vector3_added( bounds.origin, bounds.extents );
405 mins = vector3_subtracted( bounds.origin, bounds.extents );
408 void Select_GetMid( Vector3& mid ){
410 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
411 mid = vector3_snapped( bounds.origin );
415 void Select_FlipAxis( int axis ){
416 Vector3 flip( 1, 1, 1 );
418 GlobalSelectionSystem().scaleSelected( flip );
422 void Select_Scale( float x, float y, float z ){
423 GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
439 inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
443 if ( sign == eSignPositive ) {
444 return matrix4_rotation_for_sincos_x( 1, 0 );
448 return matrix4_rotation_for_sincos_x( -1, 0 );
451 if ( sign == eSignPositive ) {
452 return matrix4_rotation_for_sincos_y( 1, 0 );
456 return matrix4_rotation_for_sincos_y( -1, 0 );
458 default: //case eAxisZ:
459 if ( sign == eSignPositive ) {
460 return matrix4_rotation_for_sincos_z( 1, 0 );
464 return matrix4_rotation_for_sincos_z( -1, 0 );
469 inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
470 matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
473 inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
474 matrix4_translate_by_vec3( matrix, pivotpoint );
475 matrix4_rotate_by_axis90( matrix, axis, sign );
476 matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
479 inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
483 if ( sign == eSignPositive ) {
484 return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
488 return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
491 if ( sign == eSignPositive ) {
492 return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
496 return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
498 default: //case eAxisZ:
499 if ( sign == eSignPositive ) {
500 return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
504 return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
509 void Select_RotateAxis( int axis, float deg ){
510 if ( fabs( deg ) == 90.f ) {
511 GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
518 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ) );
521 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ) );
524 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ) );
531 void Select_ShiftTexture( float x, float y ){
532 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
533 Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
534 Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
536 //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
537 Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
540 void Select_ScaleTexture( float x, float y ){
541 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
542 Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
543 Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
545 Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
548 void Select_RotateTexture( float amt ){
549 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
550 Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
551 Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
553 Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
556 // TTimo modified to handle shader architecture:
557 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
558 void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
559 if ( !texdef_name_valid( pFind ) ) {
560 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
563 if ( !texdef_name_valid( pReplace ) ) {
564 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
568 StringOutputStream command;
569 command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
570 UndoableCommand undo( command.c_str() );
573 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
574 Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
575 Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
577 Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
581 Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
582 Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
586 typedef std::vector<const char*> PropertyValues;
588 bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
589 for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
591 if ( string_equal( str, *i ) ) {
598 class EntityFindByPropertyValueWalker : public scene::Graph::Walker
600 const PropertyValues& m_propertyvalues;
603 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
604 : m_propertyvalues( propertyvalues ), m_prop( prop ){
606 bool pre( const scene::Path& path, scene::Instance& instance ) const {
607 Entity* entity = Node_getEntity( path.top() );
609 && propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
610 Instance_getSelectable( instance )->setSelected( true );
616 void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
617 graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
620 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
622 PropertyValues& m_propertyvalues;
625 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
626 : m_propertyvalues( propertyvalues ), m_prop( prop ){
628 bool pre( const scene::Path& path, scene::Instance& instance ) const {
629 Selectable* selectable = Instance_getSelectable( instance );
631 && selectable->isSelected() ) {
632 Entity* entity = Node_getEntity( path.top() );
634 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
635 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
643 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
644 graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
647 void Select_AllOfType(){
648 if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
649 if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
650 GlobalSelectionSystem().setSelectedAllComponents( false );
651 Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
656 PropertyValues propertyvalues;
657 const char *prop = EntityInspector_getCurrentKey();
658 if ( !prop || !*prop ) {
661 Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
662 GlobalSelectionSystem().setSelectedAll( false );
663 if ( !propertyvalues.empty() ) {
664 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
668 Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
669 Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
674 void Select_Inside( void ){
675 SelectByBounds<SelectionPolicy_Inside>::DoSelection();
678 void Select_Touching( void ){
679 SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
682 void Select_FitTexture( float horizontal, float vertical ){
683 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
684 Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
686 Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
691 inline void hide_node( scene::Node& node, bool hide ){
693 ? node.enable( scene::Node::eHidden )
694 : node.disable( scene::Node::eHidden );
697 class HideSelectedWalker : public scene::Graph::Walker
701 HideSelectedWalker( bool hide )
704 bool pre( const scene::Path& path, scene::Instance& instance ) const {
705 Selectable* selectable = Instance_getSelectable( instance );
707 && selectable->isSelected() ) {
708 hide_node( path.top(), m_hide );
714 void Scene_Hide_Selected( bool hide ){
715 GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
719 Scene_Hide_Selected( true );
725 GlobalSelectionSystem().setSelectedAll( false );
729 class HideAllWalker : public scene::Graph::Walker
733 HideAllWalker( bool hide )
736 bool pre( const scene::Path& path, scene::Instance& instance ) const {
737 hide_node( path.top(), m_hide );
742 void Scene_Hide_All( bool hide ){
743 GlobalSceneGraph().traverse( HideAllWalker( hide ) );
746 void Select_ShowAllHidden(){
747 Scene_Hide_All( false );
753 void Selection_Flipx(){
754 UndoableCommand undo( "mirrorSelected -axis x" );
755 Select_FlipAxis( 0 );
758 void Selection_Flipy(){
759 UndoableCommand undo( "mirrorSelected -axis y" );
760 Select_FlipAxis( 1 );
763 void Selection_Flipz(){
764 UndoableCommand undo( "mirrorSelected -axis z" );
765 Select_FlipAxis( 2 );
768 void Selection_Rotatex(){
769 UndoableCommand undo( "rotateSelected -axis x -angle -90" );
770 Select_RotateAxis( 0,-90 );
773 void Selection_Rotatey(){
774 UndoableCommand undo( "rotateSelected -axis y -angle 90" );
775 Select_RotateAxis( 1, 90 );
778 void Selection_Rotatez(){
779 UndoableCommand undo( "rotateSelected -axis z -angle -90" );
780 Select_RotateAxis( 2,-90 );
785 void Nudge( int nDim, float fNudge ){
786 Vector3 translate( 0, 0, 0 );
787 translate[nDim] = fNudge;
789 GlobalSelectionSystem().translateSelected( translate );
792 void Selection_NudgeZ( float amount ){
793 StringOutputStream command;
794 command << "nudgeSelected -axis z -amount " << amount;
795 UndoableCommand undo( command.c_str() );
800 void Selection_MoveDown(){
801 Selection_NudgeZ( -GetGridSize() );
804 void Selection_MoveUp(){
805 Selection_NudgeZ( GetGridSize() );
808 void SceneSelectionChange( const Selectable& selectable ){
812 SignalHandlerId Selection_boundsChanged;
814 void Selection_construct(){
815 typedef FreeCaller1<const Selectable&, SceneSelectionChange> SceneSelectionChangeCaller;
816 GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
817 typedef FreeCaller1<const Selectable&, UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
818 GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
819 typedef FreeCaller<UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
820 Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
823 void Selection_destroy(){
824 GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
829 #include <gtk/gtkbox.h>
830 #include <gtk/gtkspinbutton.h>
831 #include <gtk/gtktable.h>
832 #include <gtk/gtklabel.h>
833 #include <gdk/gdkkeysyms.h>
836 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
837 double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
838 double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
839 double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
840 double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
841 double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
842 double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
845 cz * cy * sx - sz * sy * cx,
846 cz * sy * cx + sz * cy * sx,
847 sz * cy * cx - cz * sy * sx,
848 cz * cy * cx + sz * sy * sx
860 static gboolean rotatedlg_apply( GtkWidget *widget, RotateDialog* rotateDialog ){
863 eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
864 eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
865 eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
867 StringOutputStream command;
868 command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
869 UndoableCommand undo( command.c_str() );
871 GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) );
875 static gboolean rotatedlg_cancel( GtkWidget *widget, RotateDialog* rotateDialog ){
876 gtk_widget_hide( GTK_WIDGET( rotateDialog->window ) );
878 gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
879 gtk_spin_button_set_value( rotateDialog->y, 0.0f );
880 gtk_spin_button_set_value( rotateDialog->z, 0.0f );
885 static gboolean rotatedlg_ok( GtkWidget *widget, RotateDialog* rotateDialog ){
886 rotatedlg_apply( widget, rotateDialog );
887 gtk_widget_hide( GTK_WIDGET( rotateDialog->window ) );
891 static gboolean rotatedlg_delete( GtkWidget *widget, GdkEventAny *event, RotateDialog* rotateDialog ){
892 rotatedlg_cancel( widget, rotateDialog );
896 RotateDialog g_rotate_dialog;
898 if ( g_rotate_dialog.window == NULL ) {
899 g_rotate_dialog.window = create_dialog_window( MainFrame_getWindow(), "Arbitrary rotation", G_CALLBACK( rotatedlg_delete ), &g_rotate_dialog );
901 GtkAccelGroup* accel = gtk_accel_group_new();
902 gtk_window_add_accel_group( g_rotate_dialog.window, accel );
905 GtkHBox* hbox = create_dialog_hbox( 4, 4 );
906 gtk_container_add( GTK_CONTAINER( g_rotate_dialog.window ), GTK_WIDGET( hbox ) );
908 GtkTable* table = create_dialog_table( 3, 2, 4, 4 );
909 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
911 GtkWidget* label = gtk_label_new( " X " );
912 gtk_widget_show( label );
913 gtk_table_attach( table, label, 0, 1, 0, 1,
914 (GtkAttachOptions) ( 0 ),
915 (GtkAttachOptions) ( 0 ), 0, 0 );
918 GtkWidget* label = gtk_label_new( " Y " );
919 gtk_widget_show( label );
920 gtk_table_attach( table, label, 0, 1, 1, 2,
921 (GtkAttachOptions) ( 0 ),
922 (GtkAttachOptions) ( 0 ), 0, 0 );
925 GtkWidget* label = gtk_label_new( " Z " );
926 gtk_widget_show( label );
927 gtk_table_attach( table, label, 0, 1, 2, 3,
928 (GtkAttachOptions) ( 0 ),
929 (GtkAttachOptions) ( 0 ), 0, 0 );
932 GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
933 GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) );
934 gtk_widget_show( GTK_WIDGET( spin ) );
935 gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
936 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
937 (GtkAttachOptions) ( 0 ), 0, 0 );
938 gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
939 gtk_spin_button_set_wrap( spin, TRUE );
941 gtk_widget_grab_focus( GTK_WIDGET( spin ) );
943 g_rotate_dialog.x = spin;
946 GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
947 GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) );
948 gtk_widget_show( GTK_WIDGET( spin ) );
949 gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 1, 2,
950 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
951 (GtkAttachOptions) ( 0 ), 0, 0 );
952 gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
953 gtk_spin_button_set_wrap( spin, TRUE );
955 g_rotate_dialog.y = spin;
958 GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) );
959 GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) );
960 gtk_widget_show( GTK_WIDGET( spin ) );
961 gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 2, 3,
962 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
963 (GtkAttachOptions) ( 0 ), 0, 0 );
964 gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
965 gtk_spin_button_set_wrap( spin, TRUE );
967 g_rotate_dialog.z = spin;
971 GtkVBox* vbox = create_dialog_vbox( 4 );
972 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
974 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
975 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
976 widget_make_default( GTK_WIDGET( button ) );
977 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
980 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
981 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
982 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
985 GtkButton* button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
986 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
992 gtk_widget_show( GTK_WIDGET( g_rotate_dialog.window ) );
1011 static gboolean scaledlg_apply( GtkWidget *widget, ScaleDialog* scaleDialog ){
1014 sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1015 sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1016 sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1018 StringOutputStream command;
1019 command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1020 UndoableCommand undo( command.c_str() );
1022 Select_Scale( sx, sy, sz );
1027 static gboolean scaledlg_cancel( GtkWidget *widget, ScaleDialog* scaleDialog ){
1028 gtk_widget_hide( GTK_WIDGET( scaleDialog->window ) );
1030 gtk_entry_set_text( GTK_ENTRY( scaleDialog->x ), "1.0" );
1031 gtk_entry_set_text( GTK_ENTRY( scaleDialog->y ), "1.0" );
1032 gtk_entry_set_text( GTK_ENTRY( scaleDialog->z ), "1.0" );
1037 static gboolean scaledlg_ok( GtkWidget *widget, ScaleDialog* scaleDialog ){
1038 scaledlg_apply( widget, scaleDialog );
1039 gtk_widget_hide( GTK_WIDGET( scaleDialog->window ) );
1043 static gboolean scaledlg_delete( GtkWidget *widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1044 scaledlg_cancel( widget, scaleDialog );
1048 ScaleDialog g_scale_dialog;
1051 if ( g_scale_dialog.window == NULL ) {
1052 g_scale_dialog.window = create_dialog_window( MainFrame_getWindow(), "Arbitrary scale", G_CALLBACK( scaledlg_delete ), &g_scale_dialog );
1054 GtkAccelGroup* accel = gtk_accel_group_new();
1055 gtk_window_add_accel_group( g_scale_dialog.window, accel );
1058 GtkHBox* hbox = create_dialog_hbox( 4, 4 );
1059 gtk_container_add( GTK_CONTAINER( g_scale_dialog.window ), GTK_WIDGET( hbox ) );
1061 GtkTable* table = create_dialog_table( 3, 2, 4, 4 );
1062 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1064 GtkWidget* label = gtk_label_new( " X " );
1065 gtk_widget_show( label );
1066 gtk_table_attach( table, label, 0, 1, 0, 1,
1067 (GtkAttachOptions) ( 0 ),
1068 (GtkAttachOptions) ( 0 ), 0, 0 );
1071 GtkWidget* label = gtk_label_new( " Y " );
1072 gtk_widget_show( label );
1073 gtk_table_attach( table, label, 0, 1, 1, 2,
1074 (GtkAttachOptions) ( 0 ),
1075 (GtkAttachOptions) ( 0 ), 0, 0 );
1078 GtkWidget* label = gtk_label_new( " Z " );
1079 gtk_widget_show( label );
1080 gtk_table_attach( table, label, 0, 1, 2, 3,
1081 (GtkAttachOptions) ( 0 ),
1082 (GtkAttachOptions) ( 0 ), 0, 0 );
1085 GtkWidget* entry = gtk_entry_new();
1086 gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" );
1087 gtk_widget_show( entry );
1088 gtk_table_attach( table, entry, 1, 2, 0, 1,
1089 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1090 (GtkAttachOptions) ( 0 ), 0, 0 );
1092 g_scale_dialog.x = entry;
1095 GtkWidget* entry = gtk_entry_new();
1096 gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" );
1097 gtk_widget_show( entry );
1098 gtk_table_attach( table, entry, 1, 2, 1, 2,
1099 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1100 (GtkAttachOptions) ( 0 ), 0, 0 );
1102 g_scale_dialog.y = entry;
1105 GtkWidget* entry = gtk_entry_new();
1106 gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" );
1107 gtk_widget_show( entry );
1108 gtk_table_attach( table, entry, 1, 2, 2, 3,
1109 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1110 (GtkAttachOptions) ( 0 ), 0, 0 );
1112 g_scale_dialog.z = entry;
1116 GtkVBox* vbox = create_dialog_vbox( 4 );
1117 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
1119 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1120 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1121 widget_make_default( GTK_WIDGET( button ) );
1122 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1125 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1126 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1127 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1130 GtkButton* button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1131 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1137 gtk_widget_show( GTK_WIDGET( g_scale_dialog.window ) );