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 if( path.top().get().visible() ){
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() ) &&
128 ( selectable != 0 ) &&
129 ( !node_is_group( path.top() ) )
131 for ( Unsigned i = 0; i < m_count; ++i )
133 if ( policy.Evaluate( m_aabbs[i], instance ) ) {
134 selectable->setSelected( true );
147 Performs selection operation on the global scenegraph.
148 If delete_bounds_src is true, then the objects which were
149 used as source for the selection aabbs will be deleted.
151 static void DoSelection( bool delete_bounds_src = true ){
152 if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
153 // we may not need all AABBs since not all selected objects have to be brushes
154 const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
155 AABB* aabbs = new AABB[max];
158 CollectSelectedBrushesBounds collector( aabbs, max, count );
159 GlobalSelectionSystem().foreachSelected( collector );
161 // nothing usable in selection
167 // delete selected objects
168 if ( delete_bounds_src ) { // see deleteSelection
169 UndoableCommand undo( "deleteSelected" );
173 // select objects with bounds
174 GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
183 SelectionPolicy for SelectByBounds
184 Returns true if box and the AABB of instance intersect
186 class SelectionPolicy_Touching
189 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
190 const AABB& other( instance.worldAABB() );
191 for ( Unsigned i = 0; i < 3; ++i )
193 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
202 SelectionPolicy for SelectByBounds
203 Returns true if the AABB of instance is inside box
205 class SelectionPolicy_Inside
208 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
209 const AABB& other( instance.worldAABB() );
210 for ( Unsigned i = 0; i < 3; ++i )
212 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
220 class DeleteSelected : public scene::Graph::Walker
222 mutable bool m_remove;
223 mutable bool m_removedChild;
226 : m_remove( false ), m_removedChild( false ){
228 bool pre( const scene::Path& path, scene::Instance& instance ) const {
229 m_removedChild = false;
231 Selectable* selectable = Instance_getSelectable( instance );
233 && selectable->isSelected()
235 && !path.top().get().isRoot() ) {
238 return false; // dont traverse into child elements
242 void post( const scene::Path& path, scene::Instance& instance ) const {
244 if ( m_removedChild ) {
245 m_removedChild = false;
247 // delete empty entities
248 Entity* entity = Node_getEntity( path.top() );
250 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
251 && Node_getTraversable( path.top() )->empty() ) {
252 Path_deleteTop( path );
256 // node should be removed
258 if ( Node_isEntity( path.parent() ) != 0 ) {
259 m_removedChild = true;
263 Path_deleteTop( path );
268 void Scene_DeleteSelected( scene::Graph& graph ){
269 graph.traverse( DeleteSelected() );
273 void Select_Delete( void ){
274 Scene_DeleteSelected( GlobalSceneGraph() );
277 class InvertSelectionWalker : public scene::Graph::Walker
279 SelectionSystem::EMode m_mode;
280 mutable Selectable* m_selectable;
282 InvertSelectionWalker( SelectionSystem::EMode mode )
283 : m_mode( mode ), m_selectable( 0 ){
285 bool pre( const scene::Path& path, scene::Instance& instance ) const {
286 if( !path.top().get().visible() ){
290 Selectable* selectable = Instance_getSelectable( instance );
294 case SelectionSystem::eEntity:
295 if ( Node_isEntity( path.top() ) != 0 ) {
296 m_selectable = path.top().get().visible() ? selectable : 0;
299 case SelectionSystem::ePrimitive:
300 m_selectable = path.top().get().visible() ? selectable : 0;
302 case SelectionSystem::eComponent:
308 void post( const scene::Path& path, scene::Instance& instance ) const {
309 if ( m_selectable != 0 ) {
310 m_selectable->setSelected( !m_selectable->isSelected() );
316 void Scene_Invert_Selection( scene::Graph& graph ){
317 graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) );
320 void Select_Invert(){
321 Scene_Invert_Selection( GlobalSceneGraph() );
324 //interesting printings
325 class ExpandSelectionToEntitiesWalker_dbg : public scene::Graph::Walker
327 mutable std::size_t m_depth;
328 NodeSmartReference worldspawn;
330 ExpandSelectionToEntitiesWalker_dbg() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
332 bool pre( const scene::Path& path, scene::Instance& instance ) const {
334 globalOutputStream() << "pre depth_" << m_depth;
335 globalOutputStream() << " path.size()_" << path.size();
336 if ( path.top().get() == worldspawn )
337 globalOutputStream() << " worldspawn";
338 if( path.top().get().isRoot() )
339 globalOutputStream() << " path.top().get().isRoot()";
340 Entity* entity = Node_getEntity( path.top() );
342 globalOutputStream() << " entity!=0";
343 if( entity->isContainer() ){
344 globalOutputStream() << " entity->isContainer()";
346 globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
348 globalOutputStream() << "\n";
349 // globalOutputStream() << "" << ;
350 // globalOutputStream() << "" << ;
351 // globalOutputStream() << "" << ;
352 // globalOutputStream() << "" << ;
355 void post( const scene::Path& path, scene::Instance& instance ) const {
356 globalOutputStream() << "post depth_" << m_depth;
357 globalOutputStream() << " path.size()_" << path.size();
358 if ( path.top().get() == worldspawn )
359 globalOutputStream() << " worldspawn";
360 if( path.top().get().isRoot() )
361 globalOutputStream() << " path.top().get().isRoot()";
362 Entity* entity = Node_getEntity( path.top() );
364 globalOutputStream() << " entity!=0";
365 if( entity->isContainer() ){
366 globalOutputStream() << " entity->isContainer()";
368 globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
370 globalOutputStream() << "\n";
375 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
377 mutable std::size_t m_depth;
378 NodeSmartReference worldspawn;
380 ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
382 bool pre( const scene::Path& path, scene::Instance& instance ) const {
386 // NodeSmartReference me( path.top().get() );
387 // if ( me == worldspawn ) {
391 if ( m_depth == 2 ) { // entity depth
392 // traverse and select children if any one is selected
393 bool beselected = false;
394 if ( instance.childSelected() ) {
396 if( path.top().get() != worldspawn ){
397 Instance_setSelected( instance, true );
400 return Node_getEntity( path.top() )->isContainer() && beselected;
402 else if ( m_depth == 3 ) { // primitive depth
403 Instance_setSelected( instance, true );
408 void post( const scene::Path& path, scene::Instance& instance ) const {
413 void Scene_ExpandSelectionToEntities(){
414 GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
420 void Selection_UpdateWorkzone(){
421 if ( GlobalSelectionSystem().countSelected() != 0 ) {
422 Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
425 typedef FreeCaller<void(), Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
427 IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
430 const select_workzone_t& Select_getWorkZone(){
431 g_idleWorkzone.flush();
432 return g_select_workzone;
435 void UpdateWorkzone_ForSelection(){
436 g_idleWorkzone.queueDraw();
439 // update the workzone to the current selection
440 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
441 if ( selectable.isSelected() ) {
442 UpdateWorkzone_ForSelection();
446 void Select_SetShader( const char* shader ){
447 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
448 Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
449 Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
451 Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
454 void Select_SetTexdef( const TextureProjection& projection ){
455 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
456 Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
458 Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
461 void Select_SetFlags( const ContentsFlagsValue& flags ){
462 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
463 Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
465 Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
468 void Select_GetBounds( Vector3& mins, Vector3& maxs ){
470 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
471 maxs = vector3_added( bounds.origin, bounds.extents );
472 mins = vector3_subtracted( bounds.origin, bounds.extents );
475 void Select_GetMid( Vector3& mid ){
477 Scene_BoundsSelected( GlobalSceneGraph(), bounds );
478 mid = vector3_snapped( bounds.origin );
482 void Select_FlipAxis( int axis ){
483 Vector3 flip( 1, 1, 1 );
485 GlobalSelectionSystem().scaleSelected( flip );
489 void Select_Scale( float x, float y, float z ){
490 GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
506 inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
510 if ( sign == eSignPositive ) {
511 return matrix4_rotation_for_sincos_x( 1, 0 );
515 return matrix4_rotation_for_sincos_x( -1, 0 );
518 if ( sign == eSignPositive ) {
519 return matrix4_rotation_for_sincos_y( 1, 0 );
523 return matrix4_rotation_for_sincos_y( -1, 0 );
525 default: //case eAxisZ:
526 if ( sign == eSignPositive ) {
527 return matrix4_rotation_for_sincos_z( 1, 0 );
531 return matrix4_rotation_for_sincos_z( -1, 0 );
536 inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
537 matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
540 inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
541 matrix4_translate_by_vec3( matrix, pivotpoint );
542 matrix4_rotate_by_axis90( matrix, axis, sign );
543 matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
546 inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
551 if ( sign == eSignPositive ) {
552 return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
556 return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
559 if ( sign == eSignPositive ) {
560 return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
564 return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
566 default: //case eAxisZ:
567 if ( sign == eSignPositive ) {
568 return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
572 return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
576 quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
580 void Select_RotateAxis( int axis, float deg ){
581 if ( fabs( deg ) == 90.f ) {
582 GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ), true );
589 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ), false );
592 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ), false );
595 GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ), false );
602 void Select_ShiftTexture( float x, float y ){
603 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
604 Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
605 Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
607 //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
608 Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
611 void Select_ScaleTexture( float x, float y ){
612 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
613 Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
614 Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
616 Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
619 void Select_RotateTexture( float amt ){
620 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
621 Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
622 Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
624 Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
627 // TTimo modified to handle shader architecture:
628 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
629 void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
630 if ( !texdef_name_valid( pFind ) ) {
631 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
634 if ( !texdef_name_valid( pReplace ) ) {
635 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
639 StringOutputStream command;
640 command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
641 UndoableCommand undo( command.c_str() );
644 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
645 Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
646 Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
648 Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
652 Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
653 Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
657 typedef std::vector<const char*> PropertyValues;
659 bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
660 for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
662 if ( string_equal( str, *i ) ) {
669 class EntityFindByPropertyValueWalker : public scene::Graph::Walker
671 const PropertyValues& m_propertyvalues;
673 const NodeSmartReference worldspawn;
675 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
676 : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
678 bool pre( const scene::Path& path, scene::Instance& instance ) const {
679 if( !path.top().get().visible() ){
683 if ( path.top().get() == worldspawn ) {
687 Entity* entity = Node_getEntity( path.top() );
689 if( propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
690 Instance_getSelectable( instance )->setSelected( true );
695 else if( path.size() > 2 && !path.top().get().isRoot() ){
696 Selectable* selectable = Instance_getSelectable( instance );
697 if( selectable != 0 )
698 selectable->setSelected( true );
704 void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
705 graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
708 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
710 PropertyValues& m_propertyvalues;
712 const NodeSmartReference worldspawn;
714 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
715 : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
717 bool pre( const scene::Path& path, scene::Instance& instance ) const {
718 Entity* entity = Node_getEntity( path.top() );
720 if( path.top().get() != worldspawn ){
721 Selectable* selectable = Instance_getSelectable( instance );
722 if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
723 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
724 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
734 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
736 PropertyValues& m_propertyvalues;
738 mutable bool m_selected_children;
739 const NodeSmartReference worldspawn;
741 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
742 : m_propertyvalues( propertyvalues ), m_prop( prop ), m_selected_children( false ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
744 bool pre( const scene::Path& path, scene::Instance& instance ) const {
745 Selectable* selectable = Instance_getSelectable( instance );
747 && selectable->isSelected() ) {
748 Entity* entity = Node_getEntity( path.top() );
750 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
751 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
756 m_selected_children = true;
761 void post( const scene::Path& path, scene::Instance& instance ) const {
762 Entity* entity = Node_getEntity( path.top() );
763 if( entity != 0 && m_selected_children ){
764 m_selected_children = false;
765 if( path.top().get() == worldspawn )
767 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
768 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
774 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
775 graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
778 void Select_AllOfType(){
779 if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
780 if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
781 GlobalSelectionSystem().setSelectedAllComponents( false );
782 Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
787 PropertyValues propertyvalues;
788 const char *prop = EntityInspector_getCurrentKey();
789 if ( !prop || !*prop ) {
792 Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
793 GlobalSelectionSystem().setSelectedAll( false );
794 if ( !propertyvalues.empty() ) {
795 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
799 Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
800 Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
805 void Select_Inside( void ){
806 SelectByBounds<SelectionPolicy_Inside>::DoSelection();
809 void Select_Touching( void ){
810 SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
813 void Select_FitTexture( float horizontal, float vertical ){
814 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
815 Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
817 Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
823 #include "commands.h"
826 inline void hide_node( scene::Node& node, bool hide ){
828 ? node.enable( scene::Node::eHidden )
829 : node.disable( scene::Node::eHidden );
832 bool g_nodes_be_hidden = false;
834 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_hidden_caller( g_nodes_be_hidden );
836 ToggleItem g_hidden_item( g_hidden_caller );
838 class HideSelectedWalker : public scene::Graph::Walker
842 HideSelectedWalker( bool hide )
845 bool pre( const scene::Path& path, scene::Instance& instance ) const {
846 Selectable* selectable = Instance_getSelectable( instance );
848 && selectable->isSelected() ) {
849 g_nodes_be_hidden = m_hide;
850 hide_node( path.top(), m_hide );
856 void Scene_Hide_Selected( bool hide ){
857 GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
861 Scene_Hide_Selected( true );
867 GlobalSelectionSystem().setSelectedAll( false );
868 g_hidden_item.update();
872 class HideAllWalker : public scene::Graph::Walker
876 HideAllWalker( bool hide )
879 bool pre( const scene::Path& path, scene::Instance& instance ) const {
880 hide_node( path.top(), m_hide );
885 void Scene_Hide_All( bool hide ){
886 GlobalSceneGraph().traverse( HideAllWalker( hide ) );
889 void Select_ShowAllHidden(){
890 Scene_Hide_All( false );
892 g_nodes_be_hidden = false;
893 g_hidden_item.update();
897 void Selection_Flipx(){
898 UndoableCommand undo( "mirrorSelected -axis x" );
899 Select_FlipAxis( 0 );
902 void Selection_Flipy(){
903 UndoableCommand undo( "mirrorSelected -axis y" );
904 Select_FlipAxis( 1 );
907 void Selection_Flipz(){
908 UndoableCommand undo( "mirrorSelected -axis z" );
909 Select_FlipAxis( 2 );
912 void Selection_Rotatex(){
913 UndoableCommand undo( "rotateSelected -axis x -angle -90" );
914 Select_RotateAxis( 0,-90 );
917 void Selection_Rotatey(){
918 UndoableCommand undo( "rotateSelected -axis y -angle 90" );
919 Select_RotateAxis( 1, 90 );
922 void Selection_Rotatez(){
923 UndoableCommand undo( "rotateSelected -axis z -angle -90" );
924 Select_RotateAxis( 2,-90 );
926 #include "xywindow.h"
927 void Selection_FlipHorizontally(){
928 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
941 void Selection_FlipVertically(){
942 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
955 void Selection_RotateClockwise(){
956 UndoableCommand undo( "rotateSelected Clockwise 90" );
957 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
961 Select_RotateAxis( 2, -90 );
964 Select_RotateAxis( 1, 90 );
967 Select_RotateAxis( 0, -90 );
972 void Selection_RotateAnticlockwise(){
973 UndoableCommand undo( "rotateSelected Anticlockwise 90" );
974 VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
978 Select_RotateAxis( 2, 90 );
981 Select_RotateAxis( 1, -90 );
984 Select_RotateAxis( 0, 90 );
992 void Select_registerCommands(){
993 GlobalCommands_insert( "ShowHidden", makeCallbackF( Select_ShowAllHidden ), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
994 GlobalToggles_insert( "HideSelected", makeCallbackF( HideSelected ), ToggleItem::AddCallbackCaller( g_hidden_item ), Accelerator( 'H' ) );
996 GlobalCommands_insert( "MirrorSelectionX", makeCallbackF( Selection_Flipx ) );
997 GlobalCommands_insert( "RotateSelectionX", makeCallbackF( Selection_Rotatex ) );
998 GlobalCommands_insert( "MirrorSelectionY", makeCallbackF( Selection_Flipy ) );
999 GlobalCommands_insert( "RotateSelectionY", makeCallbackF( Selection_Rotatey ) );
1000 GlobalCommands_insert( "MirrorSelectionZ", makeCallbackF( Selection_Flipz ) );
1001 GlobalCommands_insert( "RotateSelectionZ", makeCallbackF( Selection_Rotatez ) );
1003 GlobalCommands_insert( "MirrorSelectionHorizontally", makeCallbackF( Selection_FlipHorizontally ) );
1004 GlobalCommands_insert( "MirrorSelectionVertically", makeCallbackF( Selection_FlipVertically ) );
1006 GlobalCommands_insert( "RotateSelectionClockwise", makeCallbackF( Selection_RotateClockwise ) );
1007 GlobalCommands_insert( "RotateSelectionAnticlockwise", makeCallbackF( Selection_RotateAnticlockwise ) );
1011 void Nudge( int nDim, float fNudge ){
1012 Vector3 translate( 0, 0, 0 );
1013 translate[nDim] = fNudge;
1015 GlobalSelectionSystem().translateSelected( translate );
1018 void Selection_NudgeZ( float amount ){
1019 StringOutputStream command;
1020 command << "nudgeSelected -axis z -amount " << amount;
1021 UndoableCommand undo( command.c_str() );
1026 void Selection_MoveDown(){
1027 Selection_NudgeZ( -GetGridSize() );
1030 void Selection_MoveUp(){
1031 Selection_NudgeZ( GetGridSize() );
1034 void SceneSelectionChange( const Selectable& selectable ){
1035 SceneChangeNotify();
1038 SignalHandlerId Selection_boundsChanged;
1040 void Selection_construct(){
1041 typedef FreeCaller<void(const Selectable&), SceneSelectionChange> SceneSelectionChangeCaller;
1042 GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
1043 typedef FreeCaller<void(const Selectable&), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
1044 GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
1045 typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
1046 Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
1049 void Selection_destroy(){
1050 GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
1054 #include "gtkdlgs.h"
1055 #include <gdk/gdkkeysyms.h>
1058 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
1060 return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
1062 return quaternion_multiplied_by_quaternion(
1063 quaternion_multiplied_by_quaternion(
1064 quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
1065 quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
1067 quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
1070 double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
1071 double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
1072 double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
1073 double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
1074 double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
1075 double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
1078 cz * cy * sx - sz * sy * cx,
1079 cz * sy * cx + sz * cy * sx,
1080 sz * cy * cx - cz * sy * sx,
1081 cz * cy * cx + sz * sy * sx
1088 ui::SpinButton x{ui::null};
1089 ui::SpinButton y{ui::null};
1090 ui::SpinButton z{ui::null};
1091 ui::Window window{ui::null};
1094 static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
1097 gtk_spin_button_update ( rotateDialog->x );
1098 gtk_spin_button_update ( rotateDialog->y );
1099 gtk_spin_button_update ( rotateDialog->z );
1100 eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
1101 eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
1102 eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
1104 StringOutputStream command;
1105 command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
1106 UndoableCommand undo( command.c_str() );
1108 GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ), false );
1112 static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog ){
1113 rotateDialog->window.hide();
1115 gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
1116 gtk_spin_button_set_value( rotateDialog->y, 0.0f );
1117 gtk_spin_button_set_value( rotateDialog->z, 0.0f );
1122 static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
1123 rotatedlg_apply( widget, rotateDialog );
1124 // rotatedlg_cancel( widget, rotateDialog );
1125 rotateDialog->window.hide();
1129 static gboolean rotatedlg_delete( ui::Widget widget, GdkEventAny *event, RotateDialog* rotateDialog ){
1130 rotatedlg_cancel( widget, rotateDialog );
1134 RotateDialog g_rotate_dialog;
1136 if ( !g_rotate_dialog.window ) {
1137 g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
1139 auto accel = ui::AccelGroup(ui::New);
1140 g_rotate_dialog.window.add_accel_group( accel );
1143 auto hbox = create_dialog_hbox( 4, 4 );
1144 g_rotate_dialog.window.add(hbox);
1146 auto table = create_dialog_table( 3, 2, 4, 4 );
1147 hbox.pack_start( table, TRUE, TRUE, 0 );
1149 ui::Widget label = ui::Label( " X " );
1151 table.attach(label, {0, 1, 0, 1}, {0, 0});
1154 ui::Widget label = ui::Label( " Y " );
1156 table.attach(label, {0, 1, 1, 2}, {0, 0});
1159 ui::Widget label = ui::Label( " Z " );
1161 table.attach(label, {0, 1, 2, 3}, {0, 0});
1164 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1165 auto spin = ui::SpinButton( adj, 1, 0 );
1167 table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1168 spin.dimensions(64, -1);
1169 gtk_spin_button_set_wrap( spin, TRUE );
1171 gtk_widget_grab_focus( spin );
1173 g_rotate_dialog.x = spin;
1176 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1177 auto spin = ui::SpinButton( adj, 1, 0 );
1179 table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1180 spin.dimensions(64, -1);
1181 gtk_spin_button_set_wrap( spin, TRUE );
1183 g_rotate_dialog.y = spin;
1186 auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1187 auto spin = ui::SpinButton( adj, 1, 0 );
1189 table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1190 spin.dimensions(64, -1);
1191 gtk_spin_button_set_wrap( spin, TRUE );
1193 g_rotate_dialog.z = spin;
1197 auto vbox = create_dialog_vbox( 4 );
1198 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1200 auto button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
1201 vbox.pack_start( button, FALSE, FALSE, 0 );
1202 widget_make_default( button );
1203 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1206 auto button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
1207 vbox.pack_start( button, FALSE, FALSE, 0 );
1208 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1211 auto button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
1212 vbox.pack_start( button, FALSE, FALSE, 0 );
1218 g_rotate_dialog.window.show();
1231 ui::Entry x{ui::null};
1232 ui::Entry y{ui::null};
1233 ui::Entry z{ui::null};
1234 ui::Window window{ui::null};
1237 static gboolean scaledlg_apply( ui::Widget widget, ScaleDialog* scaleDialog ){
1240 sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1241 sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1242 sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1244 StringOutputStream command;
1245 command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1246 UndoableCommand undo( command.c_str() );
1248 Select_Scale( sx, sy, sz );
1253 static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
1254 scaleDialog->window.hide();
1256 scaleDialog->x.text("1.0");
1257 scaleDialog->y.text("1.0");
1258 scaleDialog->z.text("1.0");
1263 static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
1264 scaledlg_apply( widget, scaleDialog );
1265 //scaledlg_cancel( widget, scaleDialog );
1266 scaleDialog->window.hide();
1270 static gboolean scaledlg_delete( ui::Widget widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1271 scaledlg_cancel( widget, scaleDialog );
1275 ScaleDialog g_scale_dialog;
1278 if ( !g_scale_dialog.window ) {
1279 g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", G_CALLBACK(scaledlg_delete ), &g_scale_dialog );
1281 auto accel = ui::AccelGroup(ui::New);
1282 g_scale_dialog.window.add_accel_group( accel );
1285 auto hbox = create_dialog_hbox( 4, 4 );
1286 g_scale_dialog.window.add(hbox);
1288 auto table = create_dialog_table( 3, 2, 4, 4 );
1289 hbox.pack_start( table, TRUE, TRUE, 0 );
1291 ui::Widget label = ui::Label( " X " );
1293 table.attach(label, {0, 1, 0, 1}, {0, 0});
1296 ui::Widget label = ui::Label( " Y " );
1298 table.attach(label, {0, 1, 1, 2}, {0, 0});
1301 ui::Widget label = ui::Label( " Z " );
1303 table.attach(label, {0, 1, 2, 3}, {0, 0});
1306 auto entry = ui::Entry(ui::New);
1309 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1311 g_scale_dialog.x = entry;
1314 auto entry = ui::Entry(ui::New);
1317 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1319 g_scale_dialog.y = entry;
1322 auto entry = ui::Entry(ui::New);
1325 table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1327 g_scale_dialog.z = entry;
1331 auto vbox = create_dialog_vbox( 4 );
1332 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1334 auto button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1335 vbox.pack_start( button, FALSE, FALSE, 0 );
1336 widget_make_default( button );
1337 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1340 auto button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1341 vbox.pack_start( button, FALSE, FALSE, 0 );
1342 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1345 auto button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1346 vbox.pack_start( button, FALSE, FALSE, 0 );
1352 g_scale_dialog.window.show();