diff --git a/radiant/brushmanip.cpp b/radiant/brushmanip.cpp index 1e4186e9..b04fa2ca 100644 --- a/radiant/brushmanip.cpp +++ b/radiant/brushmanip.cpp @@ -327,75 +327,94 @@ void Brush_ConstructRock( Brush& brush, const AABB& bounds, std::size_t sides, c } } +#include "quickhull/QuickHull.hpp" namespace icosahedron{ -#define X .525731112119133606 -#define Z .850650808352039932 - -static float vdata[12][3] = { +constexpr double X = .525731112119133606; +constexpr double Z = .850650808352039932; +// 12 vertices +static const DoubleVector3 vdata[12] = { { -X, 0.0, Z}, {X, 0.0, Z}, { -X, 0.0, -Z}, {X, 0.0, -Z}, {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X}, {Z, X, 0.0}, { -Z, X, 0.0}, {Z, -X, 0.0}, { -Z, -X, 0.0} }; -static unsigned int tindices[20][3] = { +// 20 faces +static constexpr unsigned int tindices[20][3] = { {0, 4, 1}, {0, 9, 4}, {9, 5, 4}, {4, 5, 8}, {4, 8, 1}, {8, 10, 1}, {8, 3, 10}, {5, 3, 8}, {5, 2, 3}, {2, 7, 3}, {7, 10, 3}, {7, 6, 10}, {7, 11, 6}, {11, 0, 6}, {0, 1, 6}, {6, 1, 10}, {9, 0, 11}, {9, 11, 2}, {9, 2, 5}, {7, 2, 11} }; -void normalize( float* a ) { - float d = sqrt( a[0] * a[0] + a[1] * a[1] + a[2] * a[2] ); - a[0] /= d; - a[1] /= d; - a[2] /= d; -} - -void drawtri( float* a, float* b, float* c, std::size_t subdivisions, Brush& brush, float radius, const Vector3& mid, const char* shader, const TextureProjection& projection ) { - if( subdivisions <= 0 ) { - brush.addPlane( Vector3( a[0], a[1], a[2] ) * radius + mid, - Vector3( b[0], b[1], b[2] ) * radius + mid, - Vector3( c[0], c[1], c[2] ) * radius + mid, - shader, projection ); +void drawtri( const DoubleVector3& a, const DoubleVector3& b, const DoubleVector3& c, std::size_t subdivisions, bool truncate, std::vector>& pointCloud ) { + if( subdivisions == 0 ) { + const auto push = [&pointCloud]( const DoubleVector3& p ){ + pointCloud.push_back( quickhull::Vector3( p.x(), p.y(), p.z() ) ); + }; + if( truncate ){ // this is not quite correct after subdivision performed :thinking: + push( ( a * ( 2.0 / 3.0 ) ) + ( b * ( 1.0 / 3.0 ) ) ); + push( ( a * ( 1.0 / 3.0 ) ) + ( b * ( 2.0 / 3.0 ) ) ); + push( ( a * ( 2.0 / 3.0 ) ) + ( c * ( 1.0 / 3.0 ) ) ); + push( ( a * ( 1.0 / 3.0 ) ) + ( c * ( 2.0 / 3.0 ) ) ); + push( ( b * ( 2.0 / 3.0 ) ) + ( c * ( 1.0 / 3.0 ) ) ); + push( ( b * ( 1.0 / 3.0 ) ) + ( c * ( 2.0 / 3.0 ) ) ); + } + else{ + push( a ); + push( b ); + push( c ); + } } else{ - float ab[3], ac[3], bc[3]; - for( int i = 0; i < 3; i++ ) { - ab[i] = ( a[i] + b[i] ) / 2; - ac[i] = ( a[i] + c[i] ) / 2; - bc[i] = ( b[i] + c[i] ) / 2; - } - normalize( ab ); - normalize( ac ); - normalize( bc ); - drawtri( a, ab, ac, subdivisions - 1, brush, radius, mid, shader, projection ); - drawtri( b, bc, ab, subdivisions - 1, brush, radius, mid, shader, projection ); - drawtri( c, ac, bc, subdivisions - 1, brush, radius, mid, shader, projection ); - drawtri( ab, bc, ac, subdivisions - 1, brush, radius, mid, shader, projection ); //<--Comment this line and sphere looks really cool! + const DoubleVector3 ab = vector3_normalised( a + b ); + const DoubleVector3 ac = vector3_normalised( a + c ); + const DoubleVector3 bc = vector3_normalised( b + c ); + + drawtri( a, ab, ac, subdivisions - 1, truncate, pointCloud ); + drawtri( b, bc, ab, subdivisions - 1, truncate, pointCloud ); + drawtri( c, ac, bc, subdivisions - 1, truncate, pointCloud ); + drawtri( ab, bc, ac, subdivisions - 1, truncate, pointCloud ); //<--Comment this line and sphere looks really cool! } } -void Brush_ConstructIcosahedron( Brush& brush, const AABB& bounds, std::size_t subdivisions, const char* shader, const TextureProjection& projection ){ - if ( Unsigned( 20 * ( 4 << subdivisions ) ) > c_brush_maxFaces ) { - globalErrorStream() << "brushIcosahedron" << ": subdivisions " << Unsigned( subdivisions ) << ": wrong sides count requested\n"; - return; - } - +void Brush_ConstructIcosahedron( Brush& brush, const AABB& bounds, std::size_t subdivisions, bool truncate, const char* shader, const TextureProjection& projection ){ brush.clear(); - brush.reserve( 20 * ( 4 << subdivisions ) ); - float radius = max_extent( bounds.extents ); + const float radius = max_extent( bounds.extents ); const Vector3& mid = bounds.origin; + std::vector> pointCloud; + for( int i = 0; i < 20; i++ ){ - drawtri( vdata[tindices[i][0]], vdata[tindices[i][1]], vdata[tindices[i][2]], subdivisions, brush, radius, mid, shader, projection ); + drawtri( vdata[tindices[i][0]], vdata[tindices[i][1]], vdata[tindices[i][2]], subdivisions, truncate, pointCloud ); + } + + quickhull::QuickHull quickhull; + auto hull = quickhull.getConvexHull( pointCloud, true, true ); + const auto& indexBuffer = hull.getIndexBuffer(); + const size_t triangleCount = indexBuffer.size() / 3; + std::vector planes; + for( size_t i = 0; i < triangleCount; ++i ) { + DoubleVector3 p[3]; + for( size_t j = 0; j < 3; ++j ){ + p[j] = DoubleVector3( pointCloud[indexBuffer[i * 3 + j]].x, + pointCloud[indexBuffer[i * 3 + j]].y, + pointCloud[indexBuffer[i * 3 + j]].z ); + } + const Plane3 plane = plane3_for_points( p ); + if( plane3_valid( plane ) ){ + if( std::find_if( planes.begin(), planes.end(), [&plane]( const Plane3& pla ){ return plane3_equal( plane, pla ); } ) == planes.end() ){ + planes.push_back( plane ); + brush.addPlane( p[0] * radius + mid, p[1] * radius + mid, p[2] * radius + mid, shader, projection ); + } + } } } } //namespace icosahedron -void Brush_ConstructPrefab( Brush& brush, EBrushPrefab type, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection ){ +void Brush_ConstructPrefab( Brush& brush, EBrushPrefab type, const AABB& bounds, std::size_t sides, bool option, const char* shader, const TextureProjection& projection ){ switch ( type ) { case eBrushCuboid: @@ -448,7 +467,7 @@ void Brush_ConstructPrefab( Brush& brush, EBrushPrefab type, const AABB& bounds, command << "brushIcosahedron" << " -subdivisions " << Unsigned( sides ); UndoableCommand undo( command.c_str() ); - icosahedron::Brush_ConstructIcosahedron( brush, bounds, sides, shader, projection ); + icosahedron::Brush_ConstructIcosahedron( brush, bounds, sides, option, shader, projection ); } break; } @@ -802,14 +821,14 @@ const TextureProjection& TextureTransform_getDefault(){ return g_defaultTextureProjection; } -void Scene_BrushConstructPrefab( scene::Graph& graph, EBrushPrefab type, std::size_t sides, const char* shader ){ +void Scene_BrushConstructPrefab( scene::Graph& graph, EBrushPrefab type, std::size_t sides, bool option, const char* shader ){ if ( GlobalSelectionSystem().countSelected() != 0 ) { const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path(); Brush* brush = Node_getBrush( path.top() ); if ( brush != 0 ) { - AABB bounds = brush->localAABB(); // copy bounds because the brush will be modified - Brush_ConstructPrefab( *brush, type, bounds, sides, shader, TextureTransform_getDefault() ); + const AABB bounds = brush->localAABB(); // copy bounds because the brush will be modified + Brush_ConstructPrefab( *brush, type, bounds, sides, option, shader, TextureTransform_getDefault() ); SceneChangeNotify(); } } @@ -1447,7 +1466,7 @@ BrushMakeSided( std::size_t count ) : m_count( count ){ } void set(){ - Scene_BrushConstructPrefab( GlobalSceneGraph(), eBrushPrism, m_count, TextureBrowser_GetSelectedShader() ); + Scene_BrushConstructPrefab( GlobalSceneGraph(), eBrushPrism, m_count, false, TextureBrowser_GetSelectedShader() ); } typedef MemberCaller SetCaller; }; diff --git a/radiant/brushmanip.h b/radiant/brushmanip.h index 0ac510ec..3b16eadd 100644 --- a/radiant/brushmanip.h +++ b/radiant/brushmanip.h @@ -43,7 +43,7 @@ namespace scene class Graph; class Node; } -void Scene_BrushConstructPrefab( scene::Graph& graph, EBrushPrefab type, std::size_t sides, const char* shader ); +void Scene_BrushConstructPrefab( scene::Graph& graph, EBrushPrefab type, std::size_t sides, bool option, const char* shader ); class AABB; void Scene_BrushResize_Cuboid( scene::Node*& node, const AABB& bounds ); void Brush_ConstructPlacehoderCuboid( scene::Node& node, const AABB& bounds ); diff --git a/radiant/gtkdlgs.cpp b/radiant/gtkdlgs.cpp index 7ea95f83..88759577 100644 --- a/radiant/gtkdlgs.cpp +++ b/radiant/gtkdlgs.cpp @@ -366,8 +366,7 @@ void DoProjectSettings(){ void DoSides( int type, int axis ){ ModalDialog dialog; - //GtkEntry* sides_entry; - GtkWidget* sides_spin; + GtkWidget* spin, *toggle; GtkWindow* window = create_dialog_window( MainFrame_getWindow(), "Arbitrary sides", G_CALLBACK( dialog_delete_callback ), &dialog ); @@ -375,20 +374,21 @@ void DoSides( int type, int axis ){ gtk_window_add_accel_group( window, accel ); { + GtkVBox* vbox = create_dialog_vbox( 0, 0 ); + gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( vbox ) ); GtkHBox* hbox = create_dialog_hbox( 4, 4 ); - gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( hbox ) ); + gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, FALSE, 0 ); + + GtkLabel* label; { - GtkLabel* label = GTK_LABEL( gtk_label_new( "Sides:" ) ); + label = GTK_LABEL( gtk_label_new( "Sides:" ) ); gtk_widget_show( GTK_WIDGET( label ) ); gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 ); } -// { -// GtkEntry* entry = GTK_ENTRY( gtk_entry_new() ); -// gtk_widget_show( GTK_WIDGET( entry ) ); -// gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( entry ), FALSE, FALSE, 0 ); -// sides_entry = entry; -// gtk_widget_grab_focus( GTK_WIDGET( entry ) ); -// } + { + toggle = gtk_check_button_new_with_label( "Truncate" ); + gtk_box_pack_end( GTK_BOX( vbox ), toggle, FALSE, FALSE, 0 ); + } { GtkAdjustment* adj; EBrushPrefab BrushPrefabType = (EBrushPrefab)type; @@ -406,19 +406,19 @@ void DoSides( int type, int axis ){ break; case eBrushIcosahedron : adj = GTK_ADJUSTMENT( gtk_adjustment_new( 1, 0, 2, 1, 10, 0 ) ); //possible with 3, but buggy + gtk_widget_show( toggle ); + gtk_label_set_label( label, "Subdivisions" ); break; default: adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 31, 1, 10, 0 ) ); break; } - GtkWidget* spin = gtk_spin_button_new( adj, 1, 0 ); + spin = gtk_spin_button_new( adj, 1, 0 ); gtk_widget_show( spin ); gtk_box_pack_start( GTK_BOX( hbox ), spin, FALSE, FALSE, 0 ); gtk_widget_set_size_request( spin, 64, -1 ); gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE ); - - sides_spin = spin; } { GtkVBox* vbox = create_dialog_vbox( 4 ); @@ -438,12 +438,10 @@ void DoSides( int type, int axis ){ } if ( modal_dialog_show( window, dialog ) == eIDOK ) { -// const char *str = gtk_entry_get_text( sides_entry ); - -// Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader() ); - gtk_spin_button_update ( GTK_SPIN_BUTTON( sides_spin ) ); - int sides = static_cast( gtk_spin_button_get_value( GTK_SPIN_BUTTON( sides_spin ) ) ); - Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, sides, TextureBrowser_GetSelectedShader() ); + gtk_spin_button_update ( GTK_SPIN_BUTTON( spin ) ); + const int sides = static_cast( gtk_spin_button_get_value( GTK_SPIN_BUTTON( spin ) ) ); + const bool option = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( toggle ) ); + Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, sides, option, TextureBrowser_GetSelectedShader() ); } gtk_widget_destroy( GTK_WIDGET( window ) );