Render asteroid belts as instanced jittered icospheres
Replace the flat-grey torus asteroid belt with a swarm of 60 individual rocks per belt — one shared jittered-icosphere mesh asset scaled, rotated, and palette-tinted per instance (5-entry greyscale/brown material set). - New GeneratedAsteroid data (position/scale/rotation/material_index) generated deterministically alongside the belt, preserving the same-seed-same-galaxy invariant - New build_asteroid_mesh + asteroid_material_palette helpers - ContentAssets drops belt_material, adds asteroid_mesh + asteroid_materials - spawn_system_contents signature simplified (no more &mut Assets<Mesh>) - Belt entity is now a no-mesh parent (Mineable/Identifiable/...) with rock meshes as children — sets the stage for per-asteroid mining - Bundles the POI component/marker scaffolding (poi.rs) and per-system contents generator (contents.rs: planets, stations, anomalies, stargates, gas clouds) that the asteroid rendering sits on top of - Tests: asteroid_generation_is_deterministic, asteroid_positions_stay_inside_annulus
This commit is contained in:
@@ -6,10 +6,14 @@
|
||||
//! live in [`super::ui`].
|
||||
|
||||
mod axes;
|
||||
mod contents;
|
||||
mod params;
|
||||
mod poi;
|
||||
mod selection;
|
||||
mod ui;
|
||||
|
||||
pub use poi::*;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::{Rng, SeedableRng};
|
||||
@@ -17,6 +21,7 @@ use rand::{Rng, SeedableRng};
|
||||
use crate::camera::apply_orbit_reset;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub use contents::{SystemContents, SystemContext, SystemSummary};
|
||||
pub use params::{GalaxyParams, SelectedStar};
|
||||
use params::{CORE_COUNT, NEAREST_NEIGHBOR_CONNECTIONS, SPACING_ATTEMPTS};
|
||||
|
||||
@@ -26,8 +31,14 @@ impl Plugin for GalaxyCreationPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<GalaxyParams>()
|
||||
.init_resource::<SelectedStar>()
|
||||
.add_systems(OnEnter(AppState::GalaxyCreation), (setup_galaxy_scene, ui::setup_galaxy_ui))
|
||||
.add_systems(OnExit(AppState::GalaxyCreation), (despawn_galaxy_creation, reset_selection))
|
||||
.add_systems(
|
||||
OnEnter(AppState::GalaxyCreation),
|
||||
(setup_galaxy_scene, ui::setup_galaxy_ui),
|
||||
)
|
||||
.add_systems(
|
||||
OnExit(AppState::GalaxyCreation),
|
||||
(despawn_galaxy_creation, reset_selection),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
@@ -59,15 +70,35 @@ pub struct GalaxyCreationSpawned;
|
||||
#[derive(Component)]
|
||||
pub struct GalaxyScene;
|
||||
|
||||
/// A star system in the procedural galaxy.
|
||||
/// A star system in the procedural galaxy. Acts as a parent entity for the
|
||||
/// system's [`Star`] (visual marker) and all POI children (planets, belts,
|
||||
/// stations, anomalies, stargates, gas clouds).
|
||||
#[derive(Component, Debug)]
|
||||
pub struct StarSystem {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub faction: &'static str,
|
||||
pub security: f32,
|
||||
/// Cached POI count for quick display in the info panel without walking
|
||||
/// the child hierarchy. Updated whenever [`contents::SystemContents`] is
|
||||
/// regenerated.
|
||||
pub poi_count: usize,
|
||||
}
|
||||
|
||||
/// The anchor celestial of a star system — its primary star. Distinct from
|
||||
/// POIs (which are destinations within a system): a star is the body the
|
||||
/// system is named after and the parent that planets/moons/belts orbit.
|
||||
///
|
||||
/// Requires components that describe its physical nature; mass-locks nearby
|
||||
/// warp drives, emits light (used to compute habitable zones for orbiting
|
||||
/// planets).
|
||||
#[derive(Component, Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(Massive, Luminosity, MassLock, BoundingVolume, Identifiable)]
|
||||
pub struct Star;
|
||||
|
||||
// POI components, markers, and events live in [`poi`] (re-exported as `pub use`).
|
||||
|
||||
// ── Faction palette (matches docs GalaxyScene) ──────────────────────────────
|
||||
|
||||
const FACTIONS: &[(&str, [f32; 3])] = &[
|
||||
@@ -86,16 +117,70 @@ fn setup_galaxy_scene(
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
params: Res<GalaxyParams>,
|
||||
) {
|
||||
let systems = generate_galaxy(¶ms);
|
||||
spawn_galaxy_scene(&mut commands, &mut meshes, &mut materials, &systems);
|
||||
let (systems, contents, connections) = generate_galaxy(¶ms);
|
||||
spawn_galaxy_scene(
|
||||
&mut commands,
|
||||
&mut meshes,
|
||||
&mut materials,
|
||||
&systems,
|
||||
&contents,
|
||||
&connections,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate the full galaxy layout (pure data — no Bevy types).
|
||||
///
|
||||
/// Faithful port of the TS reference in
|
||||
/// `apps/docs/src/prototypes/r3f/galaxy/GalaxyScene.tsx::generateGalaxy`.
|
||||
fn generate_galaxy(params: &GalaxyParams) -> Vec<GeneratedSystem> {
|
||||
/// `apps/docs/src/prototypes/r3f/galaxy/GalaxyScene.tsx::generateGalaxy`,
|
||||
/// extended with per-system POI generation (planets, belts, stations,
|
||||
/// anomalies, gas clouds, stargates).
|
||||
///
|
||||
/// Returns `(systems, contents_per_system, connections)`. The contents are
|
||||
/// returned in parallel with the systems (same index) so the spawner can
|
||||
/// attach them as children of each [`StarSystem`] entity.
|
||||
fn generate_galaxy(
|
||||
params: &GalaxyParams,
|
||||
) -> (
|
||||
Vec<GeneratedSystem>,
|
||||
Vec<SystemContents>,
|
||||
Vec<(usize, usize)>,
|
||||
) {
|
||||
let mut rng = StdRng::seed_from_u64(params.seed);
|
||||
let systems = generate_system_positions(params, &mut rng);
|
||||
let connections = build_connections(&systems);
|
||||
|
||||
// Per-system contents (planets, belts, stations, anomalies, gas clouds).
|
||||
let mut contents: Vec<SystemContents> = systems
|
||||
.iter()
|
||||
.map(|sys| {
|
||||
let ctx = SystemContext {
|
||||
id: &sys.id,
|
||||
name: &sys.name,
|
||||
faction: sys.faction,
|
||||
security: sys.security,
|
||||
is_core: sys.is_core,
|
||||
};
|
||||
contents::generate_system_contents(&mut rng, &ctx)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Stargates — paired across each connection.
|
||||
let summaries: Vec<SystemSummary> = systems
|
||||
.iter()
|
||||
.map(|sys| SystemSummary {
|
||||
id: sys.id.clone(),
|
||||
name: sys.name.clone(),
|
||||
})
|
||||
.collect();
|
||||
contents::generate_stargates(&summaries, &mut contents, &connections);
|
||||
|
||||
(systems, contents, connections)
|
||||
}
|
||||
|
||||
/// Position-only galaxy generation. Pure faithful port of the TS reference
|
||||
/// in `apps/docs/src/prototypes/r3f/galaxy/GalaxyScene.tsx::generateGalaxy`.
|
||||
/// Split out so [`generate_galaxy`] can compose it with POI generation.
|
||||
fn generate_system_positions(params: &GalaxyParams, rng: &mut StdRng) -> Vec<GeneratedSystem> {
|
||||
let mut systems: Vec<GeneratedSystem> = Vec::with_capacity(params.count);
|
||||
|
||||
// Spacing scales with density: smaller for tight cores, larger spread overall.
|
||||
@@ -129,8 +214,16 @@ fn generate_galaxy(params: &GalaxyParams) -> Vec<GeneratedSystem> {
|
||||
} else {
|
||||
rng.gen::<f32>().powf(0.62) * params.size
|
||||
};
|
||||
let arm_count = if vertical { vertical_arms.max(1) } else { horizontal_arms };
|
||||
let arm_twist = if vertical { params.vertical_twist } else { params.twist };
|
||||
let arm_count = if vertical {
|
||||
vertical_arms.max(1)
|
||||
} else {
|
||||
horizontal_arms
|
||||
};
|
||||
let arm_twist = if vertical {
|
||||
params.vertical_twist
|
||||
} else {
|
||||
params.twist
|
||||
};
|
||||
let angle = if core {
|
||||
rng.gen::<f32>() * std::f32::consts::TAU
|
||||
} else {
|
||||
@@ -147,7 +240,11 @@ fn generate_galaxy(params: &GalaxyParams) -> Vec<GeneratedSystem> {
|
||||
let vy = angle.sin() * r * 0.72 + (rng.gen::<f32>() - 0.5) * 12.0;
|
||||
(vx, vy, vz)
|
||||
} else {
|
||||
(angle.cos() * r, (rng.gen::<f32>() - 0.5) * 20.0, angle.sin() * r)
|
||||
(
|
||||
angle.cos() * r,
|
||||
(rng.gen::<f32>() - 0.5) * 20.0,
|
||||
angle.sin() * r,
|
||||
)
|
||||
};
|
||||
let candidate = Vec3::new(x, y, z);
|
||||
final_radius = r;
|
||||
@@ -170,8 +267,7 @@ fn generate_galaxy(params: &GalaxyParams) -> Vec<GeneratedSystem> {
|
||||
}
|
||||
}
|
||||
|
||||
let security =
|
||||
((1.0 - (final_radius / params.size) * 2.0) * 100.0).round() / 100.0;
|
||||
let security = ((1.0 - (final_radius / params.size) * 2.0) * 100.0).round() / 100.0;
|
||||
let name = if core {
|
||||
format!("COR-{}", 100 + index)
|
||||
} else {
|
||||
@@ -186,6 +282,7 @@ fn generate_galaxy(params: &GalaxyParams) -> Vec<GeneratedSystem> {
|
||||
faction_index,
|
||||
color,
|
||||
security,
|
||||
is_core: core,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,9 +297,12 @@ struct GeneratedSystem {
|
||||
faction_index: usize,
|
||||
/// Per-system tint; currently the renderer uses per-faction materials indexed
|
||||
/// by `faction_index`, but this is kept for future per-system variation.
|
||||
#[allow(dead_code)]
|
||||
color: [f32; 3],
|
||||
security: f32,
|
||||
/// True for the [`CORE_COUNT`] Concord systems clustered near the origin.
|
||||
/// Used by [`contents::generate_system_contents`] to bias planet / station
|
||||
/// counts toward core systems.
|
||||
is_core: bool,
|
||||
}
|
||||
|
||||
fn spawn_galaxy_scene(
|
||||
@@ -210,10 +310,19 @@ fn spawn_galaxy_scene(
|
||||
meshes: &mut Assets<Mesh>,
|
||||
materials: &mut Assets<StandardMaterial>,
|
||||
systems: &[GeneratedSystem],
|
||||
contents: &[SystemContents],
|
||||
connections: &[(usize, usize)],
|
||||
) {
|
||||
// Shared meshes (Bevy 0.16 required-components model — no bundle needed).
|
||||
let star_mesh = meshes.add(Sphere::new(1.5).mesh().ico(3).unwrap());
|
||||
let core_star_mesh = meshes.add(Sphere::new(3.0).mesh().ico(4).unwrap());
|
||||
debug_assert_eq!(
|
||||
systems.len(),
|
||||
contents.len(),
|
||||
"systems and contents must be parallel arrays of the same length"
|
||||
);
|
||||
|
||||
// Shared star visuals (Bevy 0.16 required-components model — no bundle needed).
|
||||
// Unit spheres, scaled per-faction at the spawn site.
|
||||
let star_mesh = meshes.add(Sphere::new(1.0).mesh().ico(3).unwrap());
|
||||
let core_star_mesh = meshes.add(Sphere::new(1.0).mesh().ico(4).unwrap());
|
||||
|
||||
let faction_materials: Vec<Handle<StandardMaterial>> = FACTIONS
|
||||
.iter()
|
||||
@@ -233,45 +342,89 @@ fn spawn_galaxy_scene(
|
||||
..default()
|
||||
});
|
||||
|
||||
let connections = build_connections(systems);
|
||||
// Cached POI meshes/materials (one per type — entities scale via Transform).
|
||||
let content_assets = contents::ContentAssets::new(meshes, materials);
|
||||
|
||||
// Parent group so all galaxy contents despawn together.
|
||||
commands
|
||||
.spawn((
|
||||
Transform::default(),
|
||||
GalaxyScene,
|
||||
GalaxyCreationSpawned,
|
||||
))
|
||||
.spawn((Transform::default(), GalaxyScene, GalaxyCreationSpawned))
|
||||
.with_children(|parent| {
|
||||
// XYZ reference axes through the origin.
|
||||
axes::spawn_axes(parent, meshes, materials);
|
||||
|
||||
// Star systems.
|
||||
for sys in systems {
|
||||
let (mesh, material) = if sys.faction_index == 0 && sys.position.length() < 40.0
|
||||
{
|
||||
// Star systems — each is a parent entity that owns its Star + POI children.
|
||||
for (sys, sys_contents) in systems.iter().zip(contents.iter()) {
|
||||
let is_core = sys.faction_index == 0 && sys.is_core;
|
||||
let (mesh, material, scale) = if is_core {
|
||||
// Visual differentiation for the 7 Concord core systems.
|
||||
(core_star_mesh.clone(), faction_materials[0].clone())
|
||||
(core_star_mesh.clone(), faction_materials[0].clone(), 1.1)
|
||||
} else {
|
||||
(star_mesh.clone(), faction_materials[sys.faction_index].clone())
|
||||
(
|
||||
star_mesh.clone(),
|
||||
faction_materials[sys.faction_index].clone(),
|
||||
1.0,
|
||||
)
|
||||
};
|
||||
parent.spawn((
|
||||
Mesh3d(mesh),
|
||||
MeshMaterial3d(material),
|
||||
Transform::from_translation(sys.position),
|
||||
StarSystem {
|
||||
id: sys.id.clone(),
|
||||
name: sys.name.clone(),
|
||||
faction: sys.faction,
|
||||
security: sys.security,
|
||||
},
|
||||
));
|
||||
|
||||
let poi_count = sys_contents.total();
|
||||
|
||||
parent
|
||||
.spawn((
|
||||
Transform::from_translation(sys.position),
|
||||
StarSystem {
|
||||
id: sys.id.clone(),
|
||||
name: sys.name.clone(),
|
||||
faction: sys.faction,
|
||||
security: sys.security,
|
||||
poi_count,
|
||||
},
|
||||
))
|
||||
.with_children(|sys_parent| {
|
||||
// The anchor celestial — system's primary star.
|
||||
let star_entity = sys_parent
|
||||
.spawn((
|
||||
Star,
|
||||
Massive {
|
||||
mass: if sys.is_core { 10000.0 } else { 5000.0 },
|
||||
},
|
||||
Luminosity {
|
||||
value: if sys.is_core { 100.0 } else { 50.0 },
|
||||
},
|
||||
MassLock { radius: 5.0 },
|
||||
BoundingVolume { radius: 1.5 },
|
||||
Identifiable {
|
||||
id: format!("{}-star", sys.id),
|
||||
display_name: sys.name.clone(),
|
||||
classification: Classification::Celestial,
|
||||
},
|
||||
Mesh3d(mesh),
|
||||
MeshMaterial3d(material),
|
||||
Transform::default().with_scale(Vec3::splat(scale)),
|
||||
))
|
||||
.id();
|
||||
|
||||
// POI children (planets, belts, stations, anomalies, stargates, gas).
|
||||
let ctx = SystemContext {
|
||||
id: &sys.id,
|
||||
name: &sys.name,
|
||||
faction: sys.faction,
|
||||
security: sys.security,
|
||||
is_core: sys.is_core,
|
||||
};
|
||||
contents::spawn_system_contents(
|
||||
sys_parent,
|
||||
&ctx,
|
||||
sys_contents,
|
||||
star_entity,
|
||||
&content_assets,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Connections rendered as thin cylinders between linked systems.
|
||||
for (a, b) in connections {
|
||||
let from = systems[a].position;
|
||||
let to = systems[b].position;
|
||||
let from = systems[*a].position;
|
||||
let to = systems[*b].position;
|
||||
let delta = to - from;
|
||||
let length = delta.length();
|
||||
if length < 0.01 {
|
||||
@@ -371,6 +524,13 @@ fn regenerate_galaxy_on_param_change(
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
|
||||
let systems = generate_galaxy(¶ms);
|
||||
spawn_galaxy_scene(&mut commands, &mut meshes, &mut materials, &systems);
|
||||
let (systems, contents, connections) = generate_galaxy(¶ms);
|
||||
spawn_galaxy_scene(
|
||||
&mut commands,
|
||||
&mut meshes,
|
||||
&mut materials,
|
||||
&systems,
|
||||
&contents,
|
||||
&connections,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user