diff --git a/contrib/terrain_generator/brush_builder.cpp b/contrib/terrain_generator/brush_builder.cpp index 31b08565..d5f19912 100644 --- a/contrib/terrain_generator/brush_builder.cpp +++ b/contrib/terrain_generator/brush_builder.cpp @@ -274,22 +274,27 @@ static void insert_floor_ceil_brush( scene::Node& entity, static void insert_wall_brush( scene::Node& entity, double x_bl, double x_tl, double x_br, double x_tr, - double gy, double gmx_y, double gz, double gmx_z, - double outer_x, double limit_x, + double gu, double gmx_u, double gz, double gmx_z, + double outer, double limit, const char* top_tex, const char* caulk, - bool is_left, bool alt_dir ){ + bool is_left, bool alt_dir, Axis axis ){ // Wall geometry is floor/ceiling rotated 90°. - // Coordinate mapping: floor(fx,fy,fz) → world(fz, fx, fy) - // gy→fx, gz→fy, gmx_y→fmx, gmx_z→fmy, x_*→fz_*, outer_x/limit_x→fmin_z/fsolid_top - // Left wall: inner surface faces +X (like floor), cap at outer_x (min_z side). - // Right wall: inner surface faces −X (like ceiling), cap at outer_x (solid_top side). + // For a Y-running tunnel (walls at fixed X, left/right), coordinate mapping: + // floor(fx,fy,fz) → world(fz, fx, fy) — gu→fx, gz→fy, gmx_u→fmx, gmx_z→fmy, + // x_*→fz_*, outer/limit→fmin_z/fsolid_top. This is a proper 90° rotation, so + // face winding carries over unchanged. + // For an X-running tunnel (walls at fixed Y, near/far), the same swap + // (world(fx, fz, fy)) is a mirror image of the above rather than a rotation, + // so v1/v2 are swapped per face to keep normals pointing outward. + // Left/near wall: inner surface faces away from outer (min_z side). + // Right/far wall: inner surface faces away from outer (solid_top side). _QERFaceData face; const bool is_ceiling = !is_left; - const double min_z = is_left ? outer_x : limit_x; - const double solid_top = is_left ? limit_x : outer_x; + const double min_z = is_left ? outer : limit; + const double solid_top = is_left ? limit : outer; // Aliases to match insert_floor_ceil_brush variable names exactly. - const double x = gy, y = gz, mx = gmx_y, my = gmx_z; + const double x = gu, y = gz, mx = gmx_u, my = gmx_z; const double z_bl = x_bl, z_tl = x_tl, z_br = x_br, z_tr = x_tr; // fw: emit a face using floor coordinate order but with world axes permuted. @@ -297,7 +302,10 @@ static void insert_wall_brush( scene::Node& entity, double fx1, double fy1, double fz1, double fx2, double fy2, double fz2, const char* shader ){ - fill_face( face, fz0, fx0, fy0, fz1, fx1, fy1, fz2, fx2, fy2, shader ); + if ( axis == Axis::Y ) + fill_face( face, fz0, fx0, fy0, fz1, fx1, fy1, fz2, fx2, fy2, shader ); + else + fill_face( face, fx0, fz0, fy0, fx2, fz2, fy2, fx1, fz1, fy1, shader ); }; if ( !alt_dir ) { @@ -366,7 +374,7 @@ static void insert_wall_brush( scene::Node& entity, void build_tunnel_brushes( const BrushData& target, double step_x, double step_y, const TunnelMaps& maps, const char* top_texture, - double cave_height, double slope_height ){ + double cave_height, double slope_height, Axis axis ){ // Undo is started by the caller so deletion + generation form one step. scene::Node& floor_entity = create_func_group(); scene::Node& ceil_entity = create_func_group(); @@ -420,48 +428,56 @@ void build_tunnel_brushes( const BrushData& target, double step_x, double step_y } } - // Walls — must match the range generated by terrain_engine. + // Walls — must match the range generated by terrain_engine. Grid runs along + // Y (left/right walls at fixed X) for a Y-running tunnel, or along X + // (near/far walls at fixed Y) for an X-running tunnel. // slope_height may be negative (downward slope), so clamp accordingly. double wall_min_z = target.max_z + std::min( 0.0, slope_height ); double wall_max_z = wall_min_z + cave_height + std::abs( slope_height ); double step_z = maps.step_z; - double outer_left = target.min_x - step_x; - double outer_right = target.max_x + step_x; - double limit_x = ( target.min_x + target.max_x ) / 2.0; - int gy_index = 0; - for ( double gy = target.min_y; gy < target.max_y - 0.01; gy += step_y, ++gy_index ) { + const bool along_y = ( axis == Axis::Y ); + const double along_min = along_y ? target.min_y : target.min_x; + const double along_max = along_y ? target.max_y : target.max_x; + const double along_step = along_y ? step_y : step_x; + const double outer_lo = along_y ? target.min_x - step_x : target.min_y - step_y; + const double outer_hi = along_y ? target.max_x + step_x : target.max_y + step_y; + const double limit = along_y ? ( target.min_x + target.max_x ) / 2.0 + : ( target.min_y + target.max_y ) / 2.0; + + int gu_index = 0; + for ( double gu = along_min; gu < along_max - 0.01; gu += along_step, ++gu_index ) { int gz_index = 0; for ( double gz = wall_min_z; gz < wall_max_z - 0.01; gz += step_z, ++gz_index ) { - double gmx_y = gy + step_y < target.max_y ? gy + step_y : target.max_y; - double gmx_z = gz + step_z < wall_max_z ? gz + step_z : wall_max_z; + double gmx_u = gu + along_step < along_max ? gu + along_step : along_max; + double gmx_z = gz + step_z < wall_max_z ? gz + step_z : wall_max_z; - auto safe_wall = [&]( const HeightMap& m, double kx, double ky, double fallback ) -> double { - auto it = m.find( { r2( kx ), r2( ky ) } ); + auto safe_wall = [&]( const HeightMap& m, double ku, double kz, double fallback ) -> double { + auto it = m.find( { r2( ku ), r2( kz ) } ); if ( it == m.end() ) { globalErrorStream() << "TerrainGenerator: wall map missing key (" - << kx << ", " << ky << ") - using fallback\n"; + << ku << ", " << kz << ") - using fallback\n"; return fallback; } return it->second; }; - double lx_bl = safe_wall( maps.left_wall_map, gy, gz, limit_x ); - double lx_tl = safe_wall( maps.left_wall_map, gy, gmx_z, limit_x ); - double lx_br = safe_wall( maps.left_wall_map, gmx_y, gz, limit_x ); - double lx_tr = safe_wall( maps.left_wall_map, gmx_y, gmx_z, limit_x ); + double lx_bl = safe_wall( maps.left_wall_map, gu, gz, limit ); + double lx_tl = safe_wall( maps.left_wall_map, gu, gmx_z, limit ); + double lx_br = safe_wall( maps.left_wall_map, gmx_u, gz, limit ); + double lx_tr = safe_wall( maps.left_wall_map, gmx_u, gmx_z, limit ); - double rx_bl = safe_wall( maps.right_wall_map, gy, gz, limit_x ); - double rx_tl = safe_wall( maps.right_wall_map, gy, gmx_z, limit_x ); - double rx_br = safe_wall( maps.right_wall_map, gmx_y, gz, limit_x ); - double rx_tr = safe_wall( maps.right_wall_map, gmx_y, gmx_z, limit_x ); + double rx_bl = safe_wall( maps.right_wall_map, gu, gz, limit ); + double rx_tl = safe_wall( maps.right_wall_map, gu, gmx_z, limit ); + double rx_br = safe_wall( maps.right_wall_map, gmx_u, gz, limit ); + double rx_tr = safe_wall( maps.right_wall_map, gmx_u, gmx_z, limit ); - bool alt_dir = ( ( gy_index + gz_index ) % 2 ) != 0; + bool alt_dir = ( ( gu_index + gz_index ) % 2 ) != 0; insert_wall_brush( lwall_entity, lx_bl, lx_tl, lx_br, lx_tr, - gy, gmx_y, gz, gmx_z, outer_left, limit_x, top_texture, caulk, true, alt_dir ); + gu, gmx_u, gz, gmx_z, outer_lo, limit, top_texture, caulk, true, alt_dir, axis ); insert_wall_brush( rwall_entity, rx_bl, rx_tl, rx_br, rx_tr, - gy, gmx_y, gz, gmx_z, outer_right, limit_x, top_texture, caulk, false, alt_dir ); + gu, gmx_u, gz, gmx_z, outer_hi, limit, top_texture, caulk, false, alt_dir, axis ); } } diff --git a/contrib/terrain_generator/brush_builder.h b/contrib/terrain_generator/brush_builder.h index 03c880e6..d2fad3d4 100644 --- a/contrib/terrain_generator/brush_builder.h +++ b/contrib/terrain_generator/brush_builder.h @@ -11,4 +11,4 @@ void build_terrain_brushes( const BrushData& target, double step_x, double step_ void build_tunnel_brushes( const BrushData& target, double step_x, double step_y, const TunnelMaps& maps, const char* top_texture, - double cave_height, double slope_height ); + double cave_height, double slope_height, Axis axis ); diff --git a/contrib/terrain_generator/terrain_engine.cpp b/contrib/terrain_generator/terrain_engine.cpp index ab261343..f60b12f6 100644 --- a/contrib/terrain_generator/terrain_engine.cpp +++ b/contrib/terrain_generator/terrain_engine.cpp @@ -67,7 +67,8 @@ void adjust_bounds_to_fit_grid( BrushData& target, double step_x, double step_y HeightMap generate_height_map( const BrushData& target, double step_x, double step_y, ShapeType shape_type, double shape_height, double variance, double frequency, - NoiseType noise_type, double terrace_step ){ + NoiseType noise_type, double terrace_step, + Axis axis ){ HeightMap height_map; double seed_x = random_double() * 10000.0; @@ -78,6 +79,9 @@ HeightMap generate_height_map( const BrushData& target, double step_x, double st double nx = target.width_x > 0 ? ( x - target.min_x ) / target.width_x : 0.0; double ny = target.length_y > 0 ? ( y - target.min_y ) / target.length_y : 0.0; + // Position along the axis Slope / Ridge / Valley run along. + double na = ( axis == Axis::X ) ? nx : ny; + double center_dist = std::min( 1.0, std::sqrt( ( nx - 0.5 ) * ( nx - 0.5 ) + ( ny - 0.5 ) * ( ny - 0.5 ) ) / 0.5 ); @@ -92,12 +96,12 @@ HeightMap generate_height_map( const BrushData& target, double step_x, double st break; } case ShapeType::Ridge: { - double dist = std::min( 1.0, std::abs( nx - 0.5 ) / 0.5 ); + double dist = std::min( 1.0, std::abs( na - 0.5 ) / 0.5 ); base_z = shape_height * 0.5 * ( 1.0 + std::cos( dist * std::numbers::pi ) ); break; } case ShapeType::Slope: { - base_z = shape_height * nx; + base_z = shape_height * na; break; } case ShapeType::Volcano: { @@ -108,7 +112,7 @@ HeightMap generate_height_map( const BrushData& target, double step_x, double st break; } case ShapeType::Valley: { - double dist = std::min( 1.0, std::abs( nx - 0.5 ) / 0.5 ); + double dist = std::min( 1.0, std::abs( na - 0.5 ) / 0.5 ); base_z = shape_height * 0.5 * ( 1.0 - std::cos( dist * std::numbers::pi ) ); break; } @@ -116,6 +120,14 @@ HeightMap generate_height_map( const BrushData& target, double step_x, double st break; } + // Terrace the clean shape height first, before noise is mixed in, so + // terrace bands stay flat strips instead of being broken up by + // per-vertex noise jitter. Noise is then layered on top as surface + // detail on the (possibly stepped) base height. + double shape_z = target.max_z + base_z; + if ( shape_type != ShapeType::Flat && terrace_step > 0.0 ) + shape_z = std::floor( shape_z / terrace_step ) * terrace_step; + double noise_z = 0.0; if ( variance > 0.0 ) { if ( noise_type == NoiseType::Random ) { @@ -127,9 +139,7 @@ HeightMap generate_height_map( const BrushData& target, double step_x, double st } } - double final_z = target.max_z + base_z + noise_z; - if ( shape_type != ShapeType::Flat && terrace_step > 0.0 ) - final_z = std::floor( final_z / terrace_step ) * terrace_step; + double final_z = shape_z + noise_z; height_map[{ round2( x ), round2( y ) }] = std::round( final_z ); } @@ -145,7 +155,8 @@ HeightMap generate_height_map( const BrushData& target, double step_x, double st TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, double step_y, double cave_height, double slope_height, double variance, double frequency, - NoiseType noise_type, double terrace_step ){ + NoiseType noise_type, double terrace_step, + Axis axis ){ TunnelMaps result; double seed_floor_x = random_double() * 10000.0; @@ -155,15 +166,37 @@ TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, double seed_wall_l = random_double() * 10000.0; double seed_wall_r = random_double() * 10000.0; - double center_x = ( target.min_x + target.max_x ) / 2.0; - double half_width = target.width_x / 2.0; + // Cross-section (rounding) axis: X for a Y-running tunnel, Y for an + // X-running tunnel. Reproduces the original formulas exactly when axis == Y. + const bool along_y = ( axis == Axis::Y ); + const double center_cs = along_y ? ( target.min_x + target.max_x ) / 2.0 + : ( target.min_y + target.max_y ) / 2.0; + const double half_cs = along_y ? target.width_x / 2.0 + : target.length_y / 2.0; // Floor and ceiling for ( double x = target.min_x; x <= target.max_x + 0.01; x += step_x ) { for ( double y = target.min_y; y <= target.max_y + 0.01; y += step_y ) { - double t = half_width > 0 ? std::min( 1.0, std::abs( x - center_x ) / half_width ) : 0.0; + double cs_pos = along_y ? x : y; + double t = half_cs > 0 ? std::min( 1.0, std::abs( cs_pos - center_cs ) / half_cs ) : 0.0; double blend = 1.0 - std::sqrt( std::max( 0.0, 1.0 - t * t ) ); + double along_pos = along_y + ? ( target.length_y > 0 ? ( y - target.min_y ) / target.length_y : 0.0 ) + : ( target.width_x > 0 ? ( x - target.min_x ) / target.width_x : 0.0 ); + double base_z = target.max_z + slope_height * along_pos; + double floor_base = base_z + blend * ( cave_height * 0.25 ); + double ceil_base = base_z + cave_height - blend * ( cave_height * 0.25 ); + + // Terrace the clean floor/ceiling first, before noise is mixed in, so + // terrace bands stay flat strips instead of being broken up by + // per-vertex noise jitter (see the standard-terrain heightmap for the + // same reasoning). + if ( terrace_step > 0.0 ) { + floor_base = std::floor( floor_base / terrace_step ) * terrace_step; + ceil_base = std::ceil( ceil_base / terrace_step ) * terrace_step; + } + double floor_noise = 0.0, ceil_noise = 0.0; if ( variance > 0.0 ) { if ( noise_type == NoiseType::Random ) { @@ -177,10 +210,8 @@ TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, } } - double ny = target.length_y > 0 ? ( y - target.min_y ) / target.length_y : 0.0; - double base_z = target.max_z + slope_height * ny; - double floor_z = base_z + blend * ( cave_height * 0.25 ) + floor_noise; - double ceil_z = base_z + cave_height - blend * ( cave_height * 0.25 ) - ceil_noise; + double floor_z = floor_base + floor_noise; + double ceil_z = ceil_base - ceil_noise; if ( floor_z > ceil_z ) { double mid = ( floor_z + ceil_z ) / 2.0; @@ -188,11 +219,6 @@ TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, ceil_z = mid; } - if ( terrace_step > 0.0 ) { - floor_z = std::floor( floor_z / terrace_step ) * terrace_step; - ceil_z = std::ceil( ceil_z / terrace_step ) * terrace_step; - } - result.floor_map[ { round2( x ), round2( y ) }] = std::round( floor_z ); result.ceiling_map[ { round2( x ), round2( y ) }] = std::round( ceil_z ); } @@ -207,10 +233,18 @@ TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, double wall_max_z = wall_min_z + total_wall_height; result.step_z = step_z; - // Left and right walls — grid over (Y, Z) - for ( double y = target.min_y; y <= target.max_y + 0.01; y += step_y ) { + // Walls — grid over (along-tunnel coordinate, Z). For a Y-running tunnel + // this is (Y, Z) with walls at fixed X (left/right); for an X-running + // tunnel it's (X, Z) with walls at fixed Y (near/far). + double along_min = along_y ? target.min_y : target.min_x; + double along_max = along_y ? target.max_y : target.max_x; + double along_step = along_y ? step_y : step_x; + double wall_lo = along_y ? target.min_x : target.min_y; + double wall_hi = along_y ? target.max_x : target.max_y; + + for ( double u = along_min; u <= along_max + 0.01; u += along_step ) { for ( double z = wall_min_z; z <= wall_max_z + 0.01; z += step_z ) { - double ry = round2( y ); + double ru = round2( u ); double rz = round2( z ); double wall_noise = 0.0; @@ -219,10 +253,10 @@ TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, wall_noise = random_double() * variance; } else { wall_noise = std::abs( sample_noise( noise_type, - ( y + seed_wall_l ) * frequency, ( z + seed_wall_l ) * frequency ) ) * variance; + ( u + seed_wall_l ) * frequency, ( z + seed_wall_l ) * frequency ) ) * variance; } } - result.left_wall_map[{ ry, rz }] = std::round( target.min_x + wall_noise ); + result.left_wall_map[{ ru, rz }] = std::round( wall_lo + wall_noise ); wall_noise = 0.0; if ( variance > 0.0 ) { @@ -230,10 +264,10 @@ TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, wall_noise = random_double() * variance; } else { wall_noise = std::abs( sample_noise( noise_type, - ( y + seed_wall_r ) * frequency, ( z + seed_wall_r ) * frequency ) ) * variance; + ( u + seed_wall_r ) * frequency, ( z + seed_wall_r ) * frequency ) ) * variance; } } - result.right_wall_map[{ ry, rz }] = std::round( target.max_x - wall_noise ); + result.right_wall_map[{ ru, rz }] = std::round( wall_hi - wall_noise ); } } diff --git a/contrib/terrain_generator/terrain_engine.h b/contrib/terrain_generator/terrain_engine.h index 856f9d67..3a9161ab 100644 --- a/contrib/terrain_generator/terrain_engine.h +++ b/contrib/terrain_generator/terrain_engine.h @@ -36,6 +36,13 @@ enum class NoiseType { Random = 2 }; +// Direction Slope / Ridge / Valley run along. Ignored by radially symmetric +// shapes (Hill, Crater, Volcano) and by Tunnel/SlopeTunnel, which run along Y. +enum class Axis { + X = 0, + Y = 1 +}; + BrushData make_manual_brush_data( double width, double length, double height ); void adjust_bounds_to_fit_grid( BrushData& target, double step_x, double step_y ); @@ -43,9 +50,11 @@ void adjust_bounds_to_fit_grid( BrushData& target, double step_x, double step_y HeightMap generate_height_map( const BrushData& target, double step_x, double step_y, ShapeType shape_type, double shape_height, double variance, double frequency, - NoiseType noise_type, double terrace_step ); + NoiseType noise_type, double terrace_step, + Axis axis ); TunnelMaps generate_tunnel_height_maps( const BrushData& target, double step_x, double step_y, double cave_height, double slope_height, double variance, double frequency, - NoiseType noise_type, double terrace_step ); + NoiseType noise_type, double terrace_step, + Axis axis ); diff --git a/contrib/terrain_generator/terrain_generator.cpp b/contrib/terrain_generator/terrain_generator.cpp index eb00143a..41202a07 100644 --- a/contrib/terrain_generator/terrain_generator.cpp +++ b/contrib/terrain_generator/terrain_generator.cpp @@ -140,14 +140,28 @@ void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush form->addRow( "Length (Y):", sel_l_label ); form->addRow( "Height (Z):", sel_h_label ); - // Manual size inputs (shown in Manual Size mode) - auto *manual_w_spin = new SpinBox( 64, 131072, 1024, 0, 64 ); - auto *manual_l_spin = new SpinBox( 64, 131072, 1024, 0, 64 ); - auto *manual_h_spin = new SpinBox( 64, 131072, 64, 0, 64 ); + // Manual size inputs (shown in Manual Size mode). Remembers the last + // values used across dialog open/close within this editor session, so + // repeated manual-size generation doesn't reset to hardcoded defaults + // every time the dialog is reopened. + constexpr double DEFAULT_MANUAL_W = 1024.0, DEFAULT_MANUAL_L = 1024.0, DEFAULT_MANUAL_H = 64.0; + static double s_manual_w = DEFAULT_MANUAL_W, s_manual_l = DEFAULT_MANUAL_L, s_manual_h = DEFAULT_MANUAL_H; + auto *manual_w_spin = new SpinBox( 64, 131072, s_manual_w, 0, 64 ); + auto *manual_l_spin = new SpinBox( 64, 131072, s_manual_l, 0, 64 ); + auto *manual_h_spin = new SpinBox( 64, 131072, s_manual_h, 0, 64 ); form->addRow( "Width (X):", manual_w_spin ); form->addRow( "Length (Y):", manual_l_spin ); form->addRow( "Height (Z):", manual_h_spin ); + // Reset the remembered manual size back to the original hardcoded default. + auto *manual_reset_btn = new QPushButton( "Reset to Default" ); + QObject::connect( manual_reset_btn, &QPushButton::clicked, [&](){ + manual_w_spin->setValue( DEFAULT_MANUAL_W ); + manual_l_spin->setValue( DEFAULT_MANUAL_L ); + manual_h_spin->setValue( DEFAULT_MANUAL_H ); + } ); + form->addRow( "", manual_reset_btn ); + // Sub-square size: preset combo + Advanced checkbox in one row auto *sq_widget = new QWidget; auto *sq_hbox = new QHBoxLayout( sq_widget ); @@ -181,6 +195,16 @@ void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush shape_combo->setCurrentIndex( 0 ); // Flat form->addRow( "Base Shape:", shape_combo ); + // Axis — meaningful for the directional shapes (Slope, Ridge, Valley) and + // for tunnels (which way the tunnel runs). Radially symmetric shapes + // (Hill, Crater, Volcano) ignore it. Default differs per shape (see + // update_shape below) to match each shape's original hardcoded axis. + auto *axis_combo = new ComboBox; + axis_combo->addItem( "X Axis", (int)Axis::X ); + axis_combo->addItem( "Y Axis", (int)Axis::Y ); + axis_combo->setCurrentIndex( 0 ); + form->addRow( "Axis:", axis_combo ); + // Shape height — editable spinbox (manual mode or non-slope shapes). // For Slope / Slope Tunnel in Use Selection mode, replaced by a // read-only label derived from the selection brush's Z extent. @@ -369,6 +393,7 @@ void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush const double step_x = advanced ? step_x_spin->value() : sq_combo->currentData().toInt(); const double step_y = advanced ? step_y_spin->value() : sq_combo->currentData().toInt(); const ShapeType shape = (ShapeType)shape_combo->currentData().toInt(); + const Axis axis = (Axis)axis_combo->currentData().toInt(); const NoiseType noise = (NoiseType)noise_combo->currentData().toInt(); const double tun_height = tunnel_height_spin->value(); const double variance = variance_spin->value(); @@ -507,12 +532,12 @@ void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush const double cave_height = ( shape == ShapeType::SlopeTunnel ) ? tun_height : shape_height; const double slope_height = ( shape == ShapeType::SlopeTunnel ) ? shape_height : 0; const double tunnel_terrace = ( shape == ShapeType::SlopeTunnel ) ? terrace : 0.0; - auto maps = generate_tunnel_height_maps( target, step_x, step_y, cave_height, slope_height, variance, frequency, noise, tunnel_terrace ); - build_tunnel_brushes( target, step_x, step_y, maps, texture, cave_height, slope_height ); + auto maps = generate_tunnel_height_maps( target, step_x, step_y, cave_height, slope_height, variance, frequency, noise, tunnel_terrace, axis ); + build_tunnel_brushes( target, step_x, step_y, maps, texture, cave_height, slope_height, axis ); } else { bool split_diagonally = ( variance > 0 || shape != ShapeType::Flat ); - auto height_map = generate_height_map( target, step_x, step_y, shape, shape_height, variance, frequency, noise, terrace ); + auto height_map = generate_height_map( target, step_x, step_y, shape, shape_height, variance, frequency, noise, terrace, axis ); build_terrain_brushes( target, step_x, step_y, height_map, texture, split_diagonally ); } @@ -576,9 +601,10 @@ void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush set_row_visible( sel_w_label, use_sel ); set_row_visible( sel_l_label, use_sel ); set_row_visible( sel_h_label, use_sel ); - set_row_visible( manual_w_spin, !use_sel ); - set_row_visible( manual_l_spin, !use_sel ); - set_row_visible( manual_h_spin, !use_sel ); + set_row_visible( manual_w_spin, !use_sel ); + set_row_visible( manual_l_spin, !use_sel ); + set_row_visible( manual_h_spin, !use_sel ); + set_row_visible( manual_reset_btn, !use_sel ); }; // Advanced sub-square toggle @@ -593,7 +619,17 @@ void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush const ShapeType st = (ShapeType)shape_combo->itemData( idx ).toInt(); const bool is_flat = ( st == ShapeType::Flat ); const bool is_slope_tunnel = ( st == ShapeType::SlopeTunnel ); + const bool is_tunnel_shape = ( st == ShapeType::Tunnel || st == ShapeType::SlopeTunnel ); + const bool has_axis = ( st == ShapeType::Slope || st == ShapeType::Ridge || st == ShapeType::Valley + || is_tunnel_shape ); + set_row_visible( axis_combo, has_axis ); + if ( has_axis ) { + // Match each shape's original hardcoded axis: Slope/Ridge/Valley + // ran along X, tunnels ran along Y. Re-applied on every shape + // switch, same as the slope-height auto-fill below. + axis_combo->setCurrentIndex( is_tunnel_shape ? (int)Axis::Y : (int)Axis::X ); + } set_row_visible( shape_height_spin, !is_flat ); if ( !is_flat ) { if ( auto *lbl = qobject_cast( form->labelForField( shape_height_spin ) ) ) @@ -640,6 +676,9 @@ void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush QObject::connect( &dialog, &QDialog::finished, &loop, &QEventLoop::quit ); loop.exec(); } + s_manual_w = manual_w_spin->value(); + s_manual_l = manual_l_spin->value(); + s_manual_h = manual_h_spin->value(); s_active_dialog = nullptr; } }