on-the-fly asteroid generation
This commit is contained in:
parent
adb9f4b971
commit
e3e67b0c6f
203
src/world.rs
203
src/world.rs
|
@ -1,18 +1,20 @@
|
||||||
use crate::{actor, nature, settings, hud};
|
use crate::{actor, nature, settings, hud};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||||
use bevy::math::DVec3;
|
use bevy::math::{DVec3, I64Vec3};
|
||||||
use bevy_xpbd_3d::prelude::*;
|
use bevy_xpbd_3d::prelude::*;
|
||||||
use bevy_xpbd_3d::plugins::sync::SyncConfig;
|
use bevy_xpbd_3d::plugins::sync::SyncConfig;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use std::f64::consts::PI as PI64;
|
|
||||||
|
|
||||||
const ASTEROID_UPDATE_INTERVAL: f32 = 1.0; // seconds
|
const ASTEROID_UPDATE_INTERVAL: f32 = 1.0; // seconds
|
||||||
const ASTEROID_SIZE: f32 = 5000.0;
|
const ASTEROID_SIZE_FACTOR: f32 = 5.0;
|
||||||
const STARS_MAX_MAGNITUDE: f32 = 5.5;
|
const STARS_MAX_MAGNITUDE: f32 = 5.5;
|
||||||
|
|
||||||
const CENTER_WORLD_ON_PLAYER: bool = true;
|
const CENTER_WORLD_ON_PLAYER: bool = true;
|
||||||
const ASTEROIDS_ARE_SPHERES: bool = false;
|
|
||||||
|
const ASTEROID_SPAWN_STEP: f64 = 500.0;
|
||||||
|
const ASTEROID_VIEW_RADIUS: f64 = 1000.0;
|
||||||
|
|
||||||
const ASSET_ASTEROID1: &str = "models/asteroid.glb#Scene0";
|
const ASSET_ASTEROID1: &str = "models/asteroid.glb#Scene0";
|
||||||
const ASSET_ASTEROID2: &str = "models/asteroid2.glb#Scene0";
|
const ASSET_ASTEROID2: &str = "models/asteroid2.glb#Scene0";
|
||||||
|
@ -35,7 +37,6 @@ pub struct WorldPlugin;
|
||||||
impl Plugin for WorldPlugin {
|
impl Plugin for WorldPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Startup, setup);
|
app.add_systems(Startup, setup);
|
||||||
app.add_systems(Startup, generate_asteroids);
|
|
||||||
app.add_systems(Update, handle_cheats);
|
app.add_systems(Update, handle_cheats);
|
||||||
app.add_systems(PostUpdate, handle_despawn);
|
app.add_systems(PostUpdate, handle_despawn);
|
||||||
app.add_systems(Update, spawn_despawn_asteroids);
|
app.add_systems(Update, spawn_despawn_asteroids);
|
||||||
|
@ -46,6 +47,7 @@ impl Plugin for WorldPlugin {
|
||||||
app.insert_resource(AsteroidUpdateTimer(
|
app.insert_resource(AsteroidUpdateTimer(
|
||||||
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
|
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
|
||||||
app.insert_resource(AsteroidDatabase(Vec::new()));
|
app.insert_resource(AsteroidDatabase(Vec::new()));
|
||||||
|
app.insert_resource(ActiveAsteroids(HashMap::new()));
|
||||||
app.add_event::<DespawnEvent>();
|
app.add_event::<DespawnEvent>();
|
||||||
|
|
||||||
if CENTER_WORLD_ON_PLAYER {
|
if CENTER_WORLD_ON_PLAYER {
|
||||||
|
@ -62,19 +64,20 @@ impl Plugin for WorldPlugin {
|
||||||
|
|
||||||
#[derive(Resource)] struct AsteroidUpdateTimer(Timer);
|
#[derive(Resource)] struct AsteroidUpdateTimer(Timer);
|
||||||
#[derive(Resource)] struct AsteroidDatabase(Vec<AsteroidData>);
|
#[derive(Resource)] struct AsteroidDatabase(Vec<AsteroidData>);
|
||||||
|
#[derive(Resource)] struct ActiveAsteroids(HashMap<I64Vec3, AsteroidData>);
|
||||||
|
|
||||||
#[derive(Component)] struct Asteroid;
|
#[derive(Component)] struct Asteroid;
|
||||||
|
|
||||||
struct AsteroidData {
|
struct AsteroidData {
|
||||||
entity: Option<Entity>,
|
entity: Entity,
|
||||||
is_spawned: bool,
|
//viewdistance: f64,
|
||||||
class: u8,
|
|
||||||
size: f32,
|
|
||||||
pos: DVec3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
pub struct DespawnEvent(Entity);
|
pub struct DespawnEvent {
|
||||||
|
entity: Entity,
|
||||||
|
origin: I64Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||||
pub struct RingMaterial {
|
pub struct RingMaterial {
|
||||||
|
@ -169,96 +172,104 @@ pub fn setup(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_asteroids(
|
|
||||||
mut db: ResMut<AsteroidDatabase>,
|
|
||||||
) {
|
|
||||||
let phase_steps: i16 = 600;
|
|
||||||
let radius_steps: i16 = 1000;
|
|
||||||
let max_height_step: i16 = 6; // this one is inclusive for symmetry
|
|
||||||
let min_height_step: i16 = -max_height_step;
|
|
||||||
|
|
||||||
let min_radius: f64 = 92e6;
|
|
||||||
let max_radius: f64 = 229e6;
|
|
||||||
let ring_thickness: f64 = 2.0e6;
|
|
||||||
|
|
||||||
let height_step_factor: f64 = ring_thickness / 2.0 / (max_height_step as f64);
|
|
||||||
let max_phase_step_factor: f64 = 2.0 * PI64 / (phase_steps as f64);
|
|
||||||
let max_radius_factor: f64 = (max_radius - min_radius) / (radius_steps as f64);
|
|
||||||
let wobble: f64 = (ASTEROID_SIZE * 50.0).into();
|
|
||||||
let mut count = 0;
|
|
||||||
for phase_step in 0..phase_steps {
|
|
||||||
let phase = max_phase_step_factor * phase_step as f64;
|
|
||||||
for radius_step in 0..radius_steps {
|
|
||||||
let radius = max_radius_factor * radius_step as f64 + min_radius;
|
|
||||||
for height_step in min_height_step..=max_height_step {
|
|
||||||
let height = height_step_factor * height_step as f64;
|
|
||||||
|
|
||||||
let rand1 = (phase+radius).sin() + (height+radius).cos();
|
|
||||||
let rand2 = (phase+height).sin() + (phase+radius).cos();
|
|
||||||
let rand3 = (radius+height).sin() + (phase+height).cos();
|
|
||||||
|
|
||||||
let x = phase.sin() * radius + wobble * rand1;
|
|
||||||
let y = height + wobble * rand2;
|
|
||||||
let z = phase.cos() * radius + wobble * rand3;
|
|
||||||
|
|
||||||
db.0.push(AsteroidData {
|
|
||||||
entity: None,
|
|
||||||
is_spawned: false,
|
|
||||||
pos: DVec3::new(x, y, z),
|
|
||||||
size: ASTEROID_SIZE,
|
|
||||||
class: ((phase_step+radius_step+height_step) % 2) as u8,
|
|
||||||
});
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("Generated {count} asteroids");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_despawn_asteroids(
|
fn spawn_despawn_asteroids(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut timer: ResMut<AsteroidUpdateTimer>,
|
mut timer: ResMut<AsteroidUpdateTimer>,
|
||||||
mut db: ResMut<AsteroidDatabase>,
|
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
q_player: Query<&Position, With<actor::PlayerCamera>>,
|
q_player: Query<&Position, With<actor::PlayerCamera>>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut q_asteroid: Query<(Entity, &mut Visibility), With<Asteroid>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
asset_server: Res<AssetServer>,
|
|
||||||
mut log: ResMut<hud::Log>,
|
|
||||||
mut ew_despawn: EventWriter<DespawnEvent>,
|
mut ew_despawn: EventWriter<DespawnEvent>,
|
||||||
|
mut db: ResMut<ActiveAsteroids>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut q_asteroid: Query<(Entity, &mut Visibility), With<Asteroid>>,
|
||||||
|
mut log: ResMut<hud::Log>,
|
||||||
) {
|
) {
|
||||||
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
|
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let player = q_player.get_single().unwrap();
|
let player = q_player.get_single().unwrap();
|
||||||
|
let player_cell = I64Vec3::new(
|
||||||
|
(player.x / ASTEROID_SPAWN_STEP).round() as i64,
|
||||||
|
(player.y / ASTEROID_SPAWN_STEP).round() as i64,
|
||||||
|
(player.z / ASTEROID_SPAWN_STEP).round() as i64,
|
||||||
|
);
|
||||||
let mut spawned = 0;
|
let mut spawned = 0;
|
||||||
let mut despawned = 0;
|
let mut despawned = 0;
|
||||||
for asteroid in &mut db.0 {
|
|
||||||
let dist = player.distance(asteroid.pos);
|
// Parameters
|
||||||
let should_spawn = dist < 2000.0e3;
|
let view_radius: f64 = ASTEROID_VIEW_RADIUS;
|
||||||
if should_spawn == asteroid.is_spawned {
|
let step: f64 = ASTEROID_SPAWN_STEP;
|
||||||
continue; // Nothing to do
|
let stepmax: i64 = (view_radius / step) as i64;
|
||||||
}
|
|
||||||
if !should_spawn {
|
let x_min = player_cell.x - stepmax;
|
||||||
// Despawn
|
let x_max = player_cell.x + stepmax;
|
||||||
if let Some(entity) = asteroid.entity {
|
let y_min = player_cell.y - stepmax;
|
||||||
|
let y_max = player_cell.y + stepmax;
|
||||||
|
let z_min = player_cell.z - stepmax;
|
||||||
|
let z_max = player_cell.z + stepmax;
|
||||||
|
for (origin, asteroid) in db.0.iter() {
|
||||||
|
if origin.x < x_min || origin.x > x_max
|
||||||
|
|| origin.y < y_min || origin.y > y_max
|
||||||
|
|| origin.z < z_min || origin.z > z_max
|
||||||
|
{
|
||||||
|
let mut despawning_worked = false;
|
||||||
for (ent, mut vis) in &mut q_asteroid {
|
for (ent, mut vis) in &mut q_asteroid {
|
||||||
if ent == entity {
|
if ent == asteroid.entity {
|
||||||
*vis = Visibility::Hidden;
|
*vis = Visibility::Hidden;
|
||||||
asteroid.entity = None;
|
ew_despawn.send(DespawnEvent {
|
||||||
asteroid.is_spawned = false;
|
entity: asteroid.entity,
|
||||||
|
origin: origin.clone(),
|
||||||
|
});
|
||||||
despawned += 1;
|
despawned += 1;
|
||||||
ew_despawn.send(DespawnEvent(entity));
|
despawning_worked = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !despawning_worked {
|
||||||
|
error!("Couldn't despawn asteroid:");
|
||||||
|
dbg!(origin);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in -stepmax..=stepmax {
|
||||||
|
for y in -stepmax..=stepmax {
|
||||||
|
for z in -stepmax..=stepmax {
|
||||||
|
let origin = I64Vec3::new(
|
||||||
|
player_cell.x + x as i64,
|
||||||
|
player_cell.y + y as i64,
|
||||||
|
player_cell.z + z as i64,
|
||||||
|
);
|
||||||
|
if db.0.contains_key(&origin) {
|
||||||
|
// already in db, nothing to do
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate some deterministic pseudorandom numbers seeded with the origin coordinates
|
||||||
|
let rand_x = (4.0*((origin.x+origin.y) as f64).sin()
|
||||||
|
+ 3.0*((origin.y+origin.z) as f64).cos()) % 1.0;
|
||||||
|
let rand_y = (4.0*((origin.y+origin.z) as f64).sin()
|
||||||
|
+ 3.0*((origin.z+origin.x) as f64).cos()) % 1.0;
|
||||||
|
let rand_z = (4.0*((origin.z+origin.x) as f64).sin()
|
||||||
|
+ 3.0*((origin.x+origin.y) as f64).cos()) % 1.0;
|
||||||
|
let rand_s = (4.0*((origin.x+origin.y+origin.z) as f64).sin()
|
||||||
|
+ 3.0*((origin.x+origin.y+origin.z) as f64).cos()).abs() % 1.0;
|
||||||
|
let rand_c = (8.0*((origin.x+origin.z) as f64).sin()
|
||||||
|
+ 4.0*((origin.x+origin.z) as f64).cos()).abs() % 1.0;
|
||||||
|
let class = if rand_c < 0.5 { 0 } else { 1 };
|
||||||
|
|
||||||
|
//if player_cell.as_dvec3().distance(origin.as_dvec3()) > asteroid.viewdistance
|
||||||
|
//let max_viewdist = ASTEROID_VIEW_RADIUS / ASTEROID_SPAWN_STEP;
|
||||||
|
let size: f32 = (rand_s + 1e-6).powf(-0.5) as f32; // -> between ~1 and 1000
|
||||||
|
let wobble = ASTEROID_SPAWN_STEP * 0.3;
|
||||||
|
let pos = DVec3::new(
|
||||||
|
origin.x as f64 * ASTEROID_SPAWN_STEP + wobble * rand_x,
|
||||||
|
origin.y as f64 * ASTEROID_SPAWN_STEP + wobble * rand_y,
|
||||||
|
origin.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z,
|
||||||
|
);
|
||||||
|
dbg!(pos);
|
||||||
|
|
||||||
// Spawn
|
// Spawn
|
||||||
|
dbg!(pos, origin, size);
|
||||||
let mut entity_commands = commands.spawn((
|
let mut entity_commands = commands.spawn((
|
||||||
actor::Actor::default(),
|
actor::Actor::default(),
|
||||||
RigidBody::Dynamic,
|
RigidBody::Dynamic,
|
||||||
|
@ -266,44 +277,28 @@ fn spawn_despawn_asteroids(
|
||||||
LinearVelocity(DVec3::new(0.0, 0.0, 0.35)),
|
LinearVelocity(DVec3::new(0.0, 0.0, 0.35)),
|
||||||
Collider::sphere(1.0),
|
Collider::sphere(1.0),
|
||||||
Rotation::from(Quat::from_rotation_y(-PI / 3.)),
|
Rotation::from(Quat::from_rotation_y(-PI / 3.)),
|
||||||
Position::new(asteroid.pos),
|
Position::new(pos),
|
||||||
Asteroid,
|
Asteroid,
|
||||||
));
|
));
|
||||||
if ASTEROIDS_ARE_SPHERES {
|
let asset = match class {
|
||||||
let sphere_handle = meshes.add(Sphere::default());
|
|
||||||
let sphere_material_handle = materials.add(StandardMaterial {
|
|
||||||
base_color: Color::rgb(0.4, 0.4, 0.4),
|
|
||||||
perceptual_roughness: 1.0,
|
|
||||||
metallic: 0.0,
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
entity_commands.insert(PbrBundle {
|
|
||||||
mesh: sphere_handle,
|
|
||||||
material: sphere_material_handle,
|
|
||||||
transform: Transform {
|
|
||||||
scale: Vec3::splat(asteroid.size),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let asset = match asteroid.class {
|
|
||||||
0 => ASSET_ASTEROID1,
|
0 => ASSET_ASTEROID1,
|
||||||
_ => ASSET_ASTEROID2,
|
_ => ASSET_ASTEROID2,
|
||||||
};
|
};
|
||||||
entity_commands.insert(SceneBundle {
|
entity_commands.insert(SceneBundle {
|
||||||
scene: asset_server.load(asset),
|
scene: asset_server.load(asset),
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
scale: Vec3::splat(asteroid.size),
|
scale: Vec3::splat(ASTEROID_SIZE_FACTOR * size),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
}
|
db.0.insert(origin, AsteroidData {
|
||||||
asteroid.entity = Some(entity_commands.id());
|
entity: entity_commands.id(),
|
||||||
|
//viewdistance: 99999999.0,
|
||||||
|
});
|
||||||
spawned += 1;
|
spawned += 1;
|
||||||
asteroid.is_spawned = true;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if spawned != 0 || despawned != 0 {
|
if spawned != 0 || despawned != 0 {
|
||||||
log.notice(format!("spawned: {spawned}, despawned: {despawned}"));
|
log.notice(format!("spawned: {spawned}, despawned: {despawned}"));
|
||||||
|
@ -313,9 +308,11 @@ fn spawn_despawn_asteroids(
|
||||||
fn handle_despawn(
|
fn handle_despawn(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut er_despawn: EventReader<DespawnEvent>,
|
mut er_despawn: EventReader<DespawnEvent>,
|
||||||
|
mut db: ResMut<ActiveAsteroids>,
|
||||||
) {
|
) {
|
||||||
for despawn in er_despawn.read() {
|
for despawn in er_despawn.read() {
|
||||||
commands.entity(despawn.0).despawn();
|
commands.entity(despawn.entity).despawn();
|
||||||
|
db.0.remove(&despawn.origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue