fix(galaxy): anchor spiral arms to core cluster
Structural fix for detached disk arms. The core problem was that disk systems generated with inner_radius=0 tried to place near the origin, but spacing constraints blocked them (core already occupied that space), creating a visible gap between the core and spiral arms. Changes: - Pass core_radius to generate_disk so arms know where the core boundary is - When inner_radius is 0 (auto), start arms inside the core zone at 40% of core_radius, so the density peak falls inside the core sphere and arms visually emerge from the core boundary - Change density exponent from powf(0.62) to powf(0.45) for stronger concentration near the inner edge (~60% of systems in inner 30% of the radial span), creating a natural bright-core-to-faint-edge falloff - Bias randomize_disk toward inner_radius=0 (auto) 70% of the time so randomized galaxies always have core-anchored arms
This commit is contained in:
@@ -221,6 +221,7 @@ 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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,10 +310,13 @@ fn generate_core(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate one disk layer of spiral arms. Faithful to the original
|
/// Generate one disk layer of spiral arms. Arms are anchored to the core
|
||||||
/// generator's density bias (`pow(0.62)` → packed near origin) and arm twist.
|
/// cluster: when `inner_radius` is 0 (auto), systems start inside the core
|
||||||
/// Supports tilt (rotation around X, capped at 45°), Y-axis rotation offset,
|
/// zone so arms visually emerge from the core boundary rather than appearing
|
||||||
/// and independent inner/outer radii.
|
/// detached. The density bias (`pow(0.45)` → strongly packed near inner edge)
|
||||||
|
/// produces a natural falloff from bright core-proximal arms to sparse outer
|
||||||
|
/// 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,
|
||||||
@@ -321,6 +325,7 @@ 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.
|
// Effective outer radius: explicit value if set, else galaxy size.
|
||||||
@@ -329,7 +334,16 @@ fn generate_disk(
|
|||||||
} else {
|
} else {
|
||||||
galaxy_size
|
galaxy_size
|
||||||
};
|
};
|
||||||
let inner = disk.inner_radius;
|
// 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);
|
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.
|
||||||
@@ -354,7 +368,10 @@ 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 {
|
||||||
let r = inner + rng.gen::<f32>().powf(0.62) * span;
|
// powf(0.45) concentrates ~60% of systems in the inner 30% of the
|
||||||
|
// 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 - inner) / span) * disk.twist
|
||||||
+ (rng.gen::<f32>() - 0.5) * arm_scatter(arm_count);
|
+ (rng.gen::<f32>() - 0.5) * arm_scatter(arm_count);
|
||||||
|
|||||||
@@ -1334,9 +1334,15 @@ 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 = rng.gen_range(DISK_INNER_RADIUS_MIN..=DISK_INNER_RADIUS_MAX);
|
||||||
disk.inner_radius =
|
disk.inner_radius =
|
||||||
(disk.inner_radius / DISK_INNER_RADIUS_STEP).round() * DISK_INNER_RADIUS_STEP;
|
(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 = rng.gen_range(DISK_OUTER_RADIUS_MIN..=DISK_OUTER_RADIUS_MAX);
|
||||||
disk.outer_radius =
|
disk.outer_radius =
|
||||||
(disk.outer_radius / DISK_OUTER_RADIUS_STEP).round() * DISK_OUTER_RADIUS_STEP;
|
(disk.outer_radius / DISK_OUTER_RADIUS_STEP).round() * DISK_OUTER_RADIUS_STEP;
|
||||||
|
|||||||
Reference in New Issue
Block a user