on-the-fly asteroid generation

This commit is contained in:
yuni 2024-04-02 05:12:53 +02:00
parent adb9f4b971
commit e3e67b0c6f

View file

@ -1,18 +1,20 @@
use crate::{actor, nature, settings, hud};
use bevy::prelude::*;
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::plugins::sync::SyncConfig;
use std::collections::HashMap;
use std::f32::consts::PI;
use std::f64::consts::PI as PI64;
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 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_ASTEROID2: &str = "models/asteroid2.glb#Scene0";
@ -35,7 +37,6 @@ pub struct WorldPlugin;
impl Plugin for WorldPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Startup, generate_asteroids);
app.add_systems(Update, handle_cheats);
app.add_systems(PostUpdate, handle_despawn);
app.add_systems(Update, spawn_despawn_asteroids);
@ -46,6 +47,7 @@ impl Plugin for WorldPlugin {
app.insert_resource(AsteroidUpdateTimer(
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
app.insert_resource(AsteroidDatabase(Vec::new()));
app.insert_resource(ActiveAsteroids(HashMap::new()));
app.add_event::<DespawnEvent>();
if CENTER_WORLD_ON_PLAYER {
@ -62,19 +64,20 @@ impl Plugin for WorldPlugin {
#[derive(Resource)] struct AsteroidUpdateTimer(Timer);
#[derive(Resource)] struct AsteroidDatabase(Vec<AsteroidData>);
#[derive(Resource)] struct ActiveAsteroids(HashMap<I64Vec3, AsteroidData>);
#[derive(Component)] struct Asteroid;
struct AsteroidData {
entity: Option<Entity>,
is_spawned: bool,
class: u8,
size: f32,
pos: DVec3,
entity: Entity,
//viewdistance: f64,
}
#[derive(Event)]
pub struct DespawnEvent(Entity);
pub struct DespawnEvent {
entity: Entity,
origin: I64Vec3,
}
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
pub struct RingMaterial {
@ -169,141 +172,133 @@ 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(
time: Res<Time>,
mut timer: ResMut<AsteroidUpdateTimer>,
mut db: ResMut<AsteroidDatabase>,
mut commands: Commands,
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 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() {
return;
}
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 despawned = 0;
for asteroid in &mut db.0 {
let dist = player.distance(asteroid.pos);
let should_spawn = dist < 2000.0e3;
if should_spawn == asteroid.is_spawned {
continue; // Nothing to do
}
if !should_spawn {
// Despawn
if let Some(entity) = asteroid.entity {
for (ent, mut vis) in &mut q_asteroid {
if ent == entity {
*vis = Visibility::Hidden;
asteroid.entity = None;
asteroid.is_spawned = false;
despawned += 1;
ew_despawn.send(DespawnEvent(entity));
break;
}
// Parameters
let view_radius: f64 = ASTEROID_VIEW_RADIUS;
let step: f64 = ASTEROID_SPAWN_STEP;
let stepmax: i64 = (view_radius / step) as i64;
let x_min = player_cell.x - stepmax;
let x_max = player_cell.x + stepmax;
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 {
if ent == asteroid.entity {
*vis = Visibility::Hidden;
ew_despawn.send(DespawnEvent {
entity: asteroid.entity,
origin: origin.clone(),
});
despawned += 1;
despawning_worked = true;
break;
}
}
continue;
if !despawning_worked {
error!("Couldn't despawn asteroid:");
dbg!(origin);
}
}
}
// Spawn
let mut entity_commands = commands.spawn((
actor::Actor::default(),
RigidBody::Dynamic,
AngularVelocity(DVec3::new(0.1, 0.1, 0.03)),
LinearVelocity(DVec3::new(0.0, 0.0, 0.35)),
Collider::sphere(1.0),
Rotation::from(Quat::from_rotation_y(-PI / 3.)),
Position::new(asteroid.pos),
Asteroid,
));
if ASTEROIDS_ARE_SPHERES {
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),
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;
}
// 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
dbg!(pos, origin, size);
let mut entity_commands = commands.spawn((
actor::Actor::default(),
RigidBody::Dynamic,
AngularVelocity(DVec3::new(0.1, 0.1, 0.03)),
LinearVelocity(DVec3::new(0.0, 0.0, 0.35)),
Collider::sphere(1.0),
Rotation::from(Quat::from_rotation_y(-PI / 3.)),
Position::new(pos),
Asteroid,
));
let asset = match class {
0 => ASSET_ASTEROID1,
_ => ASSET_ASTEROID2,
};
entity_commands.insert(SceneBundle {
scene: asset_server.load(asset),
transform: Transform {
scale: Vec3::splat(ASTEROID_SIZE_FACTOR * size),
..default()
},
..default()
},
..default()
});
});
db.0.insert(origin, AsteroidData {
entity: entity_commands.id(),
//viewdistance: 99999999.0,
});
spawned += 1;
}
}
else {
let asset = match asteroid.class {
0 => ASSET_ASTEROID1,
_ => ASSET_ASTEROID2,
};
entity_commands.insert(SceneBundle {
scene: asset_server.load(asset),
transform: Transform {
scale: Vec3::splat(asteroid.size),
..default()
},
..default()
});
}
asteroid.entity = Some(entity_commands.id());
spawned += 1;
asteroid.is_spawned = true;
}
if spawned != 0 || despawned != 0 {
log.notice(format!("spawned: {spawned}, despawned: {despawned}"));
@ -313,9 +308,11 @@ fn spawn_despawn_asteroids(
fn handle_despawn(
mut commands: Commands,
mut er_despawn: EventReader<DespawnEvent>,
mut db: ResMut<ActiveAsteroids>,
) {
for despawn in er_despawn.read() {
commands.entity(despawn.0).despawn();
commands.entity(despawn.entity).despawn();
db.0.remove(&despawn.origin);
}
}