Add feature equivalence to abdea34c8e81676861df2ccd9e789eaf3c828d9f
This commit is contained in:
parent
8a5fbd1bb1
commit
c1994382c1
10 changed files with 5077 additions and 1536 deletions
2572
Cargo.lock
generated
2572
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -8,5 +8,7 @@ edition = "2021"
|
||||||
[dependencies.save]
|
[dependencies.save]
|
||||||
path = "save"
|
path = "save"
|
||||||
|
|
||||||
[dependencies.serde-xml-rs]
|
[dependencies.bevy]
|
||||||
version = "0.5.1"
|
version = "0.8"
|
||||||
|
default-features = false
|
||||||
|
features = ["bevy_asset", "bevy_winit", "bevy_scene", "render"]
|
||||||
|
|
2068
save/Cargo.lock
generated
2068
save/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -3,8 +3,13 @@ name = "save"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[dependencies.noise]
|
||||||
|
version = "0.7.0"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.rand]
|
||||||
version = "1.0.144"
|
version = "0.8.5"
|
||||||
features = ["derive"]
|
|
||||||
|
[dependencies.bevy]
|
||||||
|
version = "0.8"
|
||||||
|
default-features = false
|
||||||
|
|
1539
save/src/lib.rs
1539
save/src/lib.rs
File diff suppressed because it is too large
Load diff
71
save/src/math_util.rs
Normal file
71
save/src/math_util.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
f32::consts::{PI, TAU},
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy::math::Vec3A;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum CartesianError {
|
||||||
|
InvalidAlpha(f32),
|
||||||
|
}
|
||||||
|
impl Error for CartesianError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"description() is deprecated; use Display"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
|
self.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for CartesianError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
CartesianError::InvalidAlpha(alpha) => {
|
||||||
|
f.write_fmt(format_args!("Alpha value must be [0..PI], was {}", alpha))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cartesian_coordinates(alpha: f32, beta: f32, radius: f32) -> Result<Vec3A, CartesianError> {
|
||||||
|
if alpha < 0.0 || alpha > PI {
|
||||||
|
return Err(CartesianError::InvalidAlpha(alpha));
|
||||||
|
}
|
||||||
|
let mut beta = beta.clone();
|
||||||
|
|
||||||
|
while beta < 0.0 {
|
||||||
|
beta += PI;
|
||||||
|
}
|
||||||
|
while beta >= TAU {
|
||||||
|
beta -= TAU;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sin_alpha = f32::sin(alpha);
|
||||||
|
|
||||||
|
Ok(Vec3A::new(
|
||||||
|
sin_alpha * f32::cos(beta) * radius,
|
||||||
|
f32::cos(alpha) * radius,
|
||||||
|
sin_alpha * f32::sin(beta) * radius,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_point_in_sphere(radius: f32) -> Vec3A {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let x = rng.gen_range(-radius..radius);
|
||||||
|
let y = rng.gen_range(-radius..radius);
|
||||||
|
let z = rng.gen_range(-radius..radius);
|
||||||
|
let mult = 1.0 / (x * x + y * y + z * z).sqrt();
|
||||||
|
|
||||||
|
if x == 0.0 && y == 0.0 && z == 0.0 {
|
||||||
|
return Vec3A::X;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3A::new(mult * x, mult * y, mult * z)
|
||||||
|
}
|
135
save/src/world.rs
Normal file
135
save/src/world.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
f32::consts::{PI, TAU},
|
||||||
|
fmt::Display,
|
||||||
|
};
|
||||||
|
|
||||||
|
use noise::{NoiseFn, Perlin, Seedable};
|
||||||
|
|
||||||
|
use crate::{cartesian_coordinates, random_point_in_sphere, CartesianError};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum WorldGenError {
|
||||||
|
CartesianError(CartesianError),
|
||||||
|
}
|
||||||
|
impl Error for WorldGenError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
match *self {
|
||||||
|
WorldGenError::CartesianError(ref e) => Some(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"description() is deprecated; use Display"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
|
self.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for WorldGenError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
WorldGenError::CartesianError(err) => err.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct World {
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32,
|
||||||
|
pub seed: u32,
|
||||||
|
|
||||||
|
pub terrain: Vec<Vec<TerrainCell>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
pub struct TerrainCell {
|
||||||
|
pub altitude: f32,
|
||||||
|
pub rainfall: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
pub fn new(width: i32, height: i32, seed: u32) -> World {
|
||||||
|
let terrain = vec![
|
||||||
|
vec![TerrainCell::default(); width.try_into().unwrap()];
|
||||||
|
height.try_into().unwrap()
|
||||||
|
];
|
||||||
|
|
||||||
|
World {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
seed,
|
||||||
|
terrain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const MIN_ALTITUDE: f32 = -10000.0;
|
||||||
|
pub const MAX_ALTITUDE: f32 = 10000.0;
|
||||||
|
pub const ALTITUDE_SPAN: f32 = Self::MAX_ALTITUDE - Self::MIN_ALTITUDE;
|
||||||
|
|
||||||
|
pub const MIN_RAINFALL: f32 = -10.0;
|
||||||
|
pub const MAX_RAINFALL: f32 = 100.0;
|
||||||
|
pub const RAINFALL_SPAN: f32 = Self::MAX_RAINFALL - Self::MIN_RAINFALL;
|
||||||
|
pub const RAINFALL_ALTITUDE_FACTOR: f32 = 1.0;
|
||||||
|
|
||||||
|
pub fn generate(&mut self) -> Result<(), WorldGenError> {
|
||||||
|
let perlin = Perlin::new().set_seed(self.seed);
|
||||||
|
|
||||||
|
if let Err(err) = self.generate_altitude(&perlin) {
|
||||||
|
return Err(WorldGenError::CartesianError(err));
|
||||||
|
}
|
||||||
|
if let Err(err) = self.generate_rainfall(&perlin) {
|
||||||
|
return Err(WorldGenError::CartesianError(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn generate_altitude(&mut self, perlin: &Perlin) -> Result<(), CartesianError> {
|
||||||
|
let offset = random_point_in_sphere(1000.0);
|
||||||
|
const RADIUS: f32 = 2.0;
|
||||||
|
|
||||||
|
for (y, row) in self.terrain.iter_mut().enumerate() {
|
||||||
|
let alpha = (y as f32 / self.height as f32) * PI;
|
||||||
|
|
||||||
|
for (x, cell) in row.iter_mut().enumerate() {
|
||||||
|
let beta = (x as f32 / self.width as f32) * TAU;
|
||||||
|
let pos = cartesian_coordinates(alpha, beta, RADIUS)? + offset;
|
||||||
|
|
||||||
|
let value = Perlin::get(perlin, [pos.x.into(), pos.y.into(), pos.z.into()]) as f32;
|
||||||
|
|
||||||
|
let altitude = Self::MIN_ALTITUDE + (value * Self::ALTITUDE_SPAN);
|
||||||
|
|
||||||
|
cell.altitude = altitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_rainfall(&mut self, perlin: &Perlin) -> Result<(), CartesianError> {
|
||||||
|
let offset = random_point_in_sphere(1000.0);
|
||||||
|
const RADIUS: f32 = 2.0;
|
||||||
|
|
||||||
|
for (y, row) in self.terrain.iter_mut().enumerate() {
|
||||||
|
let alpha = (y as f32 / self.height as f32) * PI;
|
||||||
|
for (x, cell) in row.iter_mut().enumerate() {
|
||||||
|
let beta = (x as f32 / self.width as f32) * TAU;
|
||||||
|
|
||||||
|
let pos = cartesian_coordinates(alpha, beta, RADIUS)? + offset;
|
||||||
|
|
||||||
|
let value = Perlin::get(perlin, [pos.x.into(), pos.y.into(), pos.z.into()]) as f32;
|
||||||
|
|
||||||
|
let base_rainfall = (value * Self::RAINFALL_SPAN + Self::MIN_RAINFALL)
|
||||||
|
.clamp(0.0, World::MAX_RAINFALL);
|
||||||
|
let altitude_factor = ((cell.altitude / Self::MAX_ALTITUDE)
|
||||||
|
* World::RAINFALL_ALTITUDE_FACTOR)
|
||||||
|
.clamp(0.0, 1.0);
|
||||||
|
let rainfall = base_rainfall * (1.0 - altitude_factor);
|
||||||
|
|
||||||
|
cell.rainfall = rainfall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
21
save/src/world_manager.rs
Normal file
21
save/src/world_manager.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::{World, WorldGenError};
|
||||||
|
use rand::random;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WorldManager {
|
||||||
|
world: Option<World>,
|
||||||
|
}
|
||||||
|
impl WorldManager {
|
||||||
|
pub fn new() -> WorldManager {
|
||||||
|
WorldManager { world: None }
|
||||||
|
}
|
||||||
|
pub fn get_world(&self) -> Option<&World> {
|
||||||
|
self.world.as_ref()
|
||||||
|
}
|
||||||
|
pub fn new_world(&mut self) -> Result<&World, WorldGenError> {
|
||||||
|
let mut new_world = World::new(800, 600, random());
|
||||||
|
new_world.generate()?;
|
||||||
|
self.world = Some(new_world);
|
||||||
|
Ok(self.get_world().unwrap())
|
||||||
|
}
|
||||||
|
}
|
188
src/main.rs
188
src/main.rs
|
@ -1,12 +1,182 @@
|
||||||
use save::Save;
|
#![warn(absolute_paths_not_starting_with_crate)]
|
||||||
use std::fs;
|
// #![warn(box_pointers)]
|
||||||
|
#![warn(elided_lifetimes_in_paths)]
|
||||||
|
#![warn(explicit_outlives_requirements)]
|
||||||
|
#![warn(keyword_idents)]
|
||||||
|
#![warn(macro_use_extern_crate)]
|
||||||
|
#![warn(meta_variable_misuse)]
|
||||||
|
#![warn(missing_abi)]
|
||||||
|
#![warn(missing_copy_implementations)]
|
||||||
|
#![warn(missing_debug_implementations)]
|
||||||
|
// #![warn(missing_docs)]
|
||||||
|
#![warn(non_ascii_idents)]
|
||||||
|
#![warn(noop_method_call)]
|
||||||
|
#![warn(pointer_structural_match)]
|
||||||
|
#![warn(rust_2021_incompatible_closure_captures)]
|
||||||
|
#![warn(rust_2021_incompatible_or_patterns)]
|
||||||
|
#![warn(rust_2021_prefixes_incompatible_syntax)]
|
||||||
|
#![warn(rust_2021_prelude_collisions)]
|
||||||
|
#![warn(single_use_lifetimes)]
|
||||||
|
#![warn(trivial_casts)]
|
||||||
|
#![warn(trivial_numeric_casts)]
|
||||||
|
#![warn(unreachable_pub)]
|
||||||
|
#![warn(unsafe_code)]
|
||||||
|
#![warn(unsafe_op_in_unsafe_fn)]
|
||||||
|
#![warn(unstable_features)]
|
||||||
|
#![warn(unused_crate_dependencies)]
|
||||||
|
#![warn(unused_extern_crates)]
|
||||||
|
#![warn(unused_import_braces)]
|
||||||
|
#![warn(unused_lifetimes)]
|
||||||
|
#![warn(unused_macro_rules)]
|
||||||
|
#![warn(unused_qualifications)]
|
||||||
|
#![warn(unused_results)]
|
||||||
|
#![warn(variant_size_differences)]
|
||||||
|
|
||||||
use serde_xml_rs::from_reader;
|
use bevy::{
|
||||||
|
app::App,
|
||||||
|
asset::Assets,
|
||||||
|
core_pipeline::core_2d::Camera2dBundle,
|
||||||
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
|
ecs::{
|
||||||
|
change_detection::ResMut,
|
||||||
|
system::{Commands, Res},
|
||||||
|
},
|
||||||
|
hierarchy::BuildChildren,
|
||||||
|
render::{
|
||||||
|
color::Color,
|
||||||
|
render_resource::{
|
||||||
|
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
|
||||||
|
},
|
||||||
|
texture::{Image, ImageSettings},
|
||||||
|
},
|
||||||
|
ui::{
|
||||||
|
entity::{ImageBundle, NodeBundle},
|
||||||
|
Size, Style, UiImage, Val,
|
||||||
|
},
|
||||||
|
utils::default,
|
||||||
|
window::WindowDescriptor,
|
||||||
|
winit::WinitSettings,
|
||||||
|
DefaultPlugins,
|
||||||
|
};
|
||||||
|
use save::*;
|
||||||
|
|
||||||
fn main() {
|
fn get_color(cell: &TerrainCell) -> Color {
|
||||||
println!("Start");
|
let altitude_color = gen_altitude_color(cell.altitude);
|
||||||
let planet_file = fs::File::open("planet.plnt").unwrap();
|
let rainfall_color = gen_rainfall_color(cell.rainfall);
|
||||||
println!("Opened");
|
|
||||||
let planet: Save = from_reader(planet_file).unwrap();
|
let normalized_rainfall = f32::max(cell.rainfall / World::MAX_RAINFALL, 0.0);
|
||||||
println!("Deserialized!");
|
|
||||||
|
let red = (altitude_color.r() * (1.0 - normalized_rainfall))
|
||||||
|
+ rainfall_color.r() * normalized_rainfall;
|
||||||
|
let green = (altitude_color.g() * (1.0 - normalized_rainfall))
|
||||||
|
+ rainfall_color.g() * normalized_rainfall;
|
||||||
|
let blue = (altitude_color.b() * (1.0 - normalized_rainfall))
|
||||||
|
+ rainfall_color.b() * normalized_rainfall;
|
||||||
|
|
||||||
|
Color::rgb(red, green, blue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_altitude_color(altitude: f32) -> Color {
|
||||||
|
if altitude < 0.0 {
|
||||||
|
Color::BLUE
|
||||||
|
} else {
|
||||||
|
let mult = (altitude - World::MIN_ALTITUDE) / World::ALTITUDE_SPAN;
|
||||||
|
|
||||||
|
Color::rgb(0.58 * mult, 0.29 * mult, 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_rainfall_color(rainfall: f32) -> Color {
|
||||||
|
if rainfall < 0.0 {
|
||||||
|
Color::BLACK
|
||||||
|
} else {
|
||||||
|
let mult = rainfall / World::MAX_RAINFALL;
|
||||||
|
Color::GREEN * mult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_texture(
|
||||||
|
mut commands: Commands<'_, '_>,
|
||||||
|
mut images: ResMut<'_, Assets<Image>>,
|
||||||
|
world_manager: Res<'_, WorldManager>,
|
||||||
|
) {
|
||||||
|
let world = world_manager.get_world().unwrap();
|
||||||
|
let terrain_cells: Vec<_> = world.terrain.iter().rev().flatten().collect();
|
||||||
|
let colors: Vec<_> = terrain_cells.iter().map(|cell| get_color(cell)).collect();
|
||||||
|
let data: Vec<_> = colors
|
||||||
|
.iter()
|
||||||
|
.flat_map(|color| {
|
||||||
|
color
|
||||||
|
.as_rgba_f32()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|num| num.to_le_bytes())
|
||||||
|
.collect::<Vec<u8>>()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let image_handle = images.add(Image {
|
||||||
|
data,
|
||||||
|
texture_descriptor: TextureDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: Extent3d {
|
||||||
|
width: world.width as u32,
|
||||||
|
height: world.height as u32,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format: TextureFormat::Rgba32Float,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = commands.spawn_bundle(Camera2dBundle::default());
|
||||||
|
_ = commands
|
||||||
|
.spawn_bundle(NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
color: Color::NONE.into(),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|parent| {
|
||||||
|
_ = parent.spawn_bundle(ImageBundle {
|
||||||
|
style: Style {
|
||||||
|
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
image: UiImage(image_handle),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut manager = WorldManager::new();
|
||||||
|
let world = manager.new_world()?;
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
|
||||||
|
.insert_resource(WinitSettings::desktop_app())
|
||||||
|
// Use nearest-neighbor rendering for cripsier pixels
|
||||||
|
.insert_resource(ImageSettings::default_nearest())
|
||||||
|
.insert_resource(WindowDescriptor {
|
||||||
|
width: world.width as f32,
|
||||||
|
height: world.height as f32,
|
||||||
|
title: String::from("World-RS"),
|
||||||
|
resizable: true,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.insert_resource(manager)
|
||||||
|
.add_startup_system(generate_texture)
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugin(LogDiagnosticsPlugin::default())
|
||||||
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
.run();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue