From: Garux Date: Tue, 1 Aug 2017 11:00:12 +0000 (+0300) Subject: Radiant: X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=a59855266c41b68a8bfd063713c941eff7449181;p=xonotic%2Fnetradiant.git Radiant: binds... * douplicated basic shortcuts: arrows - camera freemove, ESC - deSelect, backSpace - delete menus... * modify+: snap to grid (ctrl+g) misc... * fixed warning on import (q3map2_type not found) * patch thicken * removed douplicate accelerators registering * trigger_* entities get textures/common/trigger tex on creation ( unless different is set in .game file via shader_trigger key ) * opening *.map, sent via cmd line (can assign *.map files in system to be opened with radiant) * -aero cmd line key to enable aero transparency * opening maps with bad tex defs (.#QNAN, .#IND, .#INF) (happens sometimes after rotating & often scaling with tex lock (in BP mode)); error->warning --- diff --git a/libs/stringio.h b/libs/stringio.h index ff1a3a67..82d0e258 100644 --- a/libs/stringio.h +++ b/libs/stringio.h @@ -231,6 +231,12 @@ inline bool Tokeniser_getFloat( Tokeniser& tokeniser, float& f ){ if ( token != 0 && string_parse_float( token, f ) ) { return true; } + //fallback for 1.#IND 1.#INF 1.#QNAN cases, happening sometimes after rotating & often scaling with tex lock in BP mode + else if ( token != 0 && strstr( token, ".#" ) ) { + globalErrorStream() << "Warning: " << Unsigned( tokeniser.getLine() ) << ":" << Unsigned( tokeniser.getColumn() ) << ": expected parse problem at '" << token << "': wanted '#number'\nProcessing anyway\n"; + *strstr( token, ".#" ) = '\0'; + return true; + } Tokeniser_unexpectedError( tokeniser, token, "#number" ); return false; } diff --git a/radiant/brushmanip.cpp b/radiant/brushmanip.cpp index acb83822..0502e988 100644 --- a/radiant/brushmanip.cpp +++ b/radiant/brushmanip.cpp @@ -1450,7 +1450,7 @@ void Brush_constructMenu( GtkMenu* menu ){ menu_separator( menu ); create_menu_item_with_mnemonic( menu, "Make detail", "MakeDetail" ); create_menu_item_with_mnemonic( menu, "Make structural", "MakeStructural" ); - create_menu_item_with_mnemonic( menu, "Snap selection to _grid", "SnapToGrid" ); +// create_menu_item_with_mnemonic( menu, "Snap selection to _grid", "SnapToGrid" ); create_check_menu_item_with_mnemonic( menu, "Texture Lock", "TogTexLock" ); menu_separator( menu ); diff --git a/radiant/camwindow.cpp b/radiant/camwindow.cpp index f657551b..e849f4cc 100644 --- a/radiant/camwindow.cpp +++ b/radiant/camwindow.cpp @@ -970,6 +970,24 @@ void CamWnd_registerCommands( CamWnd& camwnd ){ FreeMoveCameraMoveRightKeyDownCaller( camwnd.getCamera() ), FreeMoveCameraMoveRightKeyUpCaller( camwnd.getCamera() ) ); + + GlobalKeyEvents_insert( "CameraFreeMoveForward2", Accelerator( GDK_Up ), + FreeMoveCameraMoveForwardKeyDownCaller( camwnd.getCamera() ), + FreeMoveCameraMoveForwardKeyUpCaller( camwnd.getCamera() ) + ); + GlobalKeyEvents_insert( "CameraFreeMoveBack2", Accelerator( GDK_Down ), + FreeMoveCameraMoveBackKeyDownCaller( camwnd.getCamera() ), + FreeMoveCameraMoveBackKeyUpCaller( camwnd.getCamera() ) + ); + GlobalKeyEvents_insert( "CameraFreeMoveLeft2", Accelerator( GDK_Left ), + FreeMoveCameraMoveLeftKeyDownCaller( camwnd.getCamera() ), + FreeMoveCameraMoveLeftKeyUpCaller( camwnd.getCamera() ) + ); + GlobalKeyEvents_insert( "CameraFreeMoveRight2", Accelerator( GDK_Right ), + FreeMoveCameraMoveRightKeyDownCaller( camwnd.getCamera() ), + FreeMoveCameraMoveRightKeyUpCaller( camwnd.getCamera() ) + ); + GlobalKeyEvents_insert( "CameraFreeMoveUp", Accelerator( GDK_period ), FreeMoveCameraMoveUpKeyDownCaller( camwnd.getCamera() ), FreeMoveCameraMoveUpKeyUpCaller( camwnd.getCamera() ) @@ -1119,6 +1137,12 @@ void CamWnd_Add_Handlers_FreeMove( CamWnd& camwnd ){ KeyEvent_connect( "CameraFreeMoveBack" ); KeyEvent_connect( "CameraFreeMoveLeft" ); KeyEvent_connect( "CameraFreeMoveRight" ); + + KeyEvent_connect( "CameraFreeMoveForward2" ); + KeyEvent_connect( "CameraFreeMoveBack2" ); + KeyEvent_connect( "CameraFreeMoveLeft2" ); + KeyEvent_connect( "CameraFreeMoveRight2" ); + KeyEvent_connect( "CameraFreeMoveUp" ); KeyEvent_connect( "CameraFreeMoveDown" ); } @@ -1128,6 +1152,12 @@ void CamWnd_Remove_Handlers_FreeMove( CamWnd& camwnd ){ KeyEvent_disconnect( "CameraFreeMoveBack" ); KeyEvent_disconnect( "CameraFreeMoveLeft" ); KeyEvent_disconnect( "CameraFreeMoveRight" ); + + KeyEvent_disconnect( "CameraFreeMoveForward2" ); + KeyEvent_disconnect( "CameraFreeMoveBack2" ); + KeyEvent_disconnect( "CameraFreeMoveLeft2" ); + KeyEvent_disconnect( "CameraFreeMoveRight2" ); + KeyEvent_disconnect( "CameraFreeMoveUp" ); KeyEvent_disconnect( "CameraFreeMoveDown" ); @@ -1910,6 +1940,11 @@ void CamWnd_Construct(){ GlobalShortcuts_insert( "CameraFreeMoveLeft", Accelerator( 'A' ) ); GlobalShortcuts_insert( "CameraFreeMoveRight", Accelerator( 'D' ) ); + GlobalShortcuts_insert( "CameraFreeMoveForward2", Accelerator( GDK_Up ) ); + GlobalShortcuts_insert( "CameraFreeMoveBack2", Accelerator( GDK_Down ) ); + GlobalShortcuts_insert( "CameraFreeMoveLeft2", Accelerator( GDK_Left ) ); + GlobalShortcuts_insert( "CameraFreeMoveRight2", Accelerator( GDK_Right ) ); + GlobalToggles_insert( "ShowStats", ShowStatsToggleCaller(), ToggleItem::AddCallbackCaller( g_show_stats ) ); GlobalPreferenceSystem().registerPreference( "ShowStats", BoolImportStringCaller( g_camwindow_globals_private.m_showStats ), BoolExportStringCaller( g_camwindow_globals_private.m_showStats ) ); diff --git a/radiant/entity.cpp b/radiant/entity.cpp index 0704f50a..a5f42177 100644 --- a/radiant/entity.cpp +++ b/radiant/entity.cpp @@ -45,6 +45,9 @@ #include "qe3.h" #include "commands.h" +#include "brushmanip.h" +#include "patchmanip.h" + struct entity_globals_t { Vector3 color_entity; @@ -386,6 +389,18 @@ void Entity_createFromSelection( const char* name, const Vector3& origin ){ Node_getEntity( node )->setKeyValue( "model", model ); } } + + if ( string_compare_nocase_n( name, "trigger_", 8 ) == 0 && brushesSelected ){ + const char* shader = g_pGameDescription->getKeyValue( "trigger_shader" ); + if ( shader && *shader ){ + Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader ); + Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader ); + } + else{ + Scene_PatchSetShader_Selected( GlobalSceneGraph(), "textures/common/trigger" ); + Scene_BrushSetShader_Selected( GlobalSceneGraph(), "textures/common/trigger" ); + } + } } #if 0 diff --git a/radiant/environment.cpp b/radiant/environment.cpp index 74de78b6..fc23e9b0 100644 --- a/radiant/environment.cpp +++ b/radiant/environment.cpp @@ -257,6 +257,18 @@ void environment_init( int argc, char* argv[] ){ #include +char openCmdMap[260]; + +void cmdMap(){ + openCmdMap[0] = '\0'; + for ( int i = 1; i < g_argc; ++i ) + { + if ( !stricmp( g_argv[i] + strlen(g_argv[i]) - 4, ".map" ) ){ + strcpy( openCmdMap, g_argv[i] ); + } + } +} + void environment_init( int argc, char* argv[] ){ args_init( argc, argv ); @@ -294,6 +306,7 @@ void environment_init( int argc, char* argv[] ){ home_path = home.c_str(); } gamedetect(); + cmdMap(); } #else diff --git a/radiant/environment.h b/radiant/environment.h index 7ee16c6f..8d15115e 100644 --- a/radiant/environment.h +++ b/radiant/environment.h @@ -29,4 +29,9 @@ const char* environment_get_app_path(); extern int g_argc; extern char** g_argv; +#if defined( WIN32 ) +extern char openCmdMap[260]; +#endif + + #endif diff --git a/radiant/grid.cpp b/radiant/grid.cpp index 148fa4fa..38e11239 100644 --- a/radiant/grid.cpp +++ b/radiant/grid.cpp @@ -228,7 +228,7 @@ void Grid_constructMenu( GtkMenu* menu ){ } void Grid_registerShortcuts(){ - command_connect_accelerator( "ToggleGrid" ); +// command_connect_accelerator( "ToggleGrid" ); command_connect_accelerator( "GridDown" ); command_connect_accelerator( "GridUp" ); command_connect_accelerator( "ToggleGridSnap" ); diff --git a/radiant/main.cpp b/radiant/main.cpp index f19eb4db..0a1eb3b1 100644 --- a/radiant/main.cpp +++ b/radiant/main.cpp @@ -554,8 +554,18 @@ int main( int argc, char* argv[] ){ if ( lib != 0 ) { void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" ); if ( qDwmEnableComposition ) { + bool Aero = false; + for ( int i = 1; i < argc; ++i ){ + if ( !stricmp( argv[i], "-aero" ) ){ + Aero = true; + qDwmEnableComposition( TRUE ); + break; + } + } // disable Aero - qDwmEnableComposition( FALSE ); + if ( !Aero ){ + qDwmEnableComposition( FALSE ); + } } FreeLibrary( lib ); } @@ -624,6 +634,12 @@ int main( int argc, char* argv[] ){ hide_splash(); +#ifdef WIN32 + if( openCmdMap[0] != '\0' ){ + Map_LoadFile( openCmdMap ); + } + else +#endif // WIN32 if ( g_bLoadLastMap && !g_strLastMap.empty() ) { Map_LoadFile( g_strLastMap.c_str() ); } diff --git a/radiant/mainframe.cpp b/radiant/mainframe.cpp index 310e7d97..6cce87d0 100644 --- a/radiant/mainframe.cpp +++ b/radiant/mainframe.cpp @@ -2221,13 +2221,13 @@ void PatchInspector_registerShortcuts(){ } void Patch_registerShortcuts(){ - command_connect_accelerator( "InvertCurveTextureX" ); - command_connect_accelerator( "InvertCurveTextureY" ); +// command_connect_accelerator( "InvertCurveTextureX" ); +// command_connect_accelerator( "InvertCurveTextureY" ); command_connect_accelerator( "IncPatchColumn" ); command_connect_accelerator( "IncPatchRow" ); command_connect_accelerator( "DecPatchColumn" ); command_connect_accelerator( "DecPatchRow" ); - command_connect_accelerator( "NaturalizePatch" ); +// command_connect_accelerator( "NaturalizePatch" ); //command_connect_accelerator("CapCurrentCurve"); } @@ -2259,6 +2259,8 @@ void SelectNudge_registerShortcuts(){ //command_connect_accelerator("SelectNudgeRight"); //command_connect_accelerator("SelectNudgeUp"); //command_connect_accelerator("SelectNudgeDown"); + command_connect_accelerator( "UnSelectSelection2" ); + command_connect_accelerator( "DeleteSelection2" ); } void SnapToGrid_registerShortcuts(){ @@ -2275,17 +2277,17 @@ void SurfaceInspector_registerShortcuts(){ void register_shortcuts(){ - PatchInspector_registerShortcuts(); +// PatchInspector_registerShortcuts(); Patch_registerShortcuts(); Grid_registerShortcuts(); - XYWnd_registerShortcuts(); +// XYWnd_registerShortcuts(); CamWnd_registerShortcuts(); Manipulators_registerShortcuts(); SurfaceInspector_registerShortcuts(); TexdefNudge_registerShortcuts(); SelectNudge_registerShortcuts(); - SnapToGrid_registerShortcuts(); - SelectByType_registerShortcuts(); +// SnapToGrid_registerShortcuts(); +// SelectByType_registerShortcuts(); } void File_constructToolbar( GtkToolbar* toolbar ){ @@ -2804,7 +2806,7 @@ void MainFrame::Create(){ toolbar_append_toggle_button( plugin_toolbar, "Lights (ALT + 0)", "lightinspector.bmp", "FilterLights" ); toolbar_append_toggle_button( plugin_toolbar, "Models (SHIFT + M)", "f-models.bmp", "FilterModels" ); toolbar_append_toggle_button( plugin_toolbar, "Triggers (CTRL + SHIFT + T)", "f-triggers.bmp", "FilterTriggers" ); - toolbar_append_toggle_button( plugin_toolbar, "Decals (SHIFT + D)", "f-decals.bmp", "FilterDecals" ); +// toolbar_append_toggle_button( plugin_toolbar, "Decals (SHIFT + D)", "f-decals.bmp", "FilterDecals" ); gtk_toolbar_append_space( GTK_TOOLBAR( plugin_toolbar ) ); toolbar_append_button( plugin_toolbar, "InvertFilters", "f-invert.bmp", "InvertFilters" ); toolbar_append_button( plugin_toolbar, "ResetFilters", "f-reset.bmp", "ResetFilters" ); @@ -3245,9 +3247,11 @@ void MainFrame_Construct(){ GlobalCommands_insert( "CloneSelection", FreeCaller(), Accelerator( GDK_space ) ); GlobalCommands_insert( "CloneSelectionAndMakeUnique", FreeCaller(), Accelerator( GDK_space, (GdkModifierType)GDK_SHIFT_MASK ) ); // GlobalCommands_insert( "DeleteSelection", FreeCaller(), Accelerator( GDK_BackSpace ) ); + GlobalCommands_insert( "DeleteSelection2", FreeCaller(), Accelerator( GDK_BackSpace ) ); GlobalCommands_insert( "DeleteSelection", FreeCaller(), Accelerator( 'Z' ) ); GlobalCommands_insert( "ParentSelection", FreeCaller() ); // GlobalCommands_insert( "UnSelectSelection", FreeCaller(), Accelerator( GDK_Escape ) ); + GlobalCommands_insert( "UnSelectSelection2", FreeCaller(), Accelerator( GDK_Escape ) ); GlobalCommands_insert( "UnSelectSelection", FreeCaller(), Accelerator( 'C' ) ); GlobalCommands_insert( "InvertSelection", FreeCaller(), Accelerator( 'I' ) ); GlobalCommands_insert( "SelectInside", FreeCaller() ); diff --git a/radiant/map.cpp b/radiant/map.cpp index 4706a0c6..e664667b 100644 --- a/radiant/map.cpp +++ b/radiant/map.cpp @@ -1521,7 +1521,7 @@ bool Map_ImportFile( const char* filename ){ tryDecompile: - const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue( "q3map2_type" ); + const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" ); int n = string_length( path_get_extension( filename ) ); if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) { StringBuffer output; diff --git a/radiant/patch.cpp b/radiant/patch.cpp index b31def46..75b1a53d 100644 --- a/radiant/patch.cpp +++ b/radiant/patch.cpp @@ -2778,6 +2778,321 @@ void Patch::BuildVertexArray(){ } +Vector3 getAverageNormal(const Vector3& normal1, const Vector3& normal2, double thickness) +{ + // Beware of normals with 0 length + if ( ( fabs( normal1[0] ) + fabs( normal1[1] ) + fabs( normal1[2] ) ) == 0 ) return normal2; + if ( ( fabs( normal2[0] ) + fabs( normal2[1] ) + fabs( normal2[2] ) ) == 0) return normal1; + + // Both normals have length > 0 + Vector3 n1 = vector3_normalised( normal1 ); + Vector3 n2 = vector3_normalised( normal2 ); + + // Get the angle bisector + Vector3 normal = vector3_normalised (n1 + n2); + + // Now calculate the length correction out of the angle + // of the two normals + /* float factor = cos(n1.angle(n2) * 0.5); */ + float factor = (float) vector3_dot( n1, n2 ); + if ( factor > 1.0 ) factor = 1; + factor = acos( factor ); + + factor = cos( factor * 0.5 ); + + // Stretch the normal to fit the required thickness + normal *= thickness; + + // Check for div by zero (if the normals are antiparallel) + // and stretch the resulting normal, if necessary + if (factor != 0) + { + normal /= factor; + } + + return normal; +} + +void Patch::createThickenedOpposite(const Patch& sourcePatch, + const float thickness, + const int axis, + bool& no12, + bool& no34) +{ + // Clone the dimensions from the other patch + setDims(sourcePatch.getWidth(), sourcePatch.getHeight()); + + // Also inherit the tesselation from the source patch + //setFixedSubdivisions(sourcePatch.subdivionsFixed(), sourcePatch.getSubdivisions()); + + // Copy the shader from the source patch + SetShader(sourcePatch.GetShader()); + + // if extrudeAxis == 0,0,0 the patch is extruded along its vertex normals + Vector3 extrudeAxis(0,0,0); + + switch (axis) { + case 0: // X-Axis + extrudeAxis = Vector3(1,0,0); + break; + case 1: // Y-Axis + extrudeAxis = Vector3(0,1,0); + break; + case 2: // Z-Axis + extrudeAxis = Vector3(0,0,1); + break; + default: + // Default value already set during initialisation + break; + } + + //check if certain seams are required + //( endpoints != startpoints ) - not a cylinder or something + for (std::size_t col = 0; col < m_width; col++){ + if( vector3_length_squared( sourcePatch.ctrlAt( 0, col ).m_vertex - sourcePatch.ctrlAt( m_height - 1, col ).m_vertex ) > 0.1f ){ + //globalOutputStream() << "yes12.\n"; + no12 = false; + break; + } + } + for (std::size_t row = 0; row < m_height; row++){ + if( vector3_length_squared( sourcePatch.ctrlAt( row, 0 ).m_vertex - sourcePatch.ctrlAt( row, m_width - 1 ).m_vertex ) > 0.1f ){ + no34 = false; + //globalOutputStream() << "yes34.\n"; + break; + } + } + + for (std::size_t col = 0; col < m_width; col++) + { + for (std::size_t row = 0; row < m_height; row++) + { + // The current control vertex on the other patch + const PatchControl& curCtrl = sourcePatch.ctrlAt(row, col); + + Vector3 normal; + + // Are we extruding along vertex normals (i.e. extrudeAxis == 0,0,0)? + if (extrudeAxis == Vector3(0,0,0)) + { + // The col tangents (empty if 0,0,0) + Vector3 colTangent[2] = { Vector3(0,0,0), Vector3(0,0,0) }; + + // Are we at the beginning/end of the column? + if (col == 0 || col == m_width - 1) + { + // Get the next row index + std::size_t nextCol = (col == m_width - 1) ? (col - 1) : (col + 1); + + const PatchControl& colNeighbour = sourcePatch.ctrlAt(row, nextCol); + + // One available tangent + colTangent[0] = colNeighbour.m_vertex - curCtrl.m_vertex; + // Reverse it if we're at the end of the column + colTangent[0] *= (col == m_width - 1) ? -1 : +1; + } + // We are in between, two tangents can be calculated + else + { + // Take two neighbouring vertices that should form a line segment + const PatchControl& neighbour1 = sourcePatch.ctrlAt(row, col+1); + const PatchControl& neighbour2 = sourcePatch.ctrlAt(row, col-1); + + // Calculate both available tangents + colTangent[0] = neighbour1.m_vertex - curCtrl.m_vertex; + colTangent[1] = neighbour2.m_vertex - curCtrl.m_vertex; + + // Reverse the second one + colTangent[1] *= -1; + + // Cull redundant tangents (parallel) + if ( ( fabs( colTangent[1][0] + colTangent[0][0] ) + fabs( colTangent[1][1] + colTangent[0][1] ) + fabs( colTangent[1][2] + colTangent[0][2] ) ) < 0.00001 || + ( fabs( colTangent[1][0] - colTangent[0][0] ) + fabs( colTangent[1][1] - colTangent[0][1] ) + fabs( colTangent[1][2] - colTangent[0][2] ) ) < 0.00001 ) + { + colTangent[1] = Vector3(0,0,0); + } + } + + // Calculate the tangent vectors to the next row + Vector3 rowTangent[2] = { Vector3(0,0,0), Vector3(0,0,0) }; + + // Are we at the beginning or the end? + if (row == 0 || row == m_height - 1) + { + // Yes, only calculate one row tangent + // Get the next row index + std::size_t nextRow = (row == m_height - 1) ? (row - 1) : (row + 1); + + const PatchControl& rowNeighbour = sourcePatch.ctrlAt(nextRow, col); + + // First tangent + rowTangent[0] = rowNeighbour.m_vertex - curCtrl.m_vertex; + // Reverse it accordingly + rowTangent[0] *= (row == m_height - 1) ? -1 : +1; + } + else + { + // Two tangents to calculate + const PatchControl& rowNeighbour1 = sourcePatch.ctrlAt(row + 1, col); + const PatchControl& rowNeighbour2 = sourcePatch.ctrlAt(row - 1, col); + + // First tangent + rowTangent[0] = rowNeighbour1.m_vertex - curCtrl.m_vertex; + rowTangent[1] = rowNeighbour2.m_vertex - curCtrl.m_vertex; + + // Reverse the second one + rowTangent[1] *= -1; + + // Cull redundant tangents + if ( ( fabs( rowTangent[1][0] + rowTangent[0][0] ) + fabs( rowTangent[1][1] + rowTangent[0][1] ) + fabs( rowTangent[1][2] + rowTangent[0][2] ) ) < 0.00001 || + ( fabs( rowTangent[1][0] - rowTangent[0][0] ) + fabs( rowTangent[1][1] - rowTangent[0][1] ) + fabs( rowTangent[1][2] - rowTangent[0][2] ) ) < 0.00001 ) + { + rowTangent[1] = Vector3(0,0,0); + } + } + + // If two column tangents are available, take the length-corrected average + if ( ( fabs( colTangent[1][0] ) + fabs( colTangent[1][1] ) + fabs( colTangent[1][2] ) ) > 0) + { + // Two column normals to calculate + Vector3 normal1 = vector3_normalised( vector3_cross( rowTangent[0], colTangent[0] ) ); + Vector3 normal2 = vector3_normalised( vector3_cross( rowTangent[0], colTangent[1] ) ); + + normal = getAverageNormal(normal1, normal2, thickness); + + // Scale the normal down, as it is multiplied with thickness later on + normal /= thickness; + } + else + { + // One column tangent available, maybe we have a second rowtangent? + if ( ( fabs( rowTangent[1][0] ) + fabs( rowTangent[1][1] ) + fabs( rowTangent[1][2] ) ) > 0) + { + // Two row normals to calculate + Vector3 normal1 = vector3_normalised( vector3_cross( rowTangent[0], colTangent[0] ) ); + Vector3 normal2 = vector3_normalised( vector3_cross( rowTangent[1], colTangent[0] ) ); + + normal = getAverageNormal(normal1, normal2, thickness); + + // Scale the normal down, as it is multiplied with thickness later on + normal /= thickness; + } + else + { + if ( vector3_length_squared( vector3_cross( rowTangent[0], colTangent[0] ) ) > 0 ){ + normal = vector3_normalised( vector3_cross( rowTangent[0], colTangent[0] ) ); + } + else{ + normal = extrudeAxis; + } + } + } + } + else + { + // Take the predefined extrude direction instead + normal = extrudeAxis; + } + + // Store the new coordinates into this patch at the current coords + ctrlAt(row, col).m_vertex = curCtrl.m_vertex + normal*thickness; + + // Clone the texture cooordinates of the source patch + ctrlAt(row, col).m_texcoord = curCtrl.m_texcoord; + } + } + + // Notify the patch about the change + controlPointsChanged(); +} + +void Patch::createThickenedWall(const Patch& sourcePatch, + const Patch& targetPatch, + const int wallIndex) +{ + // Copy the shader from the source patch + SetShader(sourcePatch.GetShader()); + + // The start and end control vertex indices + int start = 0; + int end = 0; + // The increment (incr = 1 for the "long" edge, incr = width for the "short" edge) + int incr = 1; + + // These are the target dimensions of this wall + // The width is depending on which edge is "seamed". + int cols = 0; + int rows = 3; + + int sourceWidth = static_cast(sourcePatch.getWidth()); + int sourceHeight = static_cast(sourcePatch.getHeight()); +/* + bool sourceTesselationFixed = sourcePatch.subdivionsFixed(); + Subdivisions sourceTesselationX(sourcePatch.getSubdivisions().x(), 1); + Subdivisions sourceTesselationY(sourcePatch.getSubdivisions().y(), 1); +*/ + // Determine which of the four edges have to be connected + // and calculate the start, end & stepsize for the following loop + switch (wallIndex) { + case 0: + cols = sourceWidth; + start = 0; + end = sourceWidth - 1; + incr = 1; + //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationX); + break; + case 1: + cols = sourceWidth; + start = sourceWidth * (sourceHeight-1); + end = sourceWidth*sourceHeight - 1; + incr = 1; + //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationX); + break; + case 2: + cols = sourceHeight; + start = 0; + end = sourceWidth*(sourceHeight-1); + incr = sourceWidth; + //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationY); + break; + case 3: + cols = sourceHeight; + start = sourceWidth - 1; + end = sourceWidth*sourceHeight - 1; + incr = sourceWidth; + //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationY); + break; + } + + setDims(cols, rows); + + const PatchControlArray& sourceCtrl = sourcePatch.getControlPoints(); + const PatchControlArray& targetCtrl = targetPatch.getControlPoints(); + + int col = 0; + // Now go through the control vertices with these calculated stepsize + for (int idx = start; idx <= end; idx += incr, col++) { + Vector3 sourceCoord = sourceCtrl[idx].m_vertex; + Vector3 targetCoord = targetCtrl[idx].m_vertex; + Vector3 middleCoord = (sourceCoord + targetCoord) / 2; + + // Now assign the vertex coordinates + ctrlAt(0, col).m_vertex = sourceCoord; + ctrlAt(1, col).m_vertex = middleCoord; + ctrlAt(2, col).m_vertex = targetCoord; + } + + if (wallIndex == 0 || wallIndex == 3) { + InvertMatrix(); + } + + // Notify the patch about the change + controlPointsChanged(); + + // Texture the patch "naturally" + NaturalTexture(); +} + class PatchFilterWrapper : public Filter { diff --git a/radiant/patch.h b/radiant/patch.h index a1f1d272..7cca4286 100644 --- a/radiant/patch.h +++ b/radiant/patch.h @@ -880,6 +880,12 @@ const_iterator end() const { PatchControlArray& getControlPoints(){ return m_ctrl; } + +// Same as above, just for const arguments +const PatchControlArray& getControlPoints() const { + return m_ctrl; +} + PatchControlArray& getControlPointsTransformed(){ return m_ctrlTransformed; } @@ -916,6 +922,8 @@ void SetTextureRepeat( float s, float t ); // call with s=1 t=1 for FIT void CapTexture(); void NaturalTexture(); void ProjectTexture( int nAxis ); +void createThickenedOpposite(const Patch& sourcePatch, const float thickness, const int axis, bool& no12, bool& no34 ); +void createThickenedWall(const Patch& sourcePatch, const Patch& targetPatch, const int wallIndex); void undoSave(){ if ( m_map != 0 ) { diff --git a/radiant/patchmanip.cpp b/radiant/patchmanip.cpp index 9ea7c5d8..b2c0b2d6 100644 --- a/radiant/patchmanip.cpp +++ b/radiant/patchmanip.cpp @@ -196,6 +196,83 @@ void Scene_PatchDeform( scene::Graph& graph, const int deform ) } +void Patch_thicken( Patch& patch, scene::Instance& instance, const float thickness, bool seams, const int axis ){ + + // Create a new patch node + NodeSmartReference node(g_patchCreator->createPatch()); + // Insert the node into worldspawn + Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(node); + + // Retrieve the contained patch from the node + Patch* targetPatch = Node_getPatch(node); + + // Create the opposite patch with the given thickness = distance + bool no12 = true; + bool no34 = true; + targetPatch->createThickenedOpposite(patch, thickness, axis, no12, no34); + + // Now select the newly created patches + { + scene::Path patchpath(makeReference(GlobalSceneGraph().root())); + patchpath.push(makeReference(*Map_GetWorldspawn(g_map))); + patchpath.push(makeReference(node.get())); + Instance_getSelectable(*GlobalSceneGraph().find(patchpath))->setSelected(true); + } + + if (seams && thickness != 0.0f) { + int i = 0; + if ( no12 ){ + i = 2; + } + int iend = 4; + if ( no34 ){ + iend = 2; + } + // Now create the four walls + for ( ; i < iend; i++ ) { + // Allocate new patch + NodeSmartReference node = NodeSmartReference(g_patchCreator->createPatch()); + // Insert each node into worldspawn + Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(node); + + // Retrieve the contained patch from the node + Patch* wallPatch = Node_getPatch(node); + + // Create the wall patch by passing i as wallIndex + wallPatch->createThickenedWall( patch, *targetPatch, i); + + if( ( wallPatch->localAABB().extents[0] <= 0.00005 && wallPatch->localAABB().extents[1] <= 0.00005 ) || + ( wallPatch->localAABB().extents[1] <= 0.00005 && wallPatch->localAABB().extents[2] <= 0.00005 ) || + ( wallPatch->localAABB().extents[0] <= 0.00005 && wallPatch->localAABB().extents[2] <= 0.00005 ) ){ + //globalOutputStream() << "Thicken: Discarding degenerate patch.\n"; + Node_getTraversable( Map_FindOrInsertWorldspawn(g_map) )->erase( node ); + } + else + // Now select the newly created patches + { + scene::Path patchpath(makeReference(GlobalSceneGraph().root())); + patchpath.push(makeReference(*Map_GetWorldspawn(g_map))); + patchpath.push(makeReference(node.get())); + Instance_getSelectable(*GlobalSceneGraph().find(patchpath))->setSelected(true); + } + } + } + + // Invert the target patch so that it faces the opposite direction + targetPatch->InvertMatrix(); +} + +void Scene_PatchThicken( scene::Graph& graph, const int thickness, bool seams, const int axis ) +{ + InstanceVector instances; + Scene_forEachVisibleSelectedPatchInstance( PatchStoreInstance( instances ) ); + for ( InstanceVector::const_iterator i = instances.begin(); i != instances.end(); ++i ) + { + Patch_thicken( *Node_getPatch( ( *i )->path().top() ), *( *i ), thickness, seams, axis ); + } + +} + Patch* Scene_GetUltimateSelectedVisiblePatch(){ if ( GlobalSelectionSystem().countSelected() != 0 ) { scene::Node& node = GlobalSelectionSystem().ultimateSelected().path().top(); @@ -618,6 +695,13 @@ void Patch_Deform(){ DoPatchDeformDlg(); } +void DoPatchThickenDlg(); + +void Patch_Thicken(){ + UndoableCommand undo( "patchThicken" ); + + DoPatchThickenDlg(); +} @@ -732,6 +816,7 @@ void Patch_registerCommands(){ GlobalCommands_insert( "MakeOverlayPatch", FreeCaller(), Accelerator( 'Y' ) ); GlobalCommands_insert( "ClearPatchOverlays", FreeCaller(), Accelerator( 'L', (GdkModifierType)GDK_CONTROL_MASK ) ); GlobalCommands_insert( "PatchDeform", FreeCaller() ); + GlobalCommands_insert( "PatchThicken", FreeCaller() ); } void Patch_constructToolbar( GtkToolbar* toolbar ){ @@ -840,6 +925,7 @@ void Patch_constructMenu( GtkMenu* menu ){ } menu_separator( menu ); create_menu_item_with_mnemonic( menu, "Deform...", "PatchDeform" ); + create_menu_item_with_mnemonic( menu, "Thicken...", "PatchThicken" ); } @@ -1198,3 +1284,128 @@ EMessageBoxReturn DoCapDlg( ECapDialog* type ){ return ret; } + + +void DoPatchThickenDlg(){ + ModalDialog dialog; + GtkWidget* thicknessW; + GtkWidget* seamsW; + GtkWidget* radX; + GtkWidget* radY; + GtkWidget* radZ; + GtkWidget* radNormals; + + GtkWindow* window = create_dialog_window( MainFrame_getWindow(), "Patch thicken", G_CALLBACK( dialog_delete_callback ), &dialog ); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group( window, accel ); + + { + GtkHBox* hbox = create_dialog_hbox( 4, 4 ); + gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( hbox ) ); + { + GtkTable* table = create_dialog_table( 2, 4, 4, 4 ); + gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 ); + { + GtkLabel* label = GTK_LABEL( gtk_label_new( "Thickness:" ) ); + gtk_widget_show( GTK_WIDGET( label ) ); + gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1, + (GtkAttachOptions) ( GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 ); + } + { + GtkWidget* entry = gtk_entry_new(); + gtk_entry_set_text( GTK_ENTRY( entry ), "16" ); + gtk_widget_set_size_request( entry, 40, -1 ); + gtk_widget_show( entry ); + gtk_table_attach( table, entry, 1, 2, 0, 1, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + + thicknessW = entry; + } + { + // Create the "create seams" label + GtkWidget* _seamsCheckBox = gtk_check_button_new_with_label( "Side walls" ); + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( _seamsCheckBox ), TRUE ); + gtk_widget_show( _seamsCheckBox ); + gtk_table_attach( table, _seamsCheckBox, 3, 4, 0, 1, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + seamsW = _seamsCheckBox; + + } + { + // Create the radio button group for choosing the extrude axis + GtkWidget* _radNormals = gtk_radio_button_new_with_label( NULL, "Normal" ); + GtkWidget* _radX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_radNormals), "X" ); + GtkWidget* _radY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_radNormals), "Y" ); + GtkWidget* _radZ = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_radNormals), "Z" ); + gtk_widget_show( _radNormals ); + gtk_widget_show( _radX ); + gtk_widget_show( _radY ); + gtk_widget_show( _radZ ); + + + // Pack the buttons into the table + gtk_table_attach( table, _radNormals, 0, 1, 1, 2, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_table_attach( table, _radX, 1, 2, 1, 2, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_table_attach( table, _radY, 2, 3, 1, 2, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_table_attach( table, _radZ, 3, 4, 1, 2, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + radX = _radX; + radY = _radY; + radZ = _radZ; + radNormals = _radNormals; + } + } + { + 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( dialog_button_ok ), &dialog ); + gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); + widget_make_default( GTK_WIDGET( button ) ); + gtk_widget_grab_focus( 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( dialog_button_cancel ), &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 ); + } + } + } + + if ( modal_dialog_show( window, dialog ) == eIDOK ) { + int axis; + bool seams; + float thickness = static_cast( atoi( gtk_entry_get_text( GTK_ENTRY( thicknessW ) ) ) ); + seams = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( seamsW )) ? true : false; + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radX))) { + axis = 0; + } + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radY))) { + axis = 1; + } + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radZ))) { + axis = 2; + } + else { + // Extrude along normals + axis = 3; + } + Scene_PatchThicken( GlobalSceneGraph(), thickness, seams, axis ); + } + + gtk_widget_destroy( GTK_WIDGET( window ) ); +}