/* Copyright (C) 1999-2006 Id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "select.h" #include "debugging/debugging.h" #include "ientity.h" #include "iselection.h" #include "iundo.h" #include #include "stream/stringstream.h" #include "signal/isignal.h" #include "shaderlib.h" #include "scenelib.h" #include "gtkutil/idledraw.h" #include "gtkutil/dialog.h" #include "gtkutil/widget.h" #include "brushmanip.h" #include "brush.h" #include "patchmanip.h" #include "patchdialog.h" #include "selection.h" #include "texwindow.h" #include "gtkmisc.h" #include "mainframe.h" #include "grid.h" #include "map.h" #include "entityinspector.h" select_workzone_t g_select_workzone; /** Loops over all selected brushes and stores their world AABBs in the specified array. */ class CollectSelectedBrushesBounds : public SelectionSystem::Visitor { AABB* m_bounds; // array of AABBs Unsigned m_max; // max AABB-elements in array Unsigned& m_count; // count of valid AABBs stored in array public: CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count ) : m_bounds( bounds ), m_max( max ), m_count( count ){ m_count = 0; } void visit( scene::Instance& instance ) const { ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" ); // stop if the array is already full if ( m_count == m_max ) { return; } Selectable* selectable = Instance_getSelectable( instance ); if ( ( selectable != 0 ) && instance.isSelected() ) { // brushes only if ( Instance_getBrush( instance ) != 0 ) { m_bounds[m_count] = instance.worldAABB(); ++m_count; } } } }; /** Selects all objects that intersect one of the bounding AABBs. The exact intersection-method is specified through TSelectionPolicy */ template class SelectByBounds : public scene::Graph::Walker { AABB* m_aabbs; // selection aabbs Unsigned m_count; // number of aabbs in m_aabbs TSelectionPolicy policy; // type that contains a custom intersection method aabb<->aabb public: SelectByBounds( AABB* aabbs, Unsigned count ) : m_aabbs( aabbs ), m_count( count ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { Selectable* selectable = Instance_getSelectable( instance ); // ignore worldspawn Entity* entity = Node_getEntity( path.top() ); if ( entity ) { if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) { return true; } } if ( ( path.size() > 1 ) && ( !path.top().get().isRoot() ) && ( selectable != 0 ) ) { for ( Unsigned i = 0; i < m_count; ++i ) { if ( policy.Evaluate( m_aabbs[i], instance ) ) { selectable->setSelected( true ); } } } return true; } /** Performs selection operation on the global scenegraph. If delete_bounds_src is true, then the objects which were used as source for the selection aabbs will be deleted. */ static void DoSelection( bool delete_bounds_src = true ){ if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) { // we may not need all AABBs since not all selected objects have to be brushes const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected(); AABB* aabbs = new AABB[max]; Unsigned count; CollectSelectedBrushesBounds collector( aabbs, max, count ); GlobalSelectionSystem().foreachSelected( collector ); // nothing usable in selection if ( !count ) { delete[] aabbs; return; } // delete selected objects if ( delete_bounds_src ) { // see deleteSelection UndoableCommand undo( "deleteSelected" ); Select_Delete(); } // select objects with bounds GlobalSceneGraph().traverse( SelectByBounds( aabbs, count ) ); SceneChangeNotify(); delete[] aabbs; } } }; /** SelectionPolicy for SelectByBounds Returns true if box and the AABB of instance intersect */ class SelectionPolicy_Touching { public: bool Evaluate( const AABB& box, scene::Instance& instance ) const { const AABB& other( instance.worldAABB() ); for ( Unsigned i = 0; i < 3; ++i ) { if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) { return false; } } return true; } }; /** SelectionPolicy for SelectByBounds Returns true if the AABB of instance is inside box */ class SelectionPolicy_Inside { public: bool Evaluate( const AABB& box, scene::Instance& instance ) const { const AABB& other( instance.worldAABB() ); for ( Unsigned i = 0; i < 3; ++i ) { if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) { return false; } } return true; } }; class DeleteSelected : public scene::Graph::Walker { mutable bool m_remove; mutable bool m_removedChild; public: DeleteSelected() : m_remove( false ), m_removedChild( false ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { m_removedChild = false; Selectable* selectable = Instance_getSelectable( instance ); if ( selectable != 0 && selectable->isSelected() && path.size() > 1 && !path.top().get().isRoot() ) { m_remove = true; return false; // dont traverse into child elements } return true; } void post( const scene::Path& path, scene::Instance& instance ) const { if ( m_removedChild ) { m_removedChild = false; // delete empty entities Entity* entity = Node_getEntity( path.top() ); if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map ) && Node_getTraversable( path.top() )->empty() ) { Path_deleteTop( path ); } } // node should be removed if ( m_remove ) { if ( Node_isEntity( path.parent() ) != 0 ) { m_removedChild = true; } m_remove = false; Path_deleteTop( path ); } } }; void Scene_DeleteSelected( scene::Graph& graph ){ graph.traverse( DeleteSelected() ); SceneChangeNotify(); } void Select_Delete( void ){ Scene_DeleteSelected( GlobalSceneGraph() ); } class InvertSelectionWalker : public scene::Graph::Walker { SelectionSystem::EMode m_mode; mutable Selectable* m_selectable; public: InvertSelectionWalker( SelectionSystem::EMode mode ) : m_mode( mode ), m_selectable( 0 ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { Selectable* selectable = Instance_getSelectable( instance ); if ( selectable ) { switch ( m_mode ) { case SelectionSystem::eEntity: if ( Node_isEntity( path.top() ) != 0 ) { m_selectable = path.top().get().visible() ? selectable : 0; } break; case SelectionSystem::ePrimitive: m_selectable = path.top().get().visible() ? selectable : 0; break; case SelectionSystem::eComponent: break; } } return true; } void post( const scene::Path& path, scene::Instance& instance ) const { if ( m_selectable != 0 ) { m_selectable->setSelected( !m_selectable->isSelected() ); m_selectable = 0; } } }; void Scene_Invert_Selection( scene::Graph& graph ){ graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) ); } void Select_Invert(){ Scene_Invert_Selection( GlobalSceneGraph() ); } class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker { mutable std::size_t m_depth; NodeSmartReference worldspawn; public: ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { ++m_depth; // ignore worldspawn NodeSmartReference me( path.top().get() ); if ( me == worldspawn ) { return false; } if ( m_depth == 2 ) { // entity depth // traverse and select children if any one is selected if ( instance.childSelected() ) { Instance_setSelected( instance, true ); } return Node_getEntity( path.top() )->isContainer() && instance.isSelected(); } else if ( m_depth == 3 ) { // primitive depth Instance_setSelected( instance, true ); return false; } return true; } void post( const scene::Path& path, scene::Instance& instance ) const { --m_depth; } }; void Scene_ExpandSelectionToEntities(){ GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() ); } namespace { void Selection_UpdateWorkzone(){ if ( GlobalSelectionSystem().countSelected() != 0 ) { Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max ); } } typedef FreeCaller SelectionUpdateWorkzoneCaller; IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() ); } const select_workzone_t& Select_getWorkZone(){ g_idleWorkzone.flush(); return g_select_workzone; } void UpdateWorkzone_ForSelection(){ g_idleWorkzone.queueDraw(); } // update the workzone to the current selection void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){ if ( selectable.isSelected() ) { UpdateWorkzone_ForSelection(); } } void Select_SetShader( const char* shader ){ if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader ); Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader ); } Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader ); } void Select_SetTexdef( const TextureProjection& projection ){ if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection ); } Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection ); } void Select_SetFlags( const ContentsFlagsValue& flags ){ if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags ); } Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags ); } void Select_GetBounds( Vector3& mins, Vector3& maxs ){ AABB bounds; Scene_BoundsSelected( GlobalSceneGraph(), bounds ); maxs = vector3_added( bounds.origin, bounds.extents ); mins = vector3_subtracted( bounds.origin, bounds.extents ); } void Select_GetMid( Vector3& mid ){ AABB bounds; Scene_BoundsSelected( GlobalSceneGraph(), bounds ); mid = vector3_snapped( bounds.origin ); } void Select_FlipAxis( int axis ){ Vector3 flip( 1, 1, 1 ); flip[axis] = -1; GlobalSelectionSystem().scaleSelected( flip ); } void Select_Scale( float x, float y, float z ){ GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) ); } enum axis_t { eAxisX = 0, eAxisY = 1, eAxisZ = 2, }; enum sign_t { eSignPositive = 1, eSignNegative = -1, }; inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){ switch ( axis ) { case eAxisX: if ( sign == eSignPositive ) { return matrix4_rotation_for_sincos_x( 1, 0 ); } else { return matrix4_rotation_for_sincos_x( -1, 0 ); } case eAxisY: if ( sign == eSignPositive ) { return matrix4_rotation_for_sincos_y( 1, 0 ); } else { return matrix4_rotation_for_sincos_y( -1, 0 ); } default: //case eAxisZ: if ( sign == eSignPositive ) { return matrix4_rotation_for_sincos_z( 1, 0 ); } else { return matrix4_rotation_for_sincos_z( -1, 0 ); } } } inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){ matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) ); } inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){ matrix4_translate_by_vec3( matrix, pivotpoint ); matrix4_rotate_by_axis90( matrix, axis, sign ); matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) ); } inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){ #if 1 switch ( axis ) { case eAxisX: if ( sign == eSignPositive ) { return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f ); } else { return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f ); } case eAxisY: if ( sign == eSignPositive ) { return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f ); } else { return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f ); } default: //case eAxisZ: if ( sign == eSignPositive ) { return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f ); } else { return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f ); } } #else quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) ); #endif } void Select_RotateAxis( int axis, float deg ){ if ( fabs( deg ) == 90.f ) { GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) ); } else { switch ( axis ) { case 0: GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ) ); break; case 1: GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ) ); break; case 2: GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ) ); break; } } } void Select_ShiftTexture( float x, float y ){ if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y ); Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y ); } //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n'; Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y ); } void Select_ScaleTexture( float x, float y ){ if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y ); Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y ); } Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y ); } void Select_RotateTexture( float amt ){ if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt ); Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt ); } Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt ); } // TTimo modified to handle shader architecture: // expects shader names at input, comparison relies on shader names .. texture names no longer relevant void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){ if ( !texdef_name_valid( pFind ) ) { globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n"; return; } if ( !texdef_name_valid( pReplace ) ) { globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n"; return; } StringOutputStream command; command << "textureFindReplace -find " << pFind << " -replace " << pReplace; UndoableCommand undo( command.c_str() ); if ( bSelected ) { if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace ); Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace ); } Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace ); } else { Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace ); Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace ); } } typedef std::vector PropertyValues; bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){ for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i ) { if ( string_equal( str, *i ) ) { return true; } } return false; } class EntityFindByPropertyValueWalker : public scene::Graph::Walker { const PropertyValues& m_propertyvalues; const char *m_prop; public: EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues ) : m_propertyvalues( propertyvalues ), m_prop( prop ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { Entity* entity = Node_getEntity( path.top() ); if ( entity != 0 && propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) { Instance_getSelectable( instance )->setSelected( true ); } return true; } }; void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){ graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) ); } class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker { PropertyValues& m_propertyvalues; const char *m_prop; public: EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues ) : m_propertyvalues( propertyvalues ), m_prop( prop ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { Selectable* selectable = Instance_getSelectable( instance ); if ( selectable != 0 && selectable->isSelected() ) { Entity* entity = Node_getEntity( path.top() ); if ( entity != 0 ) { if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) { m_propertyvalues.push_back( entity->getKeyValue( m_prop ) ); } } } return true; } }; void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){ graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) ); } void Select_AllOfType(){ if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) { if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) { GlobalSelectionSystem().setSelectedAllComponents( false ); Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) ); } } else { PropertyValues propertyvalues; const char *prop = EntityInspector_getCurrentKey(); if ( !prop || !*prop ) { prop = "classname"; } Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues ); GlobalSelectionSystem().setSelectedAll( false ); if ( !propertyvalues.empty() ) { Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues ); } else { Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) ); Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) ); } } } void Select_Inside( void ){ SelectByBounds::DoSelection(); } void Select_Touching( void ){ SelectByBounds::DoSelection( false ); } void Select_FitTexture( float horizontal, float vertical ){ if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) { Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical ); } Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical ); SceneChangeNotify(); } inline void hide_node( scene::Node& node, bool hide ){ hide ? node.enable( scene::Node::eHidden ) : node.disable( scene::Node::eHidden ); } class HideSelectedWalker : public scene::Graph::Walker { bool m_hide; public: HideSelectedWalker( bool hide ) : m_hide( hide ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { Selectable* selectable = Instance_getSelectable( instance ); if ( selectable != 0 && selectable->isSelected() ) { hide_node( path.top(), m_hide ); } return true; } }; void Scene_Hide_Selected( bool hide ){ GlobalSceneGraph().traverse( HideSelectedWalker( hide ) ); } void Select_Hide(){ Scene_Hide_Selected( true ); SceneChangeNotify(); } void HideSelected(){ Select_Hide(); GlobalSelectionSystem().setSelectedAll( false ); } class HideAllWalker : public scene::Graph::Walker { bool m_hide; public: HideAllWalker( bool hide ) : m_hide( hide ){ } bool pre( const scene::Path& path, scene::Instance& instance ) const { hide_node( path.top(), m_hide ); return true; } }; void Scene_Hide_All( bool hide ){ GlobalSceneGraph().traverse( HideAllWalker( hide ) ); } void Select_ShowAllHidden(){ Scene_Hide_All( false ); SceneChangeNotify(); } void Selection_Flipx(){ UndoableCommand undo( "mirrorSelected -axis x" ); Select_FlipAxis( 0 ); } void Selection_Flipy(){ UndoableCommand undo( "mirrorSelected -axis y" ); Select_FlipAxis( 1 ); } void Selection_Flipz(){ UndoableCommand undo( "mirrorSelected -axis z" ); Select_FlipAxis( 2 ); } void Selection_Rotatex(){ UndoableCommand undo( "rotateSelected -axis x -angle -90" ); Select_RotateAxis( 0,-90 ); } void Selection_Rotatey(){ UndoableCommand undo( "rotateSelected -axis y -angle 90" ); Select_RotateAxis( 1, 90 ); } void Selection_Rotatez(){ UndoableCommand undo( "rotateSelected -axis z -angle -90" ); Select_RotateAxis( 2,-90 ); } void Nudge( int nDim, float fNudge ){ Vector3 translate( 0, 0, 0 ); translate[nDim] = fNudge; GlobalSelectionSystem().translateSelected( translate ); } void Selection_NudgeZ( float amount ){ StringOutputStream command; command << "nudgeSelected -axis z -amount " << amount; UndoableCommand undo( command.c_str() ); Nudge( 2, amount ); } void Selection_MoveDown(){ Selection_NudgeZ( -GetGridSize() ); } void Selection_MoveUp(){ Selection_NudgeZ( GetGridSize() ); } void SceneSelectionChange( const Selectable& selectable ){ SceneChangeNotify(); } SignalHandlerId Selection_boundsChanged; void Selection_construct(){ typedef FreeCaller1 SceneSelectionChangeCaller; GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() ); typedef FreeCaller1 UpdateWorkzoneForSelectionChangedCaller; GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() ); typedef FreeCaller UpdateWorkzoneForSelectionCaller; Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() ); } void Selection_destroy(){ GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged ); } #include "gtkdlgs.h" #include #include #include #include #include inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){ #if 0 return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) ); #elif 0 return quaternion_multiplied_by_quaternion( quaternion_multiplied_by_quaternion( quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ), quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) ) ), quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) ) ); #elif 1 double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) ); double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) ); double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) ); double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) ); double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) ); double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) ); return Quaternion( cz * cy * sx - sz * sy * cx, cz * sy * cx + sz * cy * sx, sz * cy * cx - cz * sy * sx, cz * cy * cx + sz * sy * sx ); #endif } struct RotateDialog { GtkSpinButton* x; GtkSpinButton* y; GtkSpinButton* z; GtkWindow *window; }; static gboolean rotatedlg_apply( GtkWidget *widget, RotateDialog* rotateDialog ){ Vector3 eulerXYZ; eulerXYZ[0] = static_cast( gtk_spin_button_get_value( rotateDialog->x ) ); eulerXYZ[1] = static_cast( gtk_spin_button_get_value( rotateDialog->y ) ); eulerXYZ[2] = static_cast( gtk_spin_button_get_value( rotateDialog->z ) ); StringOutputStream command; command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2]; UndoableCommand undo( command.c_str() ); GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) ); return TRUE; } static gboolean rotatedlg_cancel( GtkWidget *widget, RotateDialog* rotateDialog ){ gtk_widget_hide( GTK_WIDGET( rotateDialog->window ) ); gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close gtk_spin_button_set_value( rotateDialog->y, 0.0f ); gtk_spin_button_set_value( rotateDialog->z, 0.0f ); return TRUE; } static gboolean rotatedlg_ok( GtkWidget *widget, RotateDialog* rotateDialog ){ rotatedlg_apply( widget, rotateDialog ); rotatedlg_cancel( widget, rotateDialog ); return TRUE; } static gboolean rotatedlg_delete( GtkWidget *widget, GdkEventAny *event, RotateDialog* rotateDialog ){ rotatedlg_cancel( widget, rotateDialog ); return TRUE; } RotateDialog g_rotate_dialog; void DoRotateDlg(){ if ( g_rotate_dialog.window == NULL ) { g_rotate_dialog.window = create_dialog_window( MainFrame_getWindow(), "Arbitrary rotation", G_CALLBACK( rotatedlg_delete ), &g_rotate_dialog ); GtkAccelGroup* accel = gtk_accel_group_new(); gtk_window_add_accel_group( g_rotate_dialog.window, accel ); { GtkHBox* hbox = create_dialog_hbox( 4, 4 ); gtk_container_add( GTK_CONTAINER( g_rotate_dialog.window ), GTK_WIDGET( hbox ) ); { GtkTable* table = create_dialog_table( 3, 2, 4, 4 ); gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 ); { GtkWidget* label = gtk_label_new( " X " ); gtk_widget_show( label ); gtk_table_attach( table, label, 0, 1, 0, 1, (GtkAttachOptions) ( 0 ), (GtkAttachOptions) ( 0 ), 0, 0 ); } { GtkWidget* label = gtk_label_new( " Y " ); gtk_widget_show( label ); gtk_table_attach( table, label, 0, 1, 1, 2, (GtkAttachOptions) ( 0 ), (GtkAttachOptions) ( 0 ), 0, 0 ); } { GtkWidget* label = gtk_label_new( " Z " ); gtk_widget_show( label ); gtk_table_attach( table, label, 0, 1, 2, 3, (GtkAttachOptions) ( 0 ), (GtkAttachOptions) ( 0 ), 0, 0 ); } { GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) ); GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) ); gtk_widget_show( GTK_WIDGET( spin ) ); gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1, (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions) ( 0 ), 0, 0 ); gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 ); gtk_spin_button_set_wrap( spin, TRUE ); gtk_widget_grab_focus( GTK_WIDGET( spin ) ); g_rotate_dialog.x = spin; } { GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) ); GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) ); gtk_widget_show( GTK_WIDGET( spin ) ); gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 1, 2, (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions) ( 0 ), 0, 0 ); gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 ); gtk_spin_button_set_wrap( spin, TRUE ); g_rotate_dialog.y = spin; } { GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 0, -359, 359, 1, 10, 0 ) ); GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) ); gtk_widget_show( GTK_WIDGET( spin ) ); gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 2, 3, (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions) ( 0 ), 0, 0 ); gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 ); gtk_spin_button_set_wrap( spin, TRUE ); g_rotate_dialog.z = spin; } } { GtkVBox* vbox = create_dialog_vbox( 4 ); gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 ); { GtkButton* button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog ); gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); widget_make_default( GTK_WIDGET( button ) ); gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 ); } { GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog ); gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 ); } { GtkButton* button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog ); gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); } } } } gtk_widget_show( GTK_WIDGET( g_rotate_dialog.window ) ); } struct ScaleDialog { GtkWidget* x; GtkWidget* y; GtkWidget* z; GtkWindow *window; }; static gboolean scaledlg_apply( GtkWidget *widget, ScaleDialog* scaleDialog ){ float sx, sy, sz; sx = static_cast( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) ); sy = static_cast( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) ); sz = static_cast( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) ); StringOutputStream command; command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz; UndoableCommand undo( command.c_str() ); Select_Scale( sx, sy, sz ); return TRUE; } static gboolean scaledlg_cancel( GtkWidget *widget, ScaleDialog* scaleDialog ){ gtk_widget_hide( GTK_WIDGET( scaleDialog->window ) ); gtk_entry_set_text( GTK_ENTRY( scaleDialog->x ), "1.0" ); gtk_entry_set_text( GTK_ENTRY( scaleDialog->y ), "1.0" ); gtk_entry_set_text( GTK_ENTRY( scaleDialog->z ), "1.0" ); return TRUE; } static gboolean scaledlg_ok( GtkWidget *widget, ScaleDialog* scaleDialog ){ scaledlg_apply( widget, scaleDialog ); scaledlg_cancel( widget, scaleDialog ); return TRUE; } static gboolean scaledlg_delete( GtkWidget *widget, GdkEventAny *event, ScaleDialog* scaleDialog ){ scaledlg_cancel( widget, scaleDialog ); return TRUE; } ScaleDialog g_scale_dialog; void DoScaleDlg(){ if ( g_scale_dialog.window == NULL ) { g_scale_dialog.window = create_dialog_window( MainFrame_getWindow(), "Arbitrary scale", G_CALLBACK( scaledlg_delete ), &g_scale_dialog ); GtkAccelGroup* accel = gtk_accel_group_new(); gtk_window_add_accel_group( g_scale_dialog.window, accel ); { GtkHBox* hbox = create_dialog_hbox( 4, 4 ); gtk_container_add( GTK_CONTAINER( g_scale_dialog.window ), GTK_WIDGET( hbox ) ); { GtkTable* table = create_dialog_table( 3, 2, 4, 4 ); gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 ); { GtkWidget* label = gtk_label_new( " X " ); gtk_widget_show( label ); gtk_table_attach( table, label, 0, 1, 0, 1, (GtkAttachOptions) ( 0 ), (GtkAttachOptions) ( 0 ), 0, 0 ); } { GtkWidget* label = gtk_label_new( " Y " ); gtk_widget_show( label ); gtk_table_attach( table, label, 0, 1, 1, 2, (GtkAttachOptions) ( 0 ), (GtkAttachOptions) ( 0 ), 0, 0 ); } { GtkWidget* label = gtk_label_new( " Z " ); gtk_widget_show( label ); gtk_table_attach( table, label, 0, 1, 2, 3, (GtkAttachOptions) ( 0 ), (GtkAttachOptions) ( 0 ), 0, 0 ); } { GtkWidget* entry = gtk_entry_new(); gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" ); gtk_widget_show( entry ); gtk_table_attach( table, entry, 1, 2, 0, 1, (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions) ( 0 ), 0, 0 ); g_scale_dialog.x = entry; } { GtkWidget* entry = gtk_entry_new(); gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" ); gtk_widget_show( entry ); gtk_table_attach( table, entry, 1, 2, 1, 2, (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions) ( 0 ), 0, 0 ); g_scale_dialog.y = entry; } { GtkWidget* entry = gtk_entry_new(); gtk_entry_set_text( GTK_ENTRY( entry ), "1.0" ); gtk_widget_show( entry ); gtk_table_attach( table, entry, 1, 2, 2, 3, (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions) ( 0 ), 0, 0 ); g_scale_dialog.z = entry; } } { GtkVBox* vbox = create_dialog_vbox( 4 ); gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 ); { GtkButton* button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog ); gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); widget_make_default( GTK_WIDGET( button ) ); gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 ); } { GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog ); gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 ); } { GtkButton* button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog ); gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); } } } } gtk_widget_show( GTK_WIDGET( g_scale_dialog.window ) ); }