fix(galaxy): remove inner/outer radius from disks, restore original spiral sweep
The inner_radius/outer_radius parameters were added for multi-disk layer support but broke the spiral effect: they introduced a gap between the core and disk arms that made the galaxy look like detached floating disks. Reverted to the original generation formula: r = powf(random, 0.62) * galaxy_size angle = arm_base + (r / galaxy_size) * twist + scatter This sweeps systems continuously from origin to galaxy edge with density packed near the core — arms flow naturally from the dense center outward. Removed: - inner_radius, outer_radius fields from DiskParams - DISK_INNER/OUTER_RADIUS_* constants from params.rs - 4 ParamButton variants and their handlers - UI param rows for inner/outer radius - randomize_disk radius logic
This commit is contained in:
@@ -221,7 +221,6 @@ fn generate_system_positions(params: &GalaxyParams, rng: &mut StdRng) -> Vec<Gen
|
|||||||
base_spacing,
|
base_spacing,
|
||||||
&mut next_index,
|
&mut next_index,
|
||||||
disk_index,
|
disk_index,
|
||||||
params.core.radius,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,13 +309,11 @@ fn generate_core(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate one disk layer of spiral arms. Arms are anchored to the core
|
/// Generate one disk layer of spiral arms. Systems sweep continuously from
|
||||||
/// cluster: when `inner_radius` is 0 (auto), systems start inside the core
|
/// the origin to `galaxy_size` with a `powf(0.62)` density bias packing them
|
||||||
/// zone so arms visually emerge from the core boundary rather than appearing
|
/// near the core — no inner/outer radius gap, so arms flow naturally from the
|
||||||
/// detached. The density bias (`pow(0.45)` → strongly packed near inner edge)
|
/// dense center outward. Supports tilt (rotation around X, capped at 45°) and
|
||||||
/// produces a natural falloff from bright core-proximal arms to sparse outer
|
/// Y-axis rotation offset.
|
||||||
/// regions. Supports tilt (rotation around X, capped at 45°), Y-axis rotation
|
|
||||||
/// offset, and independent inner/outer radii.
|
|
||||||
fn generate_disk(
|
fn generate_disk(
|
||||||
systems: &mut Vec<GeneratedSystem>,
|
systems: &mut Vec<GeneratedSystem>,
|
||||||
rng: &mut StdRng,
|
rng: &mut StdRng,
|
||||||
@@ -325,26 +322,8 @@ fn generate_disk(
|
|||||||
base_spacing: f32,
|
base_spacing: f32,
|
||||||
next_index: &mut usize,
|
next_index: &mut usize,
|
||||||
disk_index: usize,
|
disk_index: usize,
|
||||||
core_radius: f32,
|
|
||||||
) {
|
) {
|
||||||
let arm_count = disk.arms.max(1);
|
let arm_count = disk.arms.max(1);
|
||||||
// Effective outer radius: explicit value if set, else galaxy size.
|
|
||||||
let outer = if disk.outer_radius > 0.0 {
|
|
||||||
disk.outer_radius
|
|
||||||
} else {
|
|
||||||
galaxy_size
|
|
||||||
};
|
|
||||||
// Anchor disk arms to the core: when inner_radius is 0 (auto), start
|
|
||||||
// systems inside the core zone so arms visually emerge from the core
|
|
||||||
// boundary rather than appearing detached. The 0.4 factor places the
|
|
||||||
// density peak well inside the core sphere, producing overlap that
|
|
||||||
// creates a seamless core-to-arm transition.
|
|
||||||
let inner = if disk.inner_radius > 0.0 {
|
|
||||||
disk.inner_radius
|
|
||||||
} else {
|
|
||||||
core_radius * 0.4
|
|
||||||
};
|
|
||||||
let span = (outer - inner).max(1.0);
|
|
||||||
|
|
||||||
// Pre-compute the rotation quaternion for this disk's tilt + Y rotation.
|
// Pre-compute the rotation quaternion for this disk's tilt + Y rotation.
|
||||||
// Tilt is applied first (around X), then the Y rotation offset.
|
// Tilt is applied first (around X), then the Y rotation offset.
|
||||||
@@ -368,12 +347,9 @@ fn generate_disk(
|
|||||||
let mut position = Option::<Vec3>::None;
|
let mut position = Option::<Vec3>::None;
|
||||||
let mut final_radius = 0.0f32;
|
let mut final_radius = 0.0f32;
|
||||||
for attempt in 0..SPACING_ATTEMPTS {
|
for attempt in 0..SPACING_ATTEMPTS {
|
||||||
// powf(0.45) concentrates ~60% of systems in the inner 30% of the
|
let r = rng.gen::<f32>().powf(0.62) * galaxy_size;
|
||||||
// radial span, ensuring arms are dense near the core boundary and
|
|
||||||
// fade naturally toward the edge.
|
|
||||||
let r = inner + rng.gen::<f32>().powf(0.45) * span;
|
|
||||||
let angle = std::f32::consts::TAU * arm as f32 / arm_count as f32
|
let angle = std::f32::consts::TAU * arm as f32 / arm_count as f32
|
||||||
+ ((r - inner) / span) * disk.twist
|
+ (r / galaxy_size) * disk.twist
|
||||||
+ (rng.gen::<f32>() - 0.5) * arm_scatter(arm_count);
|
+ (rng.gen::<f32>() - 0.5) * arm_scatter(arm_count);
|
||||||
let y = (rng.gen::<f32>() - 0.5) * 20.0;
|
let y = (rng.gen::<f32>() - 0.5) * 20.0;
|
||||||
let mut candidate = Vec3::new(angle.cos() * r, y, angle.sin() * r);
|
let mut candidate = Vec3::new(angle.cos() * r, y, angle.sin() * r);
|
||||||
|
|||||||
@@ -81,13 +81,6 @@ pub const DISK_ROTATION_MAX: f32 = std::f32::consts::TAU;
|
|||||||
/// 30° steps.
|
/// 30° steps.
|
||||||
pub const DISK_ROTATION_STEP: f32 = std::f32::consts::TAU / 12.0;
|
pub const DISK_ROTATION_STEP: f32 = std::f32::consts::TAU / 12.0;
|
||||||
|
|
||||||
pub const DISK_INNER_RADIUS_MIN: f32 = 0.0;
|
|
||||||
pub const DISK_INNER_RADIUS_MAX: f32 = 400.0;
|
|
||||||
pub const DISK_INNER_RADIUS_STEP: f32 = 10.0;
|
|
||||||
pub const DISK_OUTER_RADIUS_MIN: f32 = 40.0;
|
|
||||||
pub const DISK_OUTER_RADIUS_MAX: f32 = 420.0;
|
|
||||||
pub const DISK_OUTER_RADIUS_STEP: f32 = 10.0;
|
|
||||||
|
|
||||||
pub const BEAM_COUNT_MIN: usize = 0;
|
pub const BEAM_COUNT_MIN: usize = 0;
|
||||||
pub const BEAM_COUNT_MAX: usize = 120;
|
pub const BEAM_COUNT_MAX: usize = 120;
|
||||||
pub const BEAM_COUNT_STEP: usize = 4;
|
pub const BEAM_COUNT_STEP: usize = 4;
|
||||||
@@ -139,11 +132,12 @@ impl Default for CoreParams {
|
|||||||
|
|
||||||
/// One independent disk layer of spiral arms.
|
/// One independent disk layer of spiral arms.
|
||||||
///
|
///
|
||||||
/// Disks are generated in the XZ plane and then tilted by [`Self::tilt`]
|
/// Disks are generated in the XZ plane with systems sweeping continuously
|
||||||
/// (rotation around the X axis) and rotated by [`Self::rotation_offset`]
|
/// from the origin to `galaxy_size` (no inner/outer radius gap), then
|
||||||
/// (around the Y axis). Systems are placed between [`Self::inner_radius`]
|
/// tilted by [`Self::tilt`] (rotation around X) and rotated by
|
||||||
/// and [`Self::outer_radius`]; if `outer_radius` is 0 the galaxy-wide
|
/// [`Self::rotation_offset`] (around Y). The `powf(0.62)` density bias
|
||||||
/// [`GalaxyParams::size`] is used instead.
|
/// packs systems near the origin so arms flow naturally from the dense
|
||||||
|
/// core cluster outward.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DiskParams {
|
pub struct DiskParams {
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
@@ -157,11 +151,6 @@ pub struct DiskParams {
|
|||||||
/// Rotates the disk in the horizontal plane, determining where the
|
/// Rotates the disk in the horizontal plane, determining where the
|
||||||
/// tilted disk's "high side" points.
|
/// tilted disk's "high side" points.
|
||||||
pub rotation_offset: f32,
|
pub rotation_offset: f32,
|
||||||
/// Minimum distance from the origin for systems in this disk.
|
|
||||||
/// 0 = systems can start at the origin (overlapping the core).
|
|
||||||
pub inner_radius: f32,
|
|
||||||
/// Maximum distance from the origin. 0 = use [`GalaxyParams::size`].
|
|
||||||
pub outer_radius: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DiskParams {
|
impl Default for DiskParams {
|
||||||
@@ -172,8 +161,6 @@ impl Default for DiskParams {
|
|||||||
twist: DEFAULT_DISK_TWIST,
|
twist: DEFAULT_DISK_TWIST,
|
||||||
tilt: 0.0,
|
tilt: 0.0,
|
||||||
rotation_offset: 0.0,
|
rotation_offset: 0.0,
|
||||||
inner_radius: 0.0,
|
|
||||||
outer_radius: 0.0, // means "use galaxy size"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,10 +61,6 @@ pub enum ParamButton {
|
|||||||
DiskTiltIncr,
|
DiskTiltIncr,
|
||||||
DiskRotationDecr,
|
DiskRotationDecr,
|
||||||
DiskRotationIncr,
|
DiskRotationIncr,
|
||||||
DiskInnerRadiusDecr,
|
|
||||||
DiskInnerRadiusIncr,
|
|
||||||
DiskOuterRadiusDecr,
|
|
||||||
DiskOuterRadiusIncr,
|
|
||||||
// Top beam (+Y)
|
// Top beam (+Y)
|
||||||
BeamTopEnabled,
|
BeamTopEnabled,
|
||||||
BeamTopThicknessDecr,
|
BeamTopThicknessDecr,
|
||||||
@@ -585,30 +581,7 @@ pub fn param_button_handler(
|
|||||||
.min(DISK_ROTATION_MAX);
|
.min(DISK_ROTATION_MAX);
|
||||||
params.bump_generation();
|
params.bump_generation();
|
||||||
}
|
}
|
||||||
ParamButton::DiskInnerRadiusDecr => {
|
|
||||||
let disk = selected_disk_mut(&mut params, selected_disk.0);
|
|
||||||
disk.inner_radius =
|
|
||||||
(disk.inner_radius - DISK_INNER_RADIUS_STEP).max(DISK_INNER_RADIUS_MIN);
|
|
||||||
params.bump_generation();
|
|
||||||
}
|
|
||||||
ParamButton::DiskInnerRadiusIncr => {
|
|
||||||
let disk = selected_disk_mut(&mut params, selected_disk.0);
|
|
||||||
disk.inner_radius =
|
|
||||||
(disk.inner_radius + DISK_INNER_RADIUS_STEP).min(DISK_INNER_RADIUS_MAX);
|
|
||||||
params.bump_generation();
|
|
||||||
}
|
|
||||||
ParamButton::DiskOuterRadiusDecr => {
|
|
||||||
let disk = selected_disk_mut(&mut params, selected_disk.0);
|
|
||||||
disk.outer_radius =
|
|
||||||
(disk.outer_radius - DISK_OUTER_RADIUS_STEP).max(DISK_OUTER_RADIUS_MIN);
|
|
||||||
params.bump_generation();
|
|
||||||
}
|
|
||||||
ParamButton::DiskOuterRadiusIncr => {
|
|
||||||
let disk = selected_disk_mut(&mut params, selected_disk.0);
|
|
||||||
disk.outer_radius =
|
|
||||||
(disk.outer_radius + DISK_OUTER_RADIUS_STEP).min(DISK_OUTER_RADIUS_MAX);
|
|
||||||
params.bump_generation();
|
|
||||||
}
|
|
||||||
// ── Top Beam (+Y) ─────────────────────────────────────────────
|
// ── Top Beam (+Y) ─────────────────────────────────────────────
|
||||||
ParamButton::BeamTopEnabled => {
|
ParamButton::BeamTopEnabled => {
|
||||||
params.beam_top.enabled = !params.beam_top.enabled;
|
params.beam_top.enabled = !params.beam_top.enabled;
|
||||||
@@ -814,20 +787,7 @@ pub fn refresh_control_panel_values(
|
|||||||
"disk_twist" => format!("{:.1}", disk.twist),
|
"disk_twist" => format!("{:.1}", disk.twist),
|
||||||
"disk_tilt" => format!("{:.0}°", disk.tilt.to_degrees()),
|
"disk_tilt" => format!("{:.0}°", disk.tilt.to_degrees()),
|
||||||
"disk_rotation" => format!("{:.0}°", disk.rotation_offset.to_degrees()),
|
"disk_rotation" => format!("{:.0}°", disk.rotation_offset.to_degrees()),
|
||||||
"disk_inner_radius" => {
|
|
||||||
if disk.inner_radius > 0.0 {
|
|
||||||
format!("{:.0}", disk.inner_radius)
|
|
||||||
} else {
|
|
||||||
"0".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"disk_outer_radius" => {
|
|
||||||
if disk.outer_radius > 0.0 {
|
|
||||||
format!("{:.0}", disk.outer_radius)
|
|
||||||
} else {
|
|
||||||
"auto".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Top beam
|
// Top beam
|
||||||
"beam_top_enabled" => {
|
"beam_top_enabled" => {
|
||||||
format!(
|
format!(
|
||||||
@@ -1092,21 +1052,6 @@ fn spawn_scroll_contents_with_tabs(
|
|||||||
ParamButton::DiskRotationDecr,
|
ParamButton::DiskRotationDecr,
|
||||||
ParamButton::DiskRotationIncr,
|
ParamButton::DiskRotationIncr,
|
||||||
);
|
);
|
||||||
spawn_param_row(
|
|
||||||
scroll,
|
|
||||||
"Inner Rad",
|
|
||||||
"disk_inner_radius",
|
|
||||||
ParamButton::DiskInnerRadiusDecr,
|
|
||||||
ParamButton::DiskInnerRadiusIncr,
|
|
||||||
);
|
|
||||||
spawn_param_row(
|
|
||||||
scroll,
|
|
||||||
"Outer Rad",
|
|
||||||
"disk_outer_radius",
|
|
||||||
ParamButton::DiskOuterRadiusDecr,
|
|
||||||
ParamButton::DiskOuterRadiusIncr,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove button — only show if more than one disk.
|
// Remove button — only show if more than one disk.
|
||||||
if params.disks.len() > 1 {
|
if params.disks.len() > 1 {
|
||||||
scroll
|
scroll
|
||||||
@@ -1334,22 +1279,6 @@ fn randomize_disk(rng: &mut impl rand::Rng) -> DiskParams {
|
|||||||
disk.rotation_offset = rng.gen_range(DISK_ROTATION_MIN..=DISK_ROTATION_MAX);
|
disk.rotation_offset = rng.gen_range(DISK_ROTATION_MIN..=DISK_ROTATION_MAX);
|
||||||
disk.rotation_offset =
|
disk.rotation_offset =
|
||||||
(disk.rotation_offset / DISK_ROTATION_STEP).round() * DISK_ROTATION_STEP;
|
(disk.rotation_offset / DISK_ROTATION_STEP).round() * DISK_ROTATION_STEP;
|
||||||
// Bias inner_radius toward 0 (auto) ~70% of the time so randomized disks
|
|
||||||
// are always anchored to the core by default.
|
|
||||||
if rng.gen_bool(0.7) {
|
|
||||||
disk.inner_radius = 0.0;
|
|
||||||
} else {
|
|
||||||
disk.inner_radius = rng.gen_range(DISK_INNER_RADIUS_MIN..=DISK_INNER_RADIUS_MAX);
|
|
||||||
disk.inner_radius =
|
|
||||||
(disk.inner_radius / DISK_INNER_RADIUS_STEP).round() * DISK_INNER_RADIUS_STEP;
|
|
||||||
}
|
|
||||||
disk.outer_radius = rng.gen_range(DISK_OUTER_RADIUS_MIN..=DISK_OUTER_RADIUS_MAX);
|
|
||||||
disk.outer_radius =
|
|
||||||
(disk.outer_radius / DISK_OUTER_RADIUS_STEP).round() * DISK_OUTER_RADIUS_STEP;
|
|
||||||
// Allow 0 (auto) ~60% of the time so disks fill the galaxy by default.
|
|
||||||
if rng.gen_bool(0.6) {
|
|
||||||
disk.outer_radius = 0.0;
|
|
||||||
}
|
|
||||||
disk
|
disk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user