Compare commits
41 Commits
shader-tes
...
xpbd
| Author | SHA1 | Date | |
|---|---|---|---|
| fcee3f8158 | |||
| d9b50538d9 | |||
| f060c67b74 | |||
| 4e9a35adc6 | |||
| 58a9f62dca | |||
| cd4c9f2acf | |||
| 7b6cae39b7 | |||
| ed94f77323 | |||
| b2622f2126 | |||
| 7732d5ebda | |||
| 1e8aaa502d | |||
| d742ad8a28 | |||
| a933ef791b | |||
| 55226f0181 | |||
| 771c212798 | |||
| c434b2eab0 | |||
| 9de7bc53d0 | |||
| 279b7c0418 | |||
| 28047ebdb5 | |||
| 4c8b52a3ff | |||
| 1547e083a9 | |||
| a8763ae98a | |||
| a65848fac8 | |||
| 54d0f762d2 | |||
| 985523a1cb | |||
| b365a9d835 | |||
| 1ce46df23c | |||
| 7c770af89c | |||
| d3b5893294 | |||
| 377707e689 | |||
| f302636a98 | |||
| d31f1501eb | |||
| d40c35e891 | |||
| 2acbc3039f | |||
| b1dc3b9aef | |||
| df76dc7169 | |||
| 9fb305a887 | |||
| e9dca330c0 | |||
| b2b95937cc | |||
| 70a5d64d16 | |||
| 27193adf15 |
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "game/main/assets"]
|
||||
path = game/main/assets
|
||||
url = git@github.com:Amatsugu/phos-assets.git
|
||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -12,8 +12,14 @@
|
||||
"name": "Debug",
|
||||
"program": "${workspaceRoot}/target/debug/phos.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceRoot}/target/debug",
|
||||
"preLaunchTask": "Build",
|
||||
// "environment": [
|
||||
// {
|
||||
// "name": "RUST_BACKTRACE",
|
||||
// "value": "1"
|
||||
// }
|
||||
// ]
|
||||
}
|
||||
]
|
||||
}
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,3 +1,6 @@
|
||||
{
|
||||
"cmake.configureOnOpen": false
|
||||
"cmake.configureOnOpen": false,
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"Cargo.toml",
|
||||
]
|
||||
}
|
||||
6
.vscode/tasks.json
vendored
6
.vscode/tasks.json
vendored
@@ -3,10 +3,8 @@
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"command": "build",
|
||||
"args": [],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
|
||||
940
Cargo.lock
generated
940
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -1,15 +1,17 @@
|
||||
[workspace]
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"game/main",
|
||||
"engine/world_generation"
|
||||
, "game/camera_system"]
|
||||
resolver = "2"
|
||||
"engine/world_generation",
|
||||
"engine/asset_loader"]
|
||||
|
||||
# Enable a small amount of optimization in debug mode
|
||||
#[profile.dev]
|
||||
#opt-level = 1
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
@@ -1,9 +1,11 @@
|
||||
[package]
|
||||
name = "camera_system"
|
||||
name = "asset_loader"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.13.1"
|
||||
serde = "1.0.197"
|
||||
serde_json = "1.0.115"
|
||||
bevy = "0.13.2"
|
||||
106
engine/asset_loader/src/lib.rs
Normal file
106
engine/asset_loader/src/lib.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
pub mod macros {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! create_asset_loader {
|
||||
(
|
||||
$plugin_name: ident,
|
||||
$loader_name: ident,
|
||||
$asset_type: ident,
|
||||
$asset_loadstate_name: ident,
|
||||
$extensions: expr,
|
||||
$($string_name: ident -> $handle_name: ident)* ;
|
||||
$($string_array_name: ident -> $handle_array_name: ident)* ?
|
||||
) => {
|
||||
use bevy::prelude::*;
|
||||
use bevy::asset::{AssetLoader, AssetEvent, LoadContext, AsyncReadExt, io::Reader};
|
||||
use bevy::utils::BoxedFuture;
|
||||
pub struct $plugin_name;
|
||||
impl Plugin for $plugin_name {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_asset::<$asset_type>()
|
||||
.init_asset_loader::<$loader_name>()
|
||||
.insert_resource($asset_loadstate_name::default())
|
||||
.add_systems(Update, finalize);
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize(
|
||||
mut asset_events: EventReader<AssetEvent<$asset_type>>,
|
||||
mut assets: ResMut<Assets<$asset_type>>,
|
||||
mut load_state: ResMut<$asset_loadstate_name>,
|
||||
asset_server: Res<AssetServer>
|
||||
) {
|
||||
for event in asset_events.read() {
|
||||
match event {
|
||||
AssetEvent::Added { id } => load_state.added += 1,
|
||||
AssetEvent::LoadedWithDependencies { id } => {
|
||||
let asset = assets.get_mut(id.clone()).unwrap();
|
||||
|
||||
$(
|
||||
asset.$handle_name = asset_server.load(&asset.$string_name);
|
||||
)*
|
||||
$(
|
||||
for i in 0..asset.$string_array_name.len(){
|
||||
asset.$handle_array_name.push(asset_server.load(&asset.$string_array_name[i]));
|
||||
}
|
||||
)?
|
||||
load_state.loaded += 1;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Default)]
|
||||
pub struct $asset_loadstate_name{
|
||||
pub loaded: u32,
|
||||
pub added: u32,
|
||||
}
|
||||
|
||||
impl $asset_loadstate_name{
|
||||
pub fn is_all_loaded(&self) -> bool{
|
||||
if self.added == 0{
|
||||
return false;
|
||||
}
|
||||
return self.loaded >= self.added;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct $loader_name;
|
||||
|
||||
impl AssetLoader for $loader_name {
|
||||
type Asset = $asset_type;
|
||||
|
||||
type Settings = ();
|
||||
|
||||
type Error = String;
|
||||
|
||||
fn load<'a>(
|
||||
&'a self,
|
||||
reader: &'a mut Reader,
|
||||
_settings: &'a Self::Settings,
|
||||
_load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
|
||||
return Box::pin(async move {
|
||||
let mut data: String = String::new();
|
||||
let read_result = reader.read_to_string(&mut data).await;
|
||||
if read_result.is_err() {
|
||||
return Err(read_result.err().unwrap().to_string());
|
||||
}
|
||||
let serialized: Result<Self::Asset, serde_json::Error> =
|
||||
serde_json::from_str(&data);
|
||||
if serialized.is_err() {
|
||||
return Err(serialized.err().unwrap().to_string());
|
||||
}
|
||||
return Ok(serialized.unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
$extensions
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,13 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.13.1"
|
||||
bevy = "0.13.2"
|
||||
noise = "0.9.0"
|
||||
serde = {version="1.0.197", features=["derive"]}
|
||||
serde_json = "1.0.115"
|
||||
asset_loader = {path = "../asset_loader"}
|
||||
rayon = "1.10.0"
|
||||
bevy-inspector-egui = "0.24.0"
|
||||
|
||||
[features]
|
||||
tracing = ["bevy/trace_tracy"]
|
||||
34
engine/world_generation/src/biome_painter.rs
Normal file
34
engine/world_generation/src/biome_painter.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::{
|
||||
asset::{Asset, Handle},
|
||||
reflect::TypePath,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::tile_mapper::TileMapperAsset;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, TypePath, Asset)]
|
||||
pub struct BiomePainterAsset {
|
||||
#[serde(skip)]
|
||||
pub biomes: Vec<Handle<TileMapperAsset>>,
|
||||
pub biomes_path: [String; 16],
|
||||
}
|
||||
|
||||
impl BiomePainterAsset {
|
||||
pub fn sample_biome(&self, moisture: f32, temperature: f32) -> Handle<TileMapperAsset> {
|
||||
let x = (moisture.clamp(0., 1.) * 3.).ceil() as usize;
|
||||
let y = (temperature.clamp(0., 1.) * 3.).ceil() as usize;
|
||||
return self.biomes[x + y * 4].clone();
|
||||
}
|
||||
}
|
||||
|
||||
create_asset_loader!(
|
||||
BiomePainterPlugin,
|
||||
BiomePainterLoader,
|
||||
BiomePainterAsset,
|
||||
BiomePainterLoadState,
|
||||
&["bimoes.json"],
|
||||
;
|
||||
biomes_path -> biomes
|
||||
?
|
||||
);
|
||||
69
engine/world_generation/src/chunk_colliders.rs
Normal file
69
engine/world_generation/src/chunk_colliders.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use crate::{hex_utils::*, prelude::*};
|
||||
#[cfg(feature = "tracing")]
|
||||
use bevy::log::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
const CHUNK_TOTAL: usize = Chunk::SIZE * Chunk::SIZE;
|
||||
|
||||
pub fn generate_chunk_collider(chunk: &Chunk, map: &Map) -> (Vec<Vec3>, Vec<[u32; 3]>) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = info_span!("generate_chunk_collider").entered();
|
||||
let vertex_count: usize = CHUNK_TOTAL * 6;
|
||||
let mut verts = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
for z in 0..Chunk::SIZE {
|
||||
for x in 0..Chunk::SIZE {
|
||||
let height = chunk.heights[x + z * Chunk::SIZE];
|
||||
let coord =
|
||||
HexCoord::from_offset(IVec2::new(x as i32, z as i32) + (chunk.chunk_offset * Chunk::SIZE as i32));
|
||||
let neighbors = map.get_neighbors(&coord);
|
||||
let off_pos = Vec3::new(x as f32, height, z as f32);
|
||||
let tile_pos = offset3d_to_world(off_pos);
|
||||
create_tile_collider(tile_pos, &mut verts, &mut indices, &neighbors);
|
||||
}
|
||||
}
|
||||
return (verts, indices);
|
||||
}
|
||||
|
||||
fn create_tile_collider(pos: Vec3, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32; 3]>, neighbors: &[Option<f32>; 6]) {
|
||||
let idx = verts.len() as u32;
|
||||
for i in 0..6 {
|
||||
let p = pos + HEX_CORNERS[i];
|
||||
verts.push(p);
|
||||
}
|
||||
|
||||
//Top Surfave
|
||||
indices.push([idx, idx + 1, idx + 5]);
|
||||
indices.push([idx + 1, idx + 2, idx + 5]);
|
||||
indices.push([idx + 2, idx + 4, idx + 5]);
|
||||
indices.push([idx + 2, idx + 3, idx + 4]);
|
||||
|
||||
for i in 0..neighbors.len() {
|
||||
let cur_n = neighbors[i];
|
||||
match cur_n {
|
||||
Some(n_height) => {
|
||||
if n_height < pos.y {
|
||||
create_tile_wall_collider(
|
||||
idx,
|
||||
Vec3::new(pos.x, n_height.min(pos.y - OUTER_RADIUS / 2.), pos.z),
|
||||
i,
|
||||
verts,
|
||||
indices,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tile_wall_collider(idx: u32, pos: Vec3, dir: usize, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32; 3]>) {
|
||||
let idx2 = verts.len() as u32;
|
||||
|
||||
verts.push(pos + HEX_CORNERS[dir]);
|
||||
verts.push(pos + HEX_CORNERS[(dir + 1) % 6]);
|
||||
|
||||
let off = dir as u32;
|
||||
indices.push([idx + off, idx + ((off + 1) % 6), idx2 + 1]);
|
||||
indices.push([idx + off, idx2 + 1, idx2]);
|
||||
}
|
||||
@@ -1,25 +1,32 @@
|
||||
use bevy::math::IVec2;
|
||||
use bevy::prelude::{FloatExt, Vec2};
|
||||
use noise::{NoiseFn, SuperSimplex};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn generate_heightmap(cfg: &GenerationConfig, seed: u32) -> Map {
|
||||
let mut chunks: Vec<Chunk> = Vec::with_capacity(cfg.size.length_squared() as usize);
|
||||
for z in 0..cfg.size.y {
|
||||
for x in 0..cfg.size.x {
|
||||
chunks.push(generate_chunk(x as f64, z as f64, cfg, seed));
|
||||
}
|
||||
}
|
||||
// let mut chunks: Vec<Chunk> = Vec::with_capacity(cfg.size.length_squared() as usize);
|
||||
let chunks = (0..cfg.size.y)
|
||||
.into_par_iter()
|
||||
.flat_map(|z| {
|
||||
(0..cfg.size.x)
|
||||
.into_par_iter()
|
||||
.map(move |x| generate_chunk(x as f64, z as f64, cfg, seed))
|
||||
})
|
||||
.collect();
|
||||
return Map {
|
||||
chunks,
|
||||
height: cfg.size.y as usize,
|
||||
width: cfg.size.x as usize,
|
||||
sea_level: cfg.sea_level as f32,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn generate_chunk(chunk_x: f64, chunk_z: f64, cfg: &GenerationConfig, seed: u32) -> Chunk {
|
||||
let mut result: [f32; Chunk::SIZE * Chunk::SIZE] = [0.; Chunk::SIZE * Chunk::SIZE];
|
||||
let mut moisture = [0.; Chunk::SIZE * Chunk::SIZE];
|
||||
let mut temp = [0.; Chunk::SIZE * Chunk::SIZE];
|
||||
let noise = SuperSimplex::new(seed);
|
||||
for z in 0..Chunk::SIZE {
|
||||
for x in 0..Chunk::SIZE {
|
||||
@@ -30,15 +37,32 @@ pub fn generate_chunk(chunk_x: f64, chunk_z: f64, cfg: &GenerationConfig, seed:
|
||||
&noise,
|
||||
);
|
||||
result[x + z * Chunk::SIZE] = sample;
|
||||
moisture[x + z * Chunk::SIZE] = noise.get([
|
||||
(x as f64 + chunk_x * Chunk::SIZE as f64) / &cfg.noise_scale,
|
||||
(z as f64 + chunk_z * Chunk::SIZE as f64) / &cfg.noise_scale,
|
||||
]) as f32;
|
||||
temp[x + z * Chunk::SIZE] =
|
||||
sample_tempurature(z as f32 + chunk_z as f32 * Chunk::SIZE as f32, sample, &cfg, 100.);
|
||||
}
|
||||
}
|
||||
return Chunk {
|
||||
points: result,
|
||||
heights: result,
|
||||
moisture: moisture,
|
||||
temperature: temp,
|
||||
chunk_offset: IVec2::new(chunk_x as i32, chunk_z as i32),
|
||||
};
|
||||
}
|
||||
|
||||
fn sample_point(x: f64, z: f64, cfg: &GenerationConfig, noise: &SuperSimplex) -> f32 {
|
||||
fn sample_tempurature(z: f32, height: f32, cfg: &GenerationConfig, equator: f32) -> f32 {
|
||||
let d = (equator - z).abs();
|
||||
let max_d = equator.max(cfg.get_total_height() as f32 - equator);
|
||||
let t_mod = d.remap(0., max_d, 0., 1.).clamp(0., 1.);
|
||||
|
||||
// let max_d = d.max()
|
||||
return (height.remap(0., 50., 0., 1.).clamp(0., 1.) + t_mod) / 2.;
|
||||
}
|
||||
|
||||
fn sample_point(x: f64, z: f64, cfg: &GenerationConfig, noise: &impl NoiseFn<f64, 2>) -> f32 {
|
||||
let x_s = x / cfg.noise_scale;
|
||||
let z_s = z / cfg.noise_scale;
|
||||
|
||||
@@ -68,10 +92,7 @@ fn sample_point(x: f64, z: f64, cfg: &GenerationConfig, noise: &SuperSimplex) ->
|
||||
let d1 = p.x.min(p.y);
|
||||
let od = outer - p;
|
||||
let d2 = od.x.min(od.y);
|
||||
let d = d1
|
||||
.min(d2)
|
||||
.min(cfg.border_size)
|
||||
.remap(0., cfg.border_size, 0., 1.);
|
||||
let d = d1.min(d2).min(cfg.border_size).remap(0., cfg.border_size, 0., 1.);
|
||||
|
||||
return (elevation as f32) * d;
|
||||
}
|
||||
@@ -81,7 +102,7 @@ fn mask(mask: f64, value: f64, sea_level: f64) -> f64 {
|
||||
return value * m;
|
||||
}
|
||||
|
||||
fn sample_simple(x: f64, z: f64, cfg: &GeneratorLayer, noise: &SuperSimplex) -> f64 {
|
||||
fn sample_simple(x: f64, z: f64, cfg: &GeneratorLayer, noise: &impl NoiseFn<f64, 2>) -> f64 {
|
||||
let mut freq: f64 = cfg.base_roughness;
|
||||
let mut amp: f64 = 1.;
|
||||
let mut value = 0.;
|
||||
@@ -95,7 +116,7 @@ fn sample_simple(x: f64, z: f64, cfg: &GeneratorLayer, noise: &SuperSimplex) ->
|
||||
value -= cfg.min_value;
|
||||
return value * cfg.strength;
|
||||
}
|
||||
fn sample_rigid(x: f64, z: f64, cfg: &GeneratorLayer, noise: &SuperSimplex) -> f64 {
|
||||
fn sample_rigid(x: f64, z: f64, cfg: &GeneratorLayer, noise: &impl NoiseFn<f64, 2>) -> f64 {
|
||||
let mut freq: f64 = cfg.base_roughness;
|
||||
let mut amp: f64 = 1.;
|
||||
let mut value = 0.;
|
||||
|
||||
@@ -2,7 +2,10 @@ use crate::prelude::Chunk;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub const OUTER_RADIUS: f32 = 1.;
|
||||
pub const INNER_RADIUS: f32 = OUTER_RADIUS * 0.866025404;
|
||||
pub const INNER_RADIUS: f32 = OUTER_RADIUS * (SQRT_3 / 2.);
|
||||
pub const SHORT_DIAGONAL: f32 = 1. * SQRT_3;
|
||||
pub const LONG_DIAGONAL: f32 = 2. * OUTER_RADIUS;
|
||||
const SQRT_3: f32 = 1.7320508076;
|
||||
|
||||
pub fn offset3d_to_world(offset: Vec3) -> Vec3 {
|
||||
let x = (offset.x + (offset.z * 0.5) - (offset.z / 2.).floor()) * (INNER_RADIUS * 2.);
|
||||
@@ -25,6 +28,10 @@ pub fn offset_to_hex(offset: IVec2) -> IVec3 {
|
||||
return v;
|
||||
}
|
||||
|
||||
pub fn offset_to_index(offset: IVec2, width: usize) -> usize {
|
||||
return offset.x as usize + offset.y as usize * width;
|
||||
}
|
||||
|
||||
pub fn snap_to_hex_grid(world_pos: Vec3) -> Vec3 {
|
||||
return offset_to_world(world_to_offset_pos(world_pos), world_pos.y);
|
||||
}
|
||||
@@ -41,11 +48,11 @@ pub fn world_to_offset_pos(world_pos: Vec3) -> IVec2 {
|
||||
return IVec2::new(ox, oz);
|
||||
}
|
||||
|
||||
pub fn tile_to_world_distance(dist: i32) -> f32 {
|
||||
pub fn tile_to_world_distance(dist: u32) -> f32 {
|
||||
return dist as f32 * (2. * INNER_RADIUS);
|
||||
}
|
||||
|
||||
pub fn get_tile_count(radius: i32) -> i32 {
|
||||
pub fn get_tile_count(radius: u32) -> u32 {
|
||||
return 1 + 3 * (radius + 1) * radius;
|
||||
}
|
||||
|
||||
@@ -86,6 +93,20 @@ impl HexCoord {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn from_world_pos(world_pos: Vec3) -> Self {
|
||||
let offset = world_pos.z / (OUTER_RADIUS * 3.);
|
||||
let mut x = world_pos.x / (INNER_RADIUS * 2.);
|
||||
let mut z = -x;
|
||||
z -= offset;
|
||||
x -= offset;
|
||||
|
||||
let i_x = x.round() as i32;
|
||||
let i_z = (-x - z).round() as i32;
|
||||
let offset_pos = IVec2::new(i_x + i_z / 2, i_z);
|
||||
|
||||
return Self::from_offset(offset_pos);
|
||||
}
|
||||
|
||||
pub fn is_in_bounds(&self, map_height: usize, map_width: usize) -> bool {
|
||||
let off = self.to_offset();
|
||||
if off.x < 0 || off.y < 0 {
|
||||
@@ -99,6 +120,12 @@ impl HexCoord {
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn is_on_chunk_edge(&self) -> bool {
|
||||
let offset = self.to_offset().rem_euclid(IVec2::splat(Chunk::SIZE as i32));
|
||||
let e = (Chunk::SIZE - 1) as i32;
|
||||
return offset.x == 0 || offset.y == 0 || offset.x == e || offset.y == e;
|
||||
}
|
||||
|
||||
pub fn to_chunk_pos(&self) -> IVec2 {
|
||||
let off = self.to_offset();
|
||||
|
||||
@@ -128,22 +155,20 @@ impl HexCoord {
|
||||
return IVec2::new(self.hex.x + (self.hex.y / 2), self.hex.y);
|
||||
}
|
||||
|
||||
pub fn to_index(&self, width: usize) -> i32 {
|
||||
return (self.hex.x + self.hex.y * width as i32) + (self.hex.y / 2);
|
||||
pub fn to_index(&self, width: usize) -> usize {
|
||||
return ((self.hex.x + self.hex.y * width as i32) + (self.hex.y / 2)) as usize;
|
||||
}
|
||||
pub fn to_chunk_index(&self, width: usize) -> i32 {
|
||||
pub fn to_chunk_index(&self, width: usize) -> usize {
|
||||
let pos = self.to_chunk_pos();
|
||||
return pos.x + pos.y * width as i32;
|
||||
return (pos.x + pos.y * width as i32) as usize;
|
||||
}
|
||||
|
||||
pub fn to_chunk_local_index(&self) -> i32 {
|
||||
pub fn to_chunk_local_index(&self) -> usize {
|
||||
return self.to_chunk().to_index(Chunk::SIZE);
|
||||
}
|
||||
|
||||
pub fn distance(&self, other: &HexCoord) -> i32 {
|
||||
return (self.hex.x - other.hex.x).abs()
|
||||
+ (self.hex.y - other.hex.y).abs()
|
||||
+ (self.hex.z - other.hex.z).abs();
|
||||
return (self.hex.x - other.hex.x).abs() + (self.hex.y - other.hex.y).abs() + (self.hex.z - other.hex.z).abs();
|
||||
}
|
||||
|
||||
pub fn rotate_around(&self, center: &HexCoord, angle: i32) -> HexCoord {
|
||||
|
||||
@@ -1,10 +1,42 @@
|
||||
pub mod biome_painter;
|
||||
pub mod chunk_colliders;
|
||||
pub mod heightmap;
|
||||
pub mod hex_utils;
|
||||
pub mod mesh_generator;
|
||||
pub mod packed_mesh_generator;
|
||||
pub mod tile_manager;
|
||||
pub mod tile_mapper;
|
||||
|
||||
pub mod prelude {
|
||||
use crate::hex_utils::HexCoord;
|
||||
use bevy::math::{IVec2, UVec2};
|
||||
use crate::hex_utils::{tile_to_world_distance, HexCoord, INNER_RADIUS, OUTER_RADIUS, SHORT_DIAGONAL};
|
||||
use bevy::math::{IVec2, UVec2, Vec2, Vec3};
|
||||
use bevy::prelude::Resource;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::MeshVertexAttribute;
|
||||
use bevy::render::render_resource::VertexFormat;
|
||||
use bevy_inspector_egui::InspectorOptions;
|
||||
pub const TEX_MULTI: Vec2 = Vec2::new(1000., 1.);
|
||||
|
||||
pub const HEX_CORNERS: [Vec3; 6] = [
|
||||
Vec3::new(0., 0., OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(0., 0., -OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
];
|
||||
|
||||
pub const HEX_NORMALS: [Vec3; 6] = [
|
||||
Vec3::new(INNER_RADIUS / 2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / 2.),
|
||||
Vec3::Z,
|
||||
Vec3::new(INNER_RADIUS / -2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / 2.),
|
||||
Vec3::new(INNER_RADIUS / -2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / -2.),
|
||||
Vec3::NEG_Z,
|
||||
Vec3::new(INNER_RADIUS / 2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / -2.),
|
||||
];
|
||||
|
||||
#[derive(Resource, Reflect, Default)]
|
||||
#[reflect(Resource)]
|
||||
pub struct GenerationConfig {
|
||||
pub noise_scale: f64,
|
||||
pub sea_level: f64,
|
||||
@@ -13,6 +45,16 @@ pub mod prelude {
|
||||
pub layers: Vec<GeneratorLayer>,
|
||||
}
|
||||
|
||||
impl GenerationConfig {
|
||||
pub fn get_total_width(&self) -> usize {
|
||||
return self.size.x as usize * Chunk::SIZE;
|
||||
}
|
||||
pub fn get_total_height(&self) -> usize {
|
||||
return self.size.y as usize * Chunk::SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Reflect, InspectorOptions)]
|
||||
pub struct GeneratorLayer {
|
||||
pub strength: f64,
|
||||
pub min_value: f64,
|
||||
@@ -27,12 +69,17 @@ pub mod prelude {
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub points: [f32; Chunk::SIZE * Chunk::SIZE],
|
||||
pub heights: [f32; Chunk::SIZE * Chunk::SIZE],
|
||||
pub moisture: [f32; Chunk::SIZE * Chunk::SIZE],
|
||||
pub temperature: [f32; Chunk::SIZE * Chunk::SIZE],
|
||||
pub chunk_offset: IVec2,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub const SIZE: usize = 64;
|
||||
pub const WORLD_WIDTH: f32 = Chunk::SIZE as f32 * SHORT_DIAGONAL;
|
||||
pub const WORLD_HEIGHT: f32 = Chunk::SIZE as f32 * 1.5;
|
||||
pub const WORLD_SIZE: Vec2 = Vec2::new(Chunk::WORLD_WIDTH, Chunk::WORLD_HEIGHT);
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
@@ -40,6 +87,7 @@ pub mod prelude {
|
||||
pub chunks: Vec<Chunk>,
|
||||
pub height: usize,
|
||||
pub width: usize,
|
||||
pub sea_level: f32,
|
||||
}
|
||||
|
||||
impl Map {
|
||||
@@ -54,18 +102,58 @@ pub mod prelude {
|
||||
continue;
|
||||
}
|
||||
let c_idx = n_tile.to_chunk_index(self.width);
|
||||
let chunk = &self.chunks[c_idx as usize];
|
||||
let chunk = &self.chunks[c_idx];
|
||||
let local = n_tile.to_chunk_local_index();
|
||||
results[i] = Some(chunk.points[local as usize]);
|
||||
results[i] = Some(chunk.heights[local]);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
pub fn sample_height(&self, pos: &HexCoord) -> f32 {
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.heights[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn is_in_bounds(&self, pos: &HexCoord) -> bool {
|
||||
return pos.is_in_bounds(self.height * Chunk::SIZE, self.width * Chunk::SIZE);
|
||||
}
|
||||
|
||||
pub fn get_moisture(&self, pos: &HexCoord) -> f32 {
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.moisture[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn get_tempurature(&self, pos: &HexCoord) -> f32 {
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.temperature[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn get_center(&self) -> Vec3 {
|
||||
let w = self.get_world_width();
|
||||
let h = self.get_world_height();
|
||||
return Vec3::new(w / 2., self.sea_level, h / 2.);
|
||||
}
|
||||
|
||||
pub fn get_world_width(&self) -> f32 {
|
||||
return (self.width * Chunk::SIZE) as f32 * SHORT_DIAGONAL;
|
||||
}
|
||||
pub fn get_world_height(&self) -> f32 {
|
||||
return (self.height * Chunk::SIZE) as f32 * 1.5;
|
||||
}
|
||||
|
||||
pub fn get_world_size(&self) -> Vec2 {
|
||||
return Vec2::new(self.get_world_width(), self.get_world_height());
|
||||
}
|
||||
|
||||
pub fn set_height(&mut self, pos: &HexCoord, height: f32) {
|
||||
self.chunks[pos.to_chunk_index(self.width)].heights[pos.to_chunk_local_index()] = height;
|
||||
}
|
||||
}
|
||||
pub const ATTRIBUTE_PACKED_VERTEX_DATA: MeshVertexAttribute =
|
||||
MeshVertexAttribute::new("PackedVertexData", 988540817, VertexFormat::Uint32);
|
||||
pub const ATTRIBUTE_VERTEX_HEIGHT: MeshVertexAttribute =
|
||||
MeshVertexAttribute::new("VertexHeight", 988540717, VertexFormat::Float32);
|
||||
|
||||
pub const ATTRIBUTE_TEXTURE_INDEX: MeshVertexAttribute =
|
||||
MeshVertexAttribute::new("TextureIndex", 988540917, VertexFormat::Uint32);
|
||||
}
|
||||
|
||||
pub mod heightmap;
|
||||
pub mod hex_utils;
|
||||
pub mod mesh_generator;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::biome_painter::BiomePainterAsset;
|
||||
use crate::hex_utils::HexCoord;
|
||||
use crate::{
|
||||
hex_utils::{offset3d_to_world, INNER_RADIUS, OUTER_RADIUS},
|
||||
prelude::*,
|
||||
};
|
||||
use crate::tile_manager::TileAsset;
|
||||
use crate::tile_mapper::TileMapperAsset;
|
||||
use crate::{hex_utils::offset3d_to_world, prelude::*};
|
||||
#[cfg(feature = "tracing")]
|
||||
use bevy::log::*;
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::{
|
||||
@@ -10,43 +12,47 @@ use bevy::{
|
||||
render_asset::RenderAssetUsages,
|
||||
},
|
||||
};
|
||||
use std::vec::Vec;
|
||||
use bevy::render::mesh::MeshVertexAttribute;
|
||||
use bevy::render::render_resource::VertexFormat;
|
||||
|
||||
const HEX_CORNERS: [Vec3; 6] = [
|
||||
Vec3::new(0., 0., OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(0., 0., -OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
];
|
||||
pub fn generate_chunk_mesh(
|
||||
chunk: &Chunk,
|
||||
map: &Map,
|
||||
painter: &BiomePainterAsset,
|
||||
tiles: &Res<Assets<TileAsset>>,
|
||||
mappers: &Res<Assets<TileMapperAsset>>,
|
||||
) -> Mesh {
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = info_span!("generate_chunk_mesh").entered();
|
||||
|
||||
|
||||
pub fn generate_chunk_mesh(chunk: &Chunk, map: &Map) -> Mesh {
|
||||
let vertex_count: usize = Chunk::SIZE * Chunk::SIZE * 6;
|
||||
let mut verts = Vec::with_capacity(vertex_count);
|
||||
let mut uvs = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
let mut normals = Vec::with_capacity(vertex_count);
|
||||
|
||||
for z in 0..Chunk::SIZE {
|
||||
for x in 0..Chunk::SIZE {
|
||||
let height = chunk.points[x + z * Chunk::SIZE];
|
||||
let height = chunk.heights[x + z * Chunk::SIZE];
|
||||
let moisture = chunk.moisture[x + z * Chunk::SIZE];
|
||||
let temperature = chunk.temperature[x + z * Chunk::SIZE];
|
||||
let off_pos = Vec3::new(x as f32, height, z as f32);
|
||||
let tile_pos = offset3d_to_world(off_pos);
|
||||
let coord = HexCoord::from_offset(
|
||||
IVec2::new(x as i32, z as i32) + (chunk.chunk_offset * Chunk::SIZE as i32),
|
||||
);
|
||||
let coord =
|
||||
HexCoord::from_offset(IVec2::new(x as i32, z as i32) + (chunk.chunk_offset * Chunk::SIZE as i32));
|
||||
let n = map.get_neighbors(&coord);
|
||||
let biome = mappers.get(painter.sample_biome(moisture, temperature));
|
||||
let tile_handle = biome.unwrap().sample_tile(height);
|
||||
let tile = tiles.get(tile_handle).unwrap();
|
||||
|
||||
create_tile(
|
||||
tile_pos,
|
||||
&n,
|
||||
&mut verts,
|
||||
&mut uvs,
|
||||
&mut indices,
|
||||
&mut normals,
|
||||
// &mut tex,
|
||||
(height % 7.) as u32,
|
||||
tile.texture_id,
|
||||
tile.side_texture_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -55,11 +61,10 @@ pub fn generate_chunk_mesh(chunk: &Chunk, map: &Map) -> Mesh {
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, verts)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||
.with_inserted_indices(Indices::U32(indices))
|
||||
.with_duplicated_vertices()
|
||||
.with_computed_flat_normals();
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, verts)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
||||
.with_inserted_indices(Indices::U32(indices));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@@ -69,30 +74,38 @@ fn create_tile(
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
texture_index: u32,
|
||||
side_texture_index: u32,
|
||||
) {
|
||||
let uv_offset = Vec2::splat(0.5);
|
||||
let tex_off = Vec2::new(texture_index as f32, 0.);
|
||||
let side_tex_off = Vec2::new(side_texture_index as f32, 0.);
|
||||
|
||||
let idx = verts.len() as u32;
|
||||
uvs.push(uv_offset + tex_off);
|
||||
verts.push(pos);
|
||||
for i in 0..6 {
|
||||
let p = pos + HEX_CORNERS[i];
|
||||
verts.push(p);
|
||||
let uv = (HEX_CORNERS[i].xz() / 2.) + uv_offset;
|
||||
uvs.push(uv + tex_off);
|
||||
indices.push(idx);
|
||||
indices.push(idx + 1 + i as u32);
|
||||
indices.push(idx + 1 + ((i as u32 + 1) % 6));
|
||||
uvs.push((uv / TEX_MULTI) + tex_off);
|
||||
normals.push(Vec3::Y);
|
||||
}
|
||||
for i in 0..3 {
|
||||
let off = i * 2;
|
||||
indices.push(off + idx);
|
||||
indices.push(((off + 1) % 6) + idx);
|
||||
indices.push(((off + 2) % 6) + idx);
|
||||
}
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 4);
|
||||
|
||||
for i in 0..neighbors.len() {
|
||||
let cur_n = neighbors[i];
|
||||
match cur_n {
|
||||
Some(n_height) => {
|
||||
if n_height < pos.y {
|
||||
create_tile_wall(pos, i, n_height, verts, uvs, indices, tex_off);
|
||||
create_tile_wall(pos, i, n_height, verts, uvs, indices, normals, side_tex_off);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -107,6 +120,7 @@ fn create_tile_wall(
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
tex_off: Vec2,
|
||||
) {
|
||||
let p1 = HEX_CORNERS[(dir) % 6] + pos;
|
||||
@@ -121,6 +135,12 @@ fn create_tile_wall(
|
||||
verts.push(p3);
|
||||
verts.push(p4);
|
||||
|
||||
let n = HEX_NORMALS[dir];
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 1);
|
||||
@@ -130,7 +150,7 @@ fn create_tile_wall(
|
||||
indices.push(idx + 3);
|
||||
|
||||
uvs.push(Vec2::ZERO + tex_off);
|
||||
uvs.push(Vec2::new(1., 0.) + tex_off);
|
||||
uvs.push(Vec2::new(0., pos.y - height) + tex_off);
|
||||
uvs.push(Vec2::new(1., pos.y - height) + tex_off);
|
||||
uvs.push((Vec2::new(1., 0.) / TEX_MULTI) + tex_off);
|
||||
uvs.push((Vec2::new(0., pos.y - height) / TEX_MULTI) + tex_off);
|
||||
uvs.push((Vec2::new(1., pos.y - height) / TEX_MULTI) + tex_off);
|
||||
}
|
||||
|
||||
147
engine/world_generation/src/packed_mesh_generator.rs
Normal file
147
engine/world_generation/src/packed_mesh_generator.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use crate::biome_painter::BiomePainterAsset;
|
||||
use crate::hex_utils::HexCoord;
|
||||
use crate::prelude::*;
|
||||
use crate::tile_manager::TileAsset;
|
||||
use crate::tile_mapper::TileMapperAsset;
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::{
|
||||
mesh::{Indices, PrimitiveTopology},
|
||||
render_asset::RenderAssetUsages,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn generate_packed_chunk_mesh(
|
||||
chunk: &Chunk,
|
||||
map: &Map,
|
||||
painter: &BiomePainterAsset,
|
||||
tiles: &Res<Assets<TileAsset>>,
|
||||
mappers: &Res<Assets<TileMapperAsset>>,
|
||||
) -> Mesh {
|
||||
let vertex_count: usize = Chunk::SIZE * Chunk::SIZE * 6;
|
||||
let mut packed_data = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
let mut heights = Vec::with_capacity(vertex_count);
|
||||
|
||||
for z in 0..Chunk::SIZE {
|
||||
for x in 0..Chunk::SIZE {
|
||||
let height = chunk.heights[x + z * Chunk::SIZE];
|
||||
let moisture = chunk.moisture[x + z * Chunk::SIZE];
|
||||
let temperature = chunk.temperature[x + z * Chunk::SIZE];
|
||||
let coord =
|
||||
HexCoord::from_offset(IVec2::new(x as i32, z as i32) + (chunk.chunk_offset * Chunk::SIZE as i32));
|
||||
let n = map.get_neighbors(&coord);
|
||||
let biome = mappers.get(painter.sample_biome(moisture, temperature));
|
||||
let tile_handle = biome.unwrap().sample_tile(height);
|
||||
let tile = tiles.get(tile_handle).unwrap();
|
||||
|
||||
create_packed_tile(
|
||||
UVec2::new(x as u32, z as u32),
|
||||
height,
|
||||
&n,
|
||||
&mut packed_data,
|
||||
&mut indices,
|
||||
&mut heights,
|
||||
tile.texture_id,
|
||||
tile.side_texture_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(ATTRIBUTE_PACKED_VERTEX_DATA, packed_data)
|
||||
.with_inserted_attribute(ATTRIBUTE_VERTEX_HEIGHT, heights)
|
||||
.with_inserted_indices(Indices::U32(indices));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
fn create_packed_tile(
|
||||
offset: UVec2,
|
||||
height: f32,
|
||||
neighbors: &[Option<f32>; 6],
|
||||
packed_data: &mut Vec<u32>,
|
||||
indices: &mut Vec<u32>,
|
||||
heights: &mut Vec<f32>,
|
||||
texture_index: u32,
|
||||
side_texture_index: u32,
|
||||
) {
|
||||
let idx = packed_data.len() as u32;
|
||||
|
||||
packed_data.push(pack_vertex_data(offset, 0, texture_index));
|
||||
heights.push(height);
|
||||
for i in 0..6 {
|
||||
packed_data.push(pack_vertex_data(offset, i + 1, texture_index));
|
||||
indices.push(idx);
|
||||
indices.push(idx + 1 + i as u32);
|
||||
indices.push(idx + 1 + ((i as u32 + 1) % 6));
|
||||
heights.push(height);
|
||||
}
|
||||
|
||||
for i in 0..neighbors.len() {
|
||||
let cur_n = neighbors[i];
|
||||
match cur_n {
|
||||
Some(n_height) => {
|
||||
if n_height < height {
|
||||
create_packed_tile_wall(
|
||||
offset,
|
||||
height,
|
||||
n_height,
|
||||
i,
|
||||
packed_data,
|
||||
indices,
|
||||
heights,
|
||||
side_texture_index,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_packed_tile_wall(
|
||||
offset: UVec2,
|
||||
height_top: f32,
|
||||
height_bottom: f32,
|
||||
side: usize,
|
||||
packed_data: &mut Vec<u32>,
|
||||
indices: &mut Vec<u32>,
|
||||
heights: &mut Vec<f32>,
|
||||
side_texture_index: u32,
|
||||
) {
|
||||
let idx = packed_data.len() as u32;
|
||||
|
||||
let side_2 = ((side + 1) % 6) + 1;
|
||||
packed_data.push(pack_vertex_data(offset, side + 1, side_texture_index));
|
||||
packed_data.push(pack_vertex_data(offset, side_2, side_texture_index));
|
||||
packed_data.push(pack_vertex_data(offset, side + 1, side_texture_index));
|
||||
packed_data.push(pack_vertex_data(offset, side_2, side_texture_index));
|
||||
|
||||
heights.push(height_top);
|
||||
heights.push(height_top);
|
||||
heights.push(height_bottom);
|
||||
heights.push(height_bottom);
|
||||
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 1);
|
||||
|
||||
indices.push(idx + 1);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 3);
|
||||
}
|
||||
|
||||
fn pack_vertex_data(offset: UVec2, vert: usize, tex: u32) -> u32 {
|
||||
//6 + 6 bits offset
|
||||
//4 bits vert
|
||||
//12 bits texture
|
||||
let mut data = offset.x;
|
||||
data += (offset.y) << 6;
|
||||
data += (vert as u32) << (6 + 6);
|
||||
data += tex << (6 + 6 + 4);
|
||||
|
||||
return data;
|
||||
}
|
||||
40
engine/world_generation/src/tile_manager.rs
Normal file
40
engine/world_generation/src/tile_manager.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::{
|
||||
asset::{Asset, Handle},
|
||||
ecs::system::Resource,
|
||||
reflect::TypePath,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(Resource, Debug)]
|
||||
pub struct TileManager {
|
||||
pub tiles: Vec<Handle<TileAsset>>,
|
||||
}
|
||||
|
||||
impl Default for TileManager {
|
||||
fn default() -> Self {
|
||||
Self { tiles: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
impl TileManager {
|
||||
pub fn register_tile(&mut self, tile: Handle<TileAsset>) -> usize {
|
||||
let id = self.tiles.len();
|
||||
self.tiles.push(tile);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, TypePath, Asset)]
|
||||
pub struct TileAsset {
|
||||
#[serde(skip)]
|
||||
pub id: usize,
|
||||
pub name: String,
|
||||
pub texture_id: u32,
|
||||
#[serde(skip)]
|
||||
pub texture: String,
|
||||
pub side_texture_id: u32,
|
||||
#[serde(skip)]
|
||||
pub side_texture: String,
|
||||
}
|
||||
|
||||
create_asset_loader!(TileAssetPlugin, TileAssetLoader, TileAsset, TileAssetLoadState, &["tile.json"],;?);
|
||||
41
engine/world_generation/src/tile_mapper.rs
Normal file
41
engine/world_generation/src/tile_mapper.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::prelude::*;
|
||||
use bevy::{
|
||||
asset::{Asset, Handle},
|
||||
reflect::TypePath,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::tile_manager::TileAsset;
|
||||
|
||||
pub struct TileMapper;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, TypePath, Asset)]
|
||||
pub struct TileMapperAsset {
|
||||
#[serde(skip)]
|
||||
pub tiles: Vec<Handle<TileAsset>>,
|
||||
pub tiles_path: Vec<String>,
|
||||
pub thresholds: Vec<f32>,
|
||||
}
|
||||
|
||||
impl TileMapperAsset {
|
||||
pub fn sample_tile(&self, height: f32) -> Handle<TileAsset> {
|
||||
for i in 0..self.thresholds.len() {
|
||||
let t = self.thresholds[i];
|
||||
if t >= height {
|
||||
return self.tiles[i].clone();
|
||||
}
|
||||
}
|
||||
return self.tiles.last().unwrap().clone();
|
||||
}
|
||||
}
|
||||
|
||||
create_asset_loader!(
|
||||
TileMapperAssetPlugin,
|
||||
TileMapperAssetLoader,
|
||||
TileMapperAsset,
|
||||
TileMapperLoadState,
|
||||
&["mapper.json"],;
|
||||
tiles_path -> tiles
|
||||
?
|
||||
);
|
||||
@@ -1,122 +0,0 @@
|
||||
use crate::prelude::PhosCamera;
|
||||
use bevy::input::mouse::MouseMotion;
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::CursorGrabMode;
|
||||
|
||||
pub mod prelude {
|
||||
use bevy::prelude::Component;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct PhosCamera {
|
||||
pub min_height: f32,
|
||||
pub max_height: f32,
|
||||
pub speed: f32,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PhosCameraPlugin;
|
||||
|
||||
impl Plugin for PhosCameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, setup).add_systems(
|
||||
Update,
|
||||
(grab_mouse, (update_camera, update_camera_mouse).chain()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0., 30., 0.)
|
||||
.looking_at(Vec3::new(1000., 0., 1000.), Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
PhosCamera {
|
||||
speed: 100.,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
fn update_camera(
|
||||
mut cam_query: Query<(&PhosCamera, &mut Transform)>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let (cam, mut transform) = cam_query.single_mut();
|
||||
|
||||
let mut move_vec = Vec3::ZERO;
|
||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||
move_vec += Vec3::NEG_X;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyD) {
|
||||
move_vec += Vec3::X;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyW) {
|
||||
move_vec += Vec3::NEG_Z;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyS) {
|
||||
move_vec += Vec3::Z;
|
||||
}
|
||||
|
||||
let rot = transform.rotation;
|
||||
move_vec = (rot * move_vec.normalize_or_zero()) * cam.speed * time.delta_seconds();
|
||||
|
||||
if keyboard_input.pressed(KeyCode::ShiftLeft) {
|
||||
move_vec += Vec3::from(transform.down());
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::Space) {
|
||||
move_vec += Vec3::from(transform.up());
|
||||
}
|
||||
|
||||
transform.translation += move_vec.normalize_or_zero() * cam.speed * time.delta_seconds();
|
||||
}
|
||||
|
||||
fn update_camera_mouse(
|
||||
mut cam_query: Query<&mut Transform, With<PhosCamera>>,
|
||||
mut mouse_move: EventReader<MouseMotion>,
|
||||
time: Res<Time>,
|
||||
windows: Query<&Window>,
|
||||
) {
|
||||
let window = windows.single();
|
||||
if window.cursor.grab_mode != CursorGrabMode::Locked {
|
||||
return;
|
||||
}
|
||||
let mut transform = cam_query.single_mut();
|
||||
|
||||
for ev in mouse_move.read() {
|
||||
let (mut yaw, mut pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
|
||||
match window.cursor.grab_mode {
|
||||
CursorGrabMode::None => (),
|
||||
_ => {
|
||||
// Using smallest of height or width ensures equal vertical and horizontal sensitivity
|
||||
pitch -= (ev.delta.y * time.delta_seconds() * 5.).to_radians();
|
||||
yaw -= (ev.delta.x * time.delta_seconds() * 5.).to_radians();
|
||||
}
|
||||
}
|
||||
|
||||
pitch = pitch.clamp(-1.54, 1.54);
|
||||
|
||||
// Order is important to prevent unintended roll
|
||||
transform.rotation =
|
||||
Quat::from_axis_angle(Vec3::Y, yaw) * Quat::from_axis_angle(Vec3::X, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
fn grab_mouse(
|
||||
mut windows: Query<&mut Window>,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
let mut window = windows.single_mut();
|
||||
|
||||
if mouse.just_pressed(MouseButton::Middle) {
|
||||
window.cursor.visible = false;
|
||||
window.cursor.grab_mode = CursorGrabMode::Locked;
|
||||
}
|
||||
|
||||
if key.just_pressed(KeyCode::Escape) {
|
||||
window.cursor.visible = true;
|
||||
window.cursor.grab_mode = CursorGrabMode::None;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,19 @@
|
||||
name = "phos"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.13.1"
|
||||
bevy = "0.13.2"
|
||||
bevy-inspector-egui = "0.23.4"
|
||||
iyes_perf_ui = "0.2.3"
|
||||
noise = "0.8.2"
|
||||
world_generation ={path="../../engine/world_generation"}
|
||||
camera_system={path = "../camera_system"}
|
||||
rayon = "1.10.0"
|
||||
bevy_xpbd_3d = { version= "0.4.2", features= ["simd", "parallel"] }
|
||||
|
||||
|
||||
[features]
|
||||
tracing = ["bevy/trace_tracy", "world_generation/tracing"]
|
||||
|
||||
1
game/main/assets
Submodule
1
game/main/assets
Submodule
Submodule game/main/assets added at 896ed4eede
@@ -1,91 +0,0 @@
|
||||
#import bevy_pbr::{
|
||||
pbr_fragment::pbr_input_from_standard_material,
|
||||
pbr_functions::alpha_discard,
|
||||
}
|
||||
#import bevy_pbr::mesh_functions::{mesh_position_local_to_world,get_model_matrix,mesh_normal_local_to_world}
|
||||
#import bevy_pbr::view_transformations::position_world_to_clip;
|
||||
|
||||
#ifdef PREPASS_PIPELINE
|
||||
#import bevy_pbr::{
|
||||
prepass_io::{VertexOutput, FragmentOutput},
|
||||
pbr_deferred_functions::deferred_output,
|
||||
}
|
||||
#else
|
||||
#import bevy_pbr::{
|
||||
forward_io::{VertexOutput, FragmentOutput},
|
||||
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@group(2) @binding(100) var array_texture: texture_2d_array<f32>;
|
||||
@group(2) @binding(101) var array_texture_sampler: sampler;
|
||||
@fragment
|
||||
fn fragment(
|
||||
in: VertexOutput,
|
||||
@builtin(front_facing) is_front: bool,
|
||||
) -> FragmentOutput {
|
||||
// var vin : VertexOutput;
|
||||
// vin.position = in.position;
|
||||
// vin.world_position = in.world_position;
|
||||
// vin.world_normal = in.world_normal;
|
||||
// vin.uv = in.uv;
|
||||
|
||||
// generate a PbrInput struct from the StandardMaterial bindings
|
||||
var pbr_input = pbr_input_from_standard_material(in, is_front);
|
||||
|
||||
|
||||
// alpha discard
|
||||
pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
|
||||
|
||||
#ifdef PREPASS_PIPELINE
|
||||
// in deferred mode we can't modify anything after that, as lighting is run in a separate fullscreen shader.
|
||||
let out = deferred_output(in, pbr_input);
|
||||
#else
|
||||
var out: FragmentOutput;
|
||||
// apply lighting
|
||||
|
||||
let index = floor(in.uv.x - 1) + 1;
|
||||
var uv = in.uv;
|
||||
uv.x = in.uv.x - index;
|
||||
out.color = textureSample(array_texture, array_texture_sampler, uv, u32(index));
|
||||
|
||||
out.color *= apply_pbr_lighting(pbr_input);
|
||||
|
||||
|
||||
|
||||
// apply in-shader post processing (fog, alpha-premultiply, and also tonemapping, debanding if the camera is non-hdr)
|
||||
// note this does not include fullscreen postprocessing effects like bloom.
|
||||
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
|
||||
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
//struct Vertex {
|
||||
// @builtin(instance_index) instance_index: u32,
|
||||
// @location(0) position: vec3<f32>,
|
||||
// @location(1) uv: vec2<f32>,
|
||||
// @location(2) normal: vec3<f32>,
|
||||
// @location(3) texture_index: u32,
|
||||
//};
|
||||
//
|
||||
//struct VOut {
|
||||
// @builtin(position) position: vec4<f32>,
|
||||
// @location(0) world_position: vec4<f32>,
|
||||
// @location(1) world_normal: vec3<f32>,
|
||||
// @location(2) uv: vec2<f32>,
|
||||
//// @location(7) @interpolate(flat) texture_index: u32,
|
||||
//};
|
||||
//
|
||||
//@vertex
|
||||
//fn vertex(vertex: Vertex) -> VOut {
|
||||
// var out: VOut;
|
||||
// out.world_position = mesh_position_local_to_world(get_model_matrix(vertex.instance_index), vec4<f32>(vertex.position, 1.0));
|
||||
// out.position = position_world_to_clip(out.world_position.xyz);
|
||||
//// out.texture_index = vertex.texture_index;
|
||||
// out.uv = vertex.uv;
|
||||
// out.world_normal = mesh_normal_local_to_world(vertex.normal, vertex.instance_index);
|
||||
// return out;
|
||||
//}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 740 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
48
game/main/build.rs
Normal file
48
game/main/build.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// A helper function for recursively copying a directory.
|
||||
fn copy_dir<P, Q>(from: P, to: Q)
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let to = to.as_ref().to_path_buf();
|
||||
|
||||
for path in fs::read_dir(from).unwrap() {
|
||||
let path = path.unwrap().path();
|
||||
let to = to.clone().join(path.file_name().unwrap());
|
||||
|
||||
if path.is_file() {
|
||||
fs::copy(&path, to).unwrap();
|
||||
} else if path.is_dir() {
|
||||
if !to.exists() {
|
||||
fs::create_dir(&to).unwrap();
|
||||
}
|
||||
|
||||
copy_dir(&path, to);
|
||||
} else { /* Skip other content */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const COPY_DIR: &'static str = "assets";
|
||||
|
||||
fn main() {
|
||||
// Request the output directory
|
||||
let out = env::var("PROFILE").unwrap();
|
||||
let out = PathBuf::from(format!("../../target/{}/{}", out, COPY_DIR));
|
||||
|
||||
// If it is already in the output directory, delete it and start over
|
||||
if out.exists() {
|
||||
fs::remove_dir_all(&out).unwrap();
|
||||
}
|
||||
|
||||
// Create the out directory
|
||||
fs::create_dir(&out).unwrap();
|
||||
|
||||
// Copy the directory
|
||||
copy_dir(COPY_DIR, &out);
|
||||
}
|
||||
241
game/main/src/camera_system/camera_plugin.rs
Normal file
241
game/main/src/camera_system/camera_plugin.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use bevy::core_pipeline::experimental::taa::{TemporalAntiAliasBundle, TemporalAntiAliasPlugin};
|
||||
use bevy::input::mouse::{MouseMotion, MouseScrollUnit, MouseWheel};
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::CursorGrabMode;
|
||||
use world_generation::hex_utils::HexCoord;
|
||||
use world_generation::prelude::Map;
|
||||
|
||||
use super::components::*;
|
||||
|
||||
pub struct PhosCameraPlugin;
|
||||
|
||||
impl Plugin for PhosCameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<PhosCamera>();
|
||||
|
||||
app.add_systems(PreStartup, setup);
|
||||
|
||||
app.add_systems(Update, rts_camera_system);
|
||||
app.add_systems(PostUpdate, limit_camera_bounds);
|
||||
//Free Cam
|
||||
//app.add_systems(Update, (grab_mouse, (update_camera, update_camera_mouse).chain()));
|
||||
|
||||
app.add_plugins(TemporalAntiAliasPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, mut msaa: ResMut<Msaa>) {
|
||||
commands
|
||||
.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0., 30., 0.).looking_to(Vec3::Z, Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
PhosCamera::default(),
|
||||
PhosCameraTargets::default(),
|
||||
))
|
||||
.insert(TemporalAntiAliasBundle::default());
|
||||
|
||||
*msaa = Msaa::Off;
|
||||
}
|
||||
fn update_camera(
|
||||
mut cam_query: Query<(&PhosCamera, &mut Transform)>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
windows: Query<&Window>,
|
||||
) {
|
||||
let window = windows.single();
|
||||
if window.cursor.grab_mode != CursorGrabMode::Locked {
|
||||
return;
|
||||
}
|
||||
let (cam, mut transform) = cam_query.single_mut();
|
||||
|
||||
let mut move_vec = Vec3::ZERO;
|
||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||
move_vec += Vec3::NEG_X;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyD) {
|
||||
move_vec += Vec3::X;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyW) {
|
||||
move_vec += Vec3::NEG_Z;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyS) {
|
||||
move_vec += Vec3::Z;
|
||||
}
|
||||
|
||||
let rot = transform.rotation;
|
||||
move_vec = (rot * move_vec.normalize_or_zero()) * cam.speed * time.delta_seconds();
|
||||
|
||||
if keyboard_input.pressed(KeyCode::ShiftLeft) {
|
||||
move_vec += Vec3::from(transform.down());
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::Space) {
|
||||
move_vec += Vec3::from(transform.up());
|
||||
}
|
||||
|
||||
transform.translation += move_vec.normalize_or_zero() * cam.speed * time.delta_seconds();
|
||||
}
|
||||
|
||||
fn update_camera_mouse(
|
||||
mut cam_query: Query<&mut Transform, With<PhosCamera>>,
|
||||
mut mouse_move: EventReader<MouseMotion>,
|
||||
time: Res<Time>,
|
||||
windows: Query<&Window>,
|
||||
) {
|
||||
let window = windows.single();
|
||||
if window.cursor.grab_mode != CursorGrabMode::Locked {
|
||||
return;
|
||||
}
|
||||
let mut transform = cam_query.single_mut();
|
||||
|
||||
for ev in mouse_move.read() {
|
||||
let (mut yaw, mut pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
|
||||
match window.cursor.grab_mode {
|
||||
CursorGrabMode::None => (),
|
||||
_ => {
|
||||
// Using smallest of height or width ensures equal vertical and horizontal sensitivity
|
||||
pitch -= ev.delta.y.to_radians() * time.delta_seconds() * 5.;
|
||||
yaw -= ev.delta.x.to_radians() * time.delta_seconds() * 5.;
|
||||
}
|
||||
}
|
||||
|
||||
pitch = pitch.clamp(-1.54, 1.54);
|
||||
|
||||
// Order is important to prevent unintended roll
|
||||
transform.rotation = Quat::from_axis_angle(Vec3::Y, yaw) * Quat::from_axis_angle(Vec3::X, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
fn grab_mouse(mut windows: Query<&mut Window>, mouse: Res<ButtonInput<MouseButton>>, key: Res<ButtonInput<KeyCode>>) {
|
||||
let mut window = windows.single_mut();
|
||||
|
||||
if mouse.just_pressed(MouseButton::Middle) {
|
||||
window.cursor.visible = false;
|
||||
window.cursor.grab_mode = CursorGrabMode::Locked;
|
||||
}
|
||||
|
||||
if key.just_pressed(KeyCode::Escape) {
|
||||
window.cursor.visible = true;
|
||||
window.cursor.grab_mode = CursorGrabMode::None;
|
||||
}
|
||||
}
|
||||
|
||||
fn rts_camera_system(
|
||||
mut cam_query: Query<(&mut Transform, &PhosCamera, &mut PhosCameraTargets)>,
|
||||
mut wheel: EventReader<MouseWheel>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
heightmap: Res<Map>,
|
||||
) {
|
||||
let (mut cam, cam_cfg, mut cam_targets) = cam_query.single_mut();
|
||||
let mut cam_move = Vec3::ZERO;
|
||||
let mut cam_pos = cam.translation;
|
||||
|
||||
if key.pressed(KeyCode::KeyA) {
|
||||
cam_move.x = 1.;
|
||||
} else if key.pressed(KeyCode::KeyD) {
|
||||
cam_move.x = -1.;
|
||||
}
|
||||
|
||||
if key.pressed(KeyCode::KeyW) {
|
||||
cam_move.z = 1.;
|
||||
} else if key.pressed(KeyCode::KeyS) {
|
||||
cam_move.z = -1.;
|
||||
}
|
||||
|
||||
let move_speed = if key.pressed(KeyCode::ShiftLeft) {
|
||||
cam_cfg.speed * 2.
|
||||
} else {
|
||||
cam_cfg.speed
|
||||
};
|
||||
|
||||
cam_move = cam_move.normalize_or_zero() * move_speed * time.delta_seconds();
|
||||
cam_pos -= cam_move;
|
||||
|
||||
let mut scroll = 0.0;
|
||||
for e in wheel.read() {
|
||||
match e.unit {
|
||||
MouseScrollUnit::Line => scroll += e.y * 5.,
|
||||
MouseScrollUnit::Pixel => scroll += e.y,
|
||||
}
|
||||
}
|
||||
|
||||
let ground_height = sample_ground(cam.translation, &heightmap);
|
||||
|
||||
cam_targets.height -= scroll;
|
||||
if cam_targets.height > cam_cfg.max_height {
|
||||
cam_targets.height = cam_cfg.max_height;
|
||||
}
|
||||
|
||||
let min_height = ground_height + cam_cfg.min_height;
|
||||
|
||||
if min_height != cam_targets.last_height {
|
||||
cam_targets.last_height = min_height;
|
||||
cam_targets.anim_time = 0.;
|
||||
cam_targets.rotate_time = 0.;
|
||||
}
|
||||
|
||||
if scroll != 0. {
|
||||
cam_targets.anim_time = 0.;
|
||||
cam_targets.rotate_time = 0.;
|
||||
if cam_targets.height < min_height {
|
||||
cam_targets.height = min_height;
|
||||
}
|
||||
}
|
||||
|
||||
let desired_height = if cam_targets.height < min_height {
|
||||
min_height
|
||||
} else {
|
||||
cam_targets.height
|
||||
};
|
||||
if cam_targets.anim_time < 1. {
|
||||
cam_targets.anim_time += time.delta_seconds() * cam_cfg.zoom_speed;
|
||||
cam_targets.anim_time = cam_targets.anim_time.min(1.);
|
||||
}
|
||||
cam_pos.y = f32::lerp(cam_pos.y, desired_height, cam_targets.anim_time);
|
||||
let t = cam_pos.y.remap(cam_cfg.min_height, cam_cfg.max_height, 0., 1.);
|
||||
|
||||
if cam_targets.rotate_time < 1. {
|
||||
cam_targets.rotate_time += time.delta_seconds();
|
||||
cam_targets.rotate_time = cam_targets.rotate_time.min(1.);
|
||||
}
|
||||
let angle = cam_cfg.min_angle.lerp(cam_cfg.max_angle, t);
|
||||
let rot = Quat::from_axis_angle(Vec3::X, -angle);
|
||||
cam.rotation = rot;
|
||||
|
||||
cam.translation = cam_pos;
|
||||
}
|
||||
|
||||
fn sample_ground(pos: Vec3, heightmap: &Map) -> f32 {
|
||||
let tile_under = HexCoord::from_world_pos(pos);
|
||||
let neighbors = heightmap.get_neighbors(&tile_under);
|
||||
let mut ground_height = if heightmap.is_in_bounds(&tile_under) {
|
||||
heightmap.sample_height(&tile_under)
|
||||
} else {
|
||||
heightmap.sea_level
|
||||
};
|
||||
|
||||
for n in neighbors {
|
||||
if let Some(h) = n {
|
||||
if h > ground_height {
|
||||
ground_height = h;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ground_height < heightmap.sea_level {
|
||||
ground_height = heightmap.sea_level;
|
||||
}
|
||||
return ground_height;
|
||||
}
|
||||
|
||||
fn limit_camera_bounds(mut cam_query: Query<(&mut Transform, &CameraBounds)>) {
|
||||
let (mut tranform, bounds) = cam_query.single_mut();
|
||||
|
||||
let mut pos = tranform.translation;
|
||||
|
||||
pos.x = pos.x.clamp(bounds.min.x, bounds.max.x);
|
||||
pos.z = pos.z.clamp(bounds.min.y, bounds.max.y);
|
||||
|
||||
tranform.translation = pos;
|
||||
}
|
||||
69
game/main/src/camera_system/components.rs
Normal file
69
game/main/src/camera_system/components.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use bevy::prelude::*;
|
||||
use world_generation::{
|
||||
hex_utils::{tile_to_world_distance, SHORT_DIAGONAL},
|
||||
prelude::Chunk,
|
||||
};
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct PhosCamera {
|
||||
pub min_height: f32,
|
||||
pub max_height: f32,
|
||||
pub speed: f32,
|
||||
pub zoom_speed: f32,
|
||||
pub min_angle: f32,
|
||||
pub max_angle: f32,
|
||||
}
|
||||
|
||||
impl Default for PhosCamera {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
min_height: 10.,
|
||||
max_height: 120.,
|
||||
speed: 30.,
|
||||
zoom_speed: 0.3,
|
||||
min_angle: (20. as f32).to_radians(),
|
||||
max_angle: 1.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct PhosCameraTargets {
|
||||
pub height: f32,
|
||||
pub forward: Vec3,
|
||||
pub last_height: f32,
|
||||
pub anim_time: f32,
|
||||
pub rotate_time: f32,
|
||||
}
|
||||
|
||||
impl Default for PhosCameraTargets {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
height: Default::default(),
|
||||
forward: Vec3::Z,
|
||||
last_height: Default::default(),
|
||||
anim_time: Default::default(),
|
||||
rotate_time: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct CameraBounds {
|
||||
pub min: Vec2,
|
||||
pub max: Vec2,
|
||||
}
|
||||
|
||||
impl CameraBounds {
|
||||
pub fn from_size(size: UVec2) -> Self {
|
||||
let padding = Chunk::WORLD_SIZE;
|
||||
return Self {
|
||||
min: Vec2::ZERO - padding,
|
||||
max: Vec2::new(
|
||||
(size.x as usize * Chunk::SIZE) as f32 * SHORT_DIAGONAL,
|
||||
(size.y * Chunk::SIZE as u32) as f32 * 1.5,
|
||||
) + padding,
|
||||
};
|
||||
}
|
||||
}
|
||||
2
game/main/src/camera_system/mod.rs
Normal file
2
game/main/src/camera_system/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod camera_plugin;
|
||||
pub mod components;
|
||||
1
game/main/src/macros.rs
Normal file
1
game/main/src/macros.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
use bevy::pbr::wireframe::WireframePlugin;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::texture::{ImageAddressMode, ImageFilterMode, ImageSamplerDescriptor};
|
||||
use bevy::window::PresentMode;
|
||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||
use phos::PhosGamePlugin;
|
||||
|
||||
mod camera_system;
|
||||
mod map_rendering;
|
||||
mod phos;
|
||||
mod prelude;
|
||||
|
||||
use phos::PhosGamePlugin;
|
||||
mod shader_extensions;
|
||||
mod utlis;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Phos".into(),
|
||||
name: Some("phos".into()),
|
||||
resolution: (1920.0, 1080.0).into(),
|
||||
resizable: true,
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
DefaultPlugins
|
||||
.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Phos".into(),
|
||||
name: Some("phos".into()),
|
||||
// resolution: (1920., 1080.).into(),
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
mode: bevy::window::WindowMode::BorderlessFullscreen,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
})
|
||||
.set(ImagePlugin {
|
||||
default_sampler: ImageSamplerDescriptor {
|
||||
address_mode_u: ImageAddressMode::Repeat,
|
||||
address_mode_v: ImageAddressMode::Repeat,
|
||||
mag_filter: ImageFilterMode::Nearest,
|
||||
..default()
|
||||
},
|
||||
}),
|
||||
..default()
|
||||
}).set(ImagePlugin {
|
||||
default_sampler: ImageSamplerDescriptor {
|
||||
address_mode_u: ImageAddressMode::Repeat,
|
||||
address_mode_v: ImageAddressMode::Repeat,
|
||||
mag_filter: ImageFilterMode::Nearest,
|
||||
..default()
|
||||
}
|
||||
}),
|
||||
WorldInspectorPlugin::new(),
|
||||
WireframePlugin,
|
||||
PhosGamePlugin,
|
||||
))
|
||||
.run();
|
||||
|
||||
97
game/main/src/map_rendering/chunk_rebuild.rs
Normal file
97
game/main/src/map_rendering/chunk_rebuild.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_xpbd_3d::plugins::collision::Collider;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use world_generation::{
|
||||
biome_painter::BiomePainterAsset,
|
||||
chunk_colliders::generate_chunk_collider,
|
||||
hex_utils::{self, offset_to_world, SHORT_DIAGONAL},
|
||||
mesh_generator::generate_chunk_mesh,
|
||||
prelude::{Chunk, Map},
|
||||
tile_manager::TileAsset,
|
||||
tile_mapper::TileMapperAsset,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
prelude::{ChunkAtlas, PhosChunk, PhosChunkRegistry},
|
||||
utlis::render_distance_system::RenderDistanceVisibility,
|
||||
};
|
||||
|
||||
use super::prelude::CurrentBiomePainter;
|
||||
pub struct ChunkRebuildPlugin;
|
||||
|
||||
impl Plugin for ChunkRebuildPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(ChunkRebuildQueue::default());
|
||||
app.init_resource::<PhosChunkRegistry>();
|
||||
app.add_systems(PreUpdate, chunk_rebuilder);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ChunkRebuildQueue {
|
||||
pub queue: Vec<usize>,
|
||||
}
|
||||
|
||||
fn chunk_rebuilder(
|
||||
mut commands: Commands,
|
||||
mut queue: ResMut<ChunkRebuildQueue>,
|
||||
mut chunks: ResMut<PhosChunkRegistry>,
|
||||
atlas: Res<ChunkAtlas>,
|
||||
heightmap: Res<Map>,
|
||||
tile_assets: Res<Assets<TileAsset>>,
|
||||
tile_mappers: Res<Assets<TileMapperAsset>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
biome_painters: Res<Assets<BiomePainterAsset>>,
|
||||
painter: Res<CurrentBiomePainter>,
|
||||
) {
|
||||
if queue.queue.len() == 0 {
|
||||
return;
|
||||
}
|
||||
queue.queue.dedup();
|
||||
|
||||
for chunk_index in &queue.queue {
|
||||
let chunk = chunks.chunks[*chunk_index];
|
||||
commands.entity(chunk).despawn();
|
||||
}
|
||||
|
||||
let b_painter = biome_painters.get(painter.handle.clone());
|
||||
|
||||
let cur_painter = b_painter.unwrap();
|
||||
|
||||
let chunk_meshes: Vec<_> = queue
|
||||
.queue
|
||||
.par_iter()
|
||||
.map(|idx| {
|
||||
let chunk = &heightmap.chunks[*idx];
|
||||
let mesh = generate_chunk_mesh(chunk, &heightmap, cur_painter, &tile_assets, &tile_mappers);
|
||||
let (col_verts, col_indicies) = generate_chunk_collider(chunk, &heightmap);
|
||||
let collider = Collider::trimesh(col_verts, col_indicies);
|
||||
return (
|
||||
mesh,
|
||||
collider,
|
||||
offset_to_world(chunk.chunk_offset * Chunk::SIZE as i32, 0.),
|
||||
hex_utils::offset_to_index(chunk.chunk_offset, heightmap.width),
|
||||
);
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (mesh, collider, pos, index) in chunk_meshes {
|
||||
let chunk = commands.spawn((
|
||||
MaterialMeshBundle {
|
||||
mesh: meshes.add(mesh),
|
||||
material: atlas.chunk_material_handle.clone(),
|
||||
transform: Transform::from_translation(pos),
|
||||
..default()
|
||||
},
|
||||
PhosChunk::new(index),
|
||||
RenderDistanceVisibility::default().with_offset(Vec3::new(
|
||||
(Chunk::SIZE / 2) as f32 * SHORT_DIAGONAL,
|
||||
0.,
|
||||
(Chunk::SIZE / 2) as f32 * 1.5,
|
||||
)),
|
||||
collider,
|
||||
));
|
||||
chunks.chunks[index] = chunk.id();
|
||||
}
|
||||
queue.queue.clear();
|
||||
}
|
||||
296
game/main/src/map_rendering/map_init.rs
Normal file
296
game/main/src/map_rendering/map_init.rs
Normal file
@@ -0,0 +1,296 @@
|
||||
#[cfg(feature = "tracing")]
|
||||
use bevy::log::*;
|
||||
use bevy::{asset::LoadState, pbr::ExtendedMaterial, prelude::*};
|
||||
use bevy_inspector_egui::quick::ResourceInspectorPlugin;
|
||||
use bevy_xpbd_3d::plugins::collision::Collider;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use world_generation::{
|
||||
biome_painter::*,
|
||||
chunk_colliders::generate_chunk_collider,
|
||||
heightmap::generate_heightmap,
|
||||
hex_utils::{self, offset_to_world, SHORT_DIAGONAL},
|
||||
mesh_generator::generate_chunk_mesh,
|
||||
prelude::*,
|
||||
tile_manager::*,
|
||||
tile_mapper::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
camera_system::components::*,
|
||||
prelude::{ChunkAtlas, PhosChunk, PhosChunkRegistry, PhosMap},
|
||||
shader_extensions::chunk_material::ChunkMaterial,
|
||||
utlis::render_distance_system::RenderDistanceVisibility,
|
||||
};
|
||||
|
||||
use super::{
|
||||
chunk_rebuild::ChunkRebuildPlugin, prelude::CurrentBiomePainter, terraforming_test::TerraFormingTestPlugin,
|
||||
};
|
||||
|
||||
pub struct MapInitPlugin;
|
||||
|
||||
impl Plugin for MapInitPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((
|
||||
ResourceInspectorPlugin::<PhosMap>::default(),
|
||||
ResourceInspectorPlugin::<GenerationConfig>::default(),
|
||||
ChunkRebuildPlugin,
|
||||
TerraFormingTestPlugin,
|
||||
));
|
||||
|
||||
app.add_systems(Startup, (load_textures, load_tiles));
|
||||
app.add_systems(PostStartup, create_map);
|
||||
|
||||
app.add_systems(Update, finalize_texture);
|
||||
app.add_systems(PostUpdate, (despawn_map, spawn_map).chain());
|
||||
app.insert_resource(TileManager::default());
|
||||
app.insert_resource(PhosMap::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn load_textures(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut standard_materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let main_tex = asset_server.load("textures/world/stack.png");
|
||||
|
||||
let water_material = standard_materials.add(StandardMaterial {
|
||||
base_color: Color::AQUAMARINE.with_a(0.5),
|
||||
alpha_mode: AlphaMode::Blend,
|
||||
..default()
|
||||
});
|
||||
commands.insert_resource(ChunkAtlas {
|
||||
handle: main_tex.clone(),
|
||||
is_loaded: false,
|
||||
chunk_material_handle: Handle::default(),
|
||||
water_material: water_material,
|
||||
});
|
||||
}
|
||||
|
||||
fn load_tiles(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let handle: Handle<BiomePainterAsset> = asset_server.load("biome_painters/terra.biomes.json");
|
||||
commands.insert_resource(CurrentBiomePainter { handle });
|
||||
}
|
||||
|
||||
fn finalize_texture(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut atlas: ResMut<ChunkAtlas>,
|
||||
mut map: ResMut<PhosMap>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
painter: Res<CurrentBiomePainter>,
|
||||
painter_load: Res<BiomePainterLoadState>,
|
||||
tile_load: Res<TileAssetLoadState>,
|
||||
mut chunk_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, ChunkMaterial>>>,
|
||||
mapper_load: Res<TileMapperLoadState>,
|
||||
) {
|
||||
if atlas.is_loaded {
|
||||
return;
|
||||
}
|
||||
|
||||
if !painter_load.is_all_loaded() || !tile_load.is_all_loaded() || !mapper_load.is_all_loaded() {
|
||||
return;
|
||||
}
|
||||
|
||||
if asset_server.load_state(atlas.handle.clone()) != LoadState::Loaded {
|
||||
return;
|
||||
}
|
||||
if asset_server.load_state(painter.handle.clone()) != LoadState::Loaded {
|
||||
return;
|
||||
}
|
||||
let image = images.get_mut(&atlas.handle).unwrap();
|
||||
|
||||
let array_layers = 14;
|
||||
image.reinterpret_stacked_2d_as_array(array_layers);
|
||||
|
||||
atlas.is_loaded = true;
|
||||
let chunk_material = chunk_materials.add(ExtendedMaterial {
|
||||
base: StandardMaterial::default(),
|
||||
extension: ChunkMaterial {
|
||||
array_texture: atlas.handle.clone(),
|
||||
},
|
||||
});
|
||||
atlas.chunk_material_handle = chunk_material;
|
||||
map.ready = true;
|
||||
map.regenerate = true;
|
||||
}
|
||||
|
||||
fn create_map(mut commands: Commands, mut cam: Query<(&mut Transform, Entity), With<PhosCamera>>) {
|
||||
let config = GenerationConfig {
|
||||
layers: vec![
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.14,
|
||||
roughness: 0.87,
|
||||
strength: 8.3,
|
||||
min_value: -0.2,
|
||||
persistence: 0.77,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 4,
|
||||
first_layer_mask: false,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.85,
|
||||
roughness: 2.,
|
||||
strength: -0.23,
|
||||
min_value: -0.,
|
||||
persistence: 1.,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 4,
|
||||
first_layer_mask: false,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.6,
|
||||
roughness: 4.,
|
||||
strength: 3.1,
|
||||
min_value: 0.,
|
||||
persistence: 1.57,
|
||||
is_rigid: true,
|
||||
weight: 1.,
|
||||
weight_multi: 0.35,
|
||||
layers: 4,
|
||||
first_layer_mask: true,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 3.87,
|
||||
roughness: 5.8,
|
||||
strength: -1.,
|
||||
min_value: 0.,
|
||||
persistence: 0.,
|
||||
is_rigid: true,
|
||||
weight: 1.,
|
||||
weight_multi: 4.57,
|
||||
layers: 3,
|
||||
first_layer_mask: true,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 3.87,
|
||||
roughness: 5.8,
|
||||
strength: -1.5,
|
||||
min_value: 0.,
|
||||
persistence: 0.3,
|
||||
is_rigid: true,
|
||||
weight: 1.,
|
||||
weight_multi: 4.57,
|
||||
layers: 3,
|
||||
first_layer_mask: false,
|
||||
},
|
||||
],
|
||||
noise_scale: 450.,
|
||||
sea_level: 8.5,
|
||||
border_size: 64.,
|
||||
//size: UVec2::splat(1024 / Chunk::SIZE as u32),
|
||||
size: UVec2::splat(2),
|
||||
};
|
||||
let heightmap = generate_heightmap(&config, 4);
|
||||
|
||||
let (mut cam_t, cam_entity) = cam.single_mut();
|
||||
cam_t.translation = heightmap.get_center();
|
||||
|
||||
commands.entity(cam_entity).insert(CameraBounds::from_size(config.size));
|
||||
|
||||
commands.insert_resource(heightmap);
|
||||
commands.insert_resource(config);
|
||||
}
|
||||
|
||||
fn spawn_map(
|
||||
heightmap: Res<Map>,
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
atlas: Res<ChunkAtlas>,
|
||||
mut map: ResMut<PhosMap>,
|
||||
tile_assets: Res<Assets<TileAsset>>,
|
||||
tile_mappers: Res<Assets<TileMapperAsset>>,
|
||||
biome_painters: Res<Assets<BiomePainterAsset>>,
|
||||
painter: Res<CurrentBiomePainter>,
|
||||
) {
|
||||
if !map.ready || !map.regenerate {
|
||||
return;
|
||||
}
|
||||
let b_painter = biome_painters.get(painter.handle.clone());
|
||||
map.regenerate = false;
|
||||
|
||||
let cur_painter = b_painter.unwrap();
|
||||
|
||||
let chunk_meshes: Vec<_> = heightmap
|
||||
.chunks
|
||||
.par_iter()
|
||||
.map(|chunk: &Chunk| {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _gen_mesh = info_span!("Generate Chunk").entered();
|
||||
let mesh = generate_chunk_mesh(chunk, &heightmap, cur_painter, &tile_assets, &tile_mappers);
|
||||
let (col_verts, col_indicies) = generate_chunk_collider(chunk, &heightmap);
|
||||
let collider: Collider;
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _collider_span = info_span!("Create Collider Trimesh").entered();
|
||||
collider = Collider::trimesh(col_verts, col_indicies);
|
||||
}
|
||||
return (
|
||||
mesh,
|
||||
collider,
|
||||
offset_to_world(chunk.chunk_offset * Chunk::SIZE as i32, 0.),
|
||||
hex_utils::offset_to_index(chunk.chunk_offset, heightmap.width),
|
||||
);
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut registry = PhosChunkRegistry::new(chunk_meshes.len());
|
||||
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _spawn_span = info_span!("Spawn Chunks").entered();
|
||||
let visibility_offset = Vec3::new(
|
||||
(Chunk::SIZE / 2) as f32 * SHORT_DIAGONAL,
|
||||
0.,
|
||||
(Chunk::SIZE / 2) as f32 * 1.5,
|
||||
);
|
||||
for (mesh, collider, pos, index) in chunk_meshes {
|
||||
// let mesh_handle = meshes.a
|
||||
let chunk = commands.spawn((
|
||||
MaterialMeshBundle {
|
||||
mesh: meshes.add(mesh),
|
||||
material: atlas.chunk_material_handle.clone(),
|
||||
transform: Transform::from_translation(pos),
|
||||
..default()
|
||||
},
|
||||
PhosChunk::new(index),
|
||||
RenderDistanceVisibility::default().with_offset(visibility_offset),
|
||||
collider,
|
||||
));
|
||||
registry.chunks.push(chunk.id());
|
||||
}
|
||||
}
|
||||
|
||||
commands.spawn((PbrBundle {
|
||||
transform: Transform::from_translation(heightmap.get_center()),
|
||||
mesh: meshes.add(
|
||||
Plane3d::default()
|
||||
.mesh()
|
||||
.size(heightmap.get_world_width(), heightmap.get_world_height()),
|
||||
),
|
||||
material: atlas.water_material.clone(),
|
||||
..default()
|
||||
},));
|
||||
|
||||
commands.insert_resource(registry);
|
||||
}
|
||||
|
||||
fn despawn_map(
|
||||
mut commands: Commands,
|
||||
mut heightmap: ResMut<Map>,
|
||||
cfg: Res<GenerationConfig>,
|
||||
map: Res<PhosMap>,
|
||||
chunks: Query<Entity, With<PhosChunk>>,
|
||||
) {
|
||||
if !map.regenerate {
|
||||
return;
|
||||
}
|
||||
for chunk in chunks.iter() {
|
||||
commands.entity(chunk).despawn();
|
||||
}
|
||||
|
||||
*heightmap = generate_heightmap(&cfg, 4);
|
||||
}
|
||||
4
game/main/src/map_rendering/mod.rs
Normal file
4
game/main/src/map_rendering/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod chunk_rebuild;
|
||||
pub mod map_init;
|
||||
pub mod prelude;
|
||||
pub mod terraforming_test;
|
||||
7
game/main/src/map_rendering/prelude.rs
Normal file
7
game/main/src/map_rendering/prelude.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use bevy::prelude::*;
|
||||
use world_generation::biome_painter::BiomePainterAsset;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct CurrentBiomePainter {
|
||||
pub handle: Handle<BiomePainterAsset>,
|
||||
}
|
||||
73
game/main/src/map_rendering/terraforming_test.rs
Normal file
73
game/main/src/map_rendering/terraforming_test.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
use bevy_xpbd_3d::plugins::spatial_query::{SpatialQuery, SpatialQueryFilter};
|
||||
use world_generation::{hex_utils::HexCoord, prelude::Map};
|
||||
|
||||
use crate::{camera_system::components::PhosCamera, prelude::PhosChunkRegistry};
|
||||
|
||||
use super::chunk_rebuild::ChunkRebuildQueue;
|
||||
|
||||
pub struct TerraFormingTestPlugin;
|
||||
|
||||
impl Plugin for TerraFormingTestPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Update, deform);
|
||||
}
|
||||
}
|
||||
|
||||
fn deform(
|
||||
cam_query: Query<(&GlobalTransform, &Camera), With<PhosCamera>>,
|
||||
window: Query<&Window, With<PrimaryWindow>>,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
spatial_query: SpatialQuery,
|
||||
mut heightmap: ResMut<Map>,
|
||||
mut rebuild: ResMut<ChunkRebuildQueue>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let mut multi = 0.;
|
||||
if mouse.pressed(MouseButton::Left) {
|
||||
multi = 1.;
|
||||
} else if mouse.pressed(MouseButton::Right) {
|
||||
multi = -1.;
|
||||
}
|
||||
|
||||
if multi == 0. {
|
||||
return;
|
||||
}
|
||||
|
||||
let win = window.single();
|
||||
let (cam_transform, camera) = cam_query.single();
|
||||
let Some(cursor_pos) = win.cursor_position() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(cam_ray) = camera.viewport_to_world(cam_transform, cursor_pos) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let collision = spatial_query.cast_ray(
|
||||
cam_ray.origin,
|
||||
cam_ray.direction.into(),
|
||||
100.,
|
||||
true,
|
||||
SpatialQueryFilter::default(),
|
||||
);
|
||||
|
||||
if let Some(hit) = collision {
|
||||
let contact_point = cam_ray.get_point(hit.time_of_impact);
|
||||
let contact_coord = HexCoord::from_world_pos(contact_point);
|
||||
let cur_height = heightmap.sample_height(&contact_coord);
|
||||
heightmap.set_height(&contact_coord, cur_height + 1. * time.delta_seconds() * multi);
|
||||
let cur_chunk = contact_coord.to_chunk_index(heightmap.width);
|
||||
|
||||
if contact_coord.is_on_chunk_edge() {
|
||||
let neighbors = contact_coord.get_neighbors();
|
||||
let mut other_chunks: Vec<_> = neighbors
|
||||
.iter()
|
||||
.map(|c| c.to_chunk_index(heightmap.width))
|
||||
.filter(|c| c != &cur_chunk)
|
||||
.collect();
|
||||
rebuild.queue.append(&mut other_chunks);
|
||||
}
|
||||
rebuild.queue.push(cur_chunk);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,60 @@
|
||||
use bevy::asset::LoadState;
|
||||
use bevy::pbr::{ExtendedMaterial};
|
||||
use bevy::{pbr::CascadeShadowConfig, prelude::*};
|
||||
use camera_system::PhosCameraPlugin;
|
||||
use iyes_perf_ui::prelude::*;
|
||||
use world_generation::hex_utils::{offset_to_world, HexCoord};
|
||||
use world_generation::{
|
||||
heightmap::generate_heightmap, mesh_generator::generate_chunk_mesh, prelude::*,
|
||||
use crate::camera_system::camera_plugin::PhosCameraPlugin;
|
||||
use crate::camera_system::components::PhosCamera;
|
||||
use crate::map_rendering::map_init::MapInitPlugin;
|
||||
use crate::shader_extensions::chunk_material::ChunkMaterial;
|
||||
use crate::utlis::render_distance_system::RenderDistancePlugin;
|
||||
use bevy::pbr::ExtendedMaterial;
|
||||
use bevy::{
|
||||
pbr::{wireframe::WireframeConfig, CascadeShadowConfig},
|
||||
prelude::*,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use bevy_xpbd_3d::components::{LinearVelocity, RigidBody};
|
||||
use bevy_xpbd_3d::plugins::collision::Collider;
|
||||
use bevy_xpbd_3d::plugins::PhysicsPlugins;
|
||||
use iyes_perf_ui::prelude::*;
|
||||
use world_generation::biome_painter::BiomePainterPlugin;
|
||||
use world_generation::tile_manager::TileAssetPlugin;
|
||||
use world_generation::tile_mapper::TileMapperAssetPlugin;
|
||||
|
||||
pub struct PhosGamePlugin;
|
||||
|
||||
impl Plugin for PhosGamePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(PhosCameraPlugin).add_plugins(MaterialPlugin::<
|
||||
ExtendedMaterial<StandardMaterial, ChunkMaterial>,
|
||||
>::default());
|
||||
app.add_systems(Startup, init_game)
|
||||
.add_systems(Startup, (load_textures, create_map).chain());
|
||||
app.add_systems(Update, (check_texture, spawn_map));
|
||||
app.add_plugins((
|
||||
PhosCameraPlugin,
|
||||
MapInitPlugin,
|
||||
MaterialPlugin::<ExtendedMaterial<StandardMaterial, ChunkMaterial>>::default(),
|
||||
RenderDistancePlugin,
|
||||
));
|
||||
|
||||
//Systems - Startup
|
||||
app.add_systems(Startup, init_game);
|
||||
|
||||
//Systems - Update
|
||||
app.add_systems(Update, spawn_sphere);
|
||||
|
||||
//Perf UI
|
||||
app.add_plugins(bevy::diagnostic::FrameTimeDiagnosticsPlugin)
|
||||
.add_plugins(bevy::diagnostic::EntityCountDiagnosticsPlugin)
|
||||
.add_plugins(bevy::diagnostic::SystemInformationDiagnosticsPlugin)
|
||||
.add_plugins(PerfUiPlugin);
|
||||
|
||||
//Assets
|
||||
app.add_plugins(TileAssetPlugin);
|
||||
app.add_plugins(TileMapperAssetPlugin);
|
||||
app.add_plugins(BiomePainterPlugin);
|
||||
//Physics
|
||||
app.add_plugins(PhysicsPlugins::default());
|
||||
// app.add_plugins(RapierDebugRenderPlugin::default());
|
||||
|
||||
app.insert_resource(WireframeConfig {
|
||||
global: false,
|
||||
default_color: Color::hex("FF0064").unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn init_game(mut commands: Commands) {
|
||||
fn init_game(mut commands: Commands, mut materials: ResMut<Assets<StandardMaterial>>) {
|
||||
commands.spawn((
|
||||
PerfUiRoot::default(),
|
||||
PerfUiEntryFPS::default(),
|
||||
@@ -41,179 +69,43 @@ fn init_game(mut commands: Commands) {
|
||||
..default()
|
||||
},
|
||||
cascade_shadow_config: CascadeShadowConfig {
|
||||
bounds: vec![500., 1000., 2000., 5000.],
|
||||
bounds: vec![200., 400., 600., 800.],
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(500., 260.0, 500.).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.insert_resource(PhosMap::default());
|
||||
let sphere_mat = StandardMaterial {
|
||||
base_color: Color::CYAN,
|
||||
..default()
|
||||
};
|
||||
let handle = materials.add(sphere_mat);
|
||||
commands.insert_resource(SphereMat(handle));
|
||||
}
|
||||
|
||||
fn load_textures(
|
||||
#[derive(Resource)]
|
||||
struct SphereMat(Handle<StandardMaterial>);
|
||||
|
||||
fn spawn_sphere(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let main_tex = asset_server.load("textures/world/stack.png");
|
||||
commands.insert_resource(ChunkAtlas {
|
||||
handle: main_tex.clone(),
|
||||
is_loaded: false,
|
||||
});
|
||||
}
|
||||
|
||||
fn check_texture(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut atlas: ResMut<ChunkAtlas>,
|
||||
mut map: ResMut<PhosMap>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
) {
|
||||
if atlas.is_loaded {
|
||||
return;
|
||||
}
|
||||
|
||||
if asset_server.load_state(atlas.handle.clone()) != LoadState::Loaded {
|
||||
return;
|
||||
}
|
||||
let image = images.get_mut(&atlas.handle).unwrap();
|
||||
|
||||
let array_layers = 7;
|
||||
image.reinterpret_stacked_2d_as_array(array_layers);
|
||||
|
||||
|
||||
atlas.is_loaded = true;
|
||||
map.ready = true;
|
||||
map.regenerate = true;
|
||||
}
|
||||
|
||||
fn draw_gizmos(mut gizmos: Gizmos, hm: Res<Map>) {
|
||||
gizmos.arrow(Vec3::ZERO, Vec3::Y * 1.5, Color::GREEN);
|
||||
gizmos.arrow(Vec3::ZERO, Vec3::Z * 1.5, Color::BLUE);
|
||||
gizmos.arrow(Vec3::ZERO, Vec3::X * 1.5, Color::RED);
|
||||
|
||||
let coord = HexCoord::from_grid_pos(64, 14);
|
||||
let ch = &hm.chunks[coord.to_chunk_index(hm.width) as usize];
|
||||
let h = ch.points[coord.to_chunk_local_index() as usize];
|
||||
gizmos.ray(coord.to_world(h), Vec3::Y, Color::RED);
|
||||
gizmos.ray(coord.to_world(h), Vec3::Z * 1.5, Color::BLUE);
|
||||
|
||||
// let t = coord.get_neighbor(5);
|
||||
// let h = ch.points[t.to_chunk_local_index() as usize];
|
||||
// gizmos.ray(t.to_world(h), Vec3::Y * 1., Color::PINK);
|
||||
let n = coord.get_neighbors();
|
||||
let nh = hm.get_neighbors(&coord);
|
||||
for i in 0..6 {
|
||||
let t = n[i];
|
||||
let h = nh[i];
|
||||
if h.is_none() {
|
||||
continue;
|
||||
}
|
||||
gizmos.ray(
|
||||
t.to_world(h.unwrap()),
|
||||
Vec3::Y * (i + 1) as f32,
|
||||
Color::CYAN,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_map(mut commands: Commands) {
|
||||
let heightmap = generate_heightmap(
|
||||
&GenerationConfig {
|
||||
layers: vec![
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.14,
|
||||
roughness: 0.87,
|
||||
strength: 2.93,
|
||||
min_value: -0.2,
|
||||
persistence: 0.77,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 4,
|
||||
first_layer_mask: false,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.85,
|
||||
roughness: 2.,
|
||||
strength: -0.23,
|
||||
min_value: -0.,
|
||||
persistence: 1.,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 4,
|
||||
first_layer_mask: false,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.6,
|
||||
roughness: 4.,
|
||||
strength: 10.44,
|
||||
min_value: 0.,
|
||||
persistence: 1.57,
|
||||
is_rigid: true,
|
||||
weight: 1.,
|
||||
weight_multi: 0.35,
|
||||
layers: 4,
|
||||
first_layer_mask: true,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 3.87,
|
||||
roughness: 5.8,
|
||||
strength: -1.,
|
||||
min_value: 0.,
|
||||
persistence: 0.,
|
||||
is_rigid: true,
|
||||
weight: 1.,
|
||||
weight_multi: 4.57,
|
||||
layers: 3,
|
||||
first_layer_mask: true,
|
||||
},
|
||||
],
|
||||
noise_scale: 350.,
|
||||
sea_level: 4.,
|
||||
border_size: 64.,
|
||||
size: UVec2::splat(1024 / Chunk::SIZE as u32),
|
||||
},
|
||||
4,
|
||||
);
|
||||
|
||||
commands.insert_resource(heightmap);
|
||||
}
|
||||
|
||||
fn spawn_map(
|
||||
heightmap: Res<Map>,
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, ChunkMaterial>>>,
|
||||
cam: Query<&Transform, With<PhosCamera>>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
atlas: Res<ChunkAtlas>,
|
||||
mut map: ResMut<PhosMap>,
|
||||
mat: Res<SphereMat>,
|
||||
) {
|
||||
if !map.ready || !map.regenerate {
|
||||
return;
|
||||
}
|
||||
map.regenerate = false;
|
||||
let chunk_material = materials.add(ExtendedMaterial {
|
||||
base: StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
..default()
|
||||
},
|
||||
extension: ChunkMaterial {
|
||||
array_texture: atlas.handle.clone(),
|
||||
},
|
||||
});
|
||||
|
||||
for chunk in &heightmap.chunks {
|
||||
let mesh = generate_chunk_mesh(&chunk, &heightmap);
|
||||
let pos = offset_to_world(chunk.chunk_offset * Chunk::SIZE as i32, 0.);
|
||||
if keyboard_input.just_pressed(KeyCode::KeyF) {
|
||||
let cam_transform = cam.single();
|
||||
commands.spawn((
|
||||
MaterialMeshBundle {
|
||||
mesh: meshes.add(mesh),
|
||||
material: chunk_material.clone(),
|
||||
transform: Transform::from_translation(pos),
|
||||
mesh: meshes.add(Sphere::new(0.3)),
|
||||
material: mat.0.clone(),
|
||||
transform: Transform::from_translation(cam_transform.translation),
|
||||
..default()
|
||||
},
|
||||
PhosChunk,
|
||||
Collider::sphere(0.3),
|
||||
RigidBody::Dynamic,
|
||||
LinearVelocity(cam_transform.forward() * 50.),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +1,46 @@
|
||||
use bevy::asset::{Asset, Handle};
|
||||
use bevy::pbr::{MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline, MaterialPipeline, MaterialPipelineKey};
|
||||
use bevy::prelude::{Component, Image, Mesh, Resource, TypePath};
|
||||
use bevy::render::mesh::{Indices, MeshVertexBufferLayout};
|
||||
use bevy::render::render_resource::{AsBindGroup, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError};
|
||||
use world_generation::prelude::ATTRIBUTE_TEXTURE_INDEX;
|
||||
use bevy::asset::Handle;
|
||||
use bevy::pbr::ExtendedMaterial;
|
||||
use bevy::prelude::*;
|
||||
use bevy::prelude::{Component, Image, Resource};
|
||||
use bevy::reflect::Reflect;
|
||||
|
||||
use crate::shader_extensions::chunk_material::ChunkMaterial;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ChunkAtlas {
|
||||
pub handle: Handle<Image>,
|
||||
pub chunk_material_handle: Handle<ExtendedMaterial<StandardMaterial, ChunkMaterial>>,
|
||||
pub water_material: Handle<StandardMaterial>,
|
||||
pub is_loaded: bool,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
#[derive(Resource, Default, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct PhosMap {
|
||||
pub ready: bool,
|
||||
pub regenerate: bool,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct PhosChunk;
|
||||
|
||||
|
||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||
pub struct ChunkMaterial {
|
||||
#[texture(100, dimension = "2d_array")]
|
||||
#[sampler(101)]
|
||||
pub array_texture: Handle<Image>,
|
||||
pub struct PhosChunk {
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl MaterialExtension for ChunkMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/world/chunk.wgsl".into()
|
||||
impl PhosChunk {
|
||||
pub fn new(index: usize) -> Self {
|
||||
return Self { index };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct PhosChunkRegistry {
|
||||
pub chunks: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl PhosChunkRegistry {
|
||||
pub fn new(size: usize) -> Self {
|
||||
return Self {
|
||||
chunks: Vec::with_capacity(size),
|
||||
};
|
||||
}
|
||||
|
||||
// fn specialize(
|
||||
// _pipeline: &MaterialExtensionPipeline,
|
||||
// descriptor: &mut RenderPipelineDescriptor,
|
||||
// layout: &MeshVertexBufferLayout,
|
||||
// _key: MaterialExtensionKey<Self>,
|
||||
// ) -> Result<(), SpecializedMeshPipelineError> {
|
||||
// let vertex_layout = layout.get_layout(&[
|
||||
// Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
// Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
|
||||
// Mesh::ATTRIBUTE_NORMAL.at_shader_location(2),
|
||||
// ATTRIBUTE_TEXTURE_INDEX.at_shader_location(3),
|
||||
// ])?;
|
||||
// descriptor.vertex.buffers = vec![vertex_layout];
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
|
||||
39
game/main/src/shader_extensions/chunk_material.rs
Normal file
39
game/main/src/shader_extensions/chunk_material.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use bevy::asset::{Asset, Handle};
|
||||
use bevy::pbr::MaterialExtension;
|
||||
use bevy::reflect::TypePath;
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||
use bevy::render::texture::Image;
|
||||
|
||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||
pub struct ChunkMaterial {
|
||||
#[texture(100, dimension = "2d_array")]
|
||||
#[sampler(101)]
|
||||
pub array_texture: Handle<Image>,
|
||||
}
|
||||
|
||||
impl MaterialExtension for ChunkMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/world/chunk.wgsl".into()
|
||||
}
|
||||
|
||||
// fn vertex_shader() -> ShaderRef {
|
||||
// "shaders/world/chunk_packed.wgsl".into()
|
||||
// }
|
||||
|
||||
// fn specialize(
|
||||
// _pipeline: &bevy::pbr::MaterialExtensionPipeline,
|
||||
// descriptor: &mut bevy::render::render_resource::RenderPipelineDescriptor,
|
||||
// layout: &bevy::render::mesh::MeshVertexBufferLayout,
|
||||
// _key: bevy::pbr::MaterialExtensionKey<Self>,
|
||||
// ) -> Result<(), bevy::render::render_resource::SpecializedMeshPipelineError> {
|
||||
// let vertex_layout = layout.get_layout(&[
|
||||
// // Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
// // Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
|
||||
// // Mesh::ATTRIBUTE_NORMAL.at_shader_location(2),
|
||||
// ATTRIBUTE_PACKED_VERTEX_DATA.at_shader_location(7),
|
||||
// ATTRIBUTE_VERTEX_HEIGHT.at_shader_location(8),
|
||||
// ])?;
|
||||
// descriptor.vertex.buffers = vec![vertex_layout];
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
1
game/main/src/shader_extensions/mod.rs
Normal file
1
game/main/src/shader_extensions/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod chunk_material;
|
||||
1
game/main/src/utlis/mod.rs
Normal file
1
game/main/src/utlis/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod render_distance_system;
|
||||
76
game/main/src/utlis/render_distance_system.rs
Normal file
76
game/main/src/utlis/render_distance_system.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::camera_system::components::PhosCamera;
|
||||
|
||||
pub struct RenderDistancePlugin;
|
||||
|
||||
impl Plugin for RenderDistancePlugin {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
app.register_type::<RenderDistanceSettings>();
|
||||
app.add_systems(PostUpdate, render_distance_system)
|
||||
.insert_resource(RenderDistanceSettings::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct RenderDistanceSettings {
|
||||
pub render_distance: f32,
|
||||
}
|
||||
|
||||
impl RenderDistanceSettings {
|
||||
pub fn new(distance: f32) -> Self {
|
||||
return Self {
|
||||
render_distance: distance,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RenderDistanceSettings {
|
||||
fn default() -> Self {
|
||||
Self::new(500.)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct RenderDistanceVisibility {
|
||||
pub distance_multiplier: f32,
|
||||
pub offset: Vec3,
|
||||
}
|
||||
|
||||
impl RenderDistanceVisibility {
|
||||
pub fn with_offset(mut self, offset: Vec3) -> Self {
|
||||
self.offset = offset;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn with_multiplier(mut self, distance_multiplier: f32) -> Self {
|
||||
self.distance_multiplier = distance_multiplier;
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RenderDistanceVisibility {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
distance_multiplier: 1.,
|
||||
offset: Vec3::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_distance_system(
|
||||
mut objects: Query<(&Transform, &mut Visibility, &RenderDistanceVisibility)>,
|
||||
camera_query: Query<&Transform, With<PhosCamera>>,
|
||||
settings: Res<RenderDistanceSettings>,
|
||||
) {
|
||||
let camera = camera_query.single();
|
||||
for (t, mut vis, r) in objects.iter_mut() {
|
||||
let dist = (camera.translation - (t.translation + r.offset)).length() * r.distance_multiplier;
|
||||
if settings.render_distance < dist {
|
||||
*vis = Visibility::Hidden;
|
||||
} else {
|
||||
*vis = Visibility::Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
hard_tabs=true
|
||||
hard_tabs=true
|
||||
max_width=120
|
||||
Reference in New Issue
Block a user