Rename planet_view to globe view; Use macros slightly better; PlanetView
f45efb28f1be9e8e39b84196b97d121b64fff607
This commit is contained in:
parent
8190666f97
commit
afd2495e43
12 changed files with 289 additions and 148 deletions
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-2022, ubuntu-22.04, macos-12]
|
os: [windows-2022, ubuntu-22.04, macos-12]
|
||||||
features: ["debug,render", "render", "debug,planet_view", "planet_view", "debug", ""]
|
features: ["debug,render", "render", "debug,globe_view", "globe_view", "debug", ""]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -34,11 +34,11 @@ jobs:
|
||||||
name: worlds-rs-${{ matrix.os }}-debug-render
|
name: worlds-rs-${{ matrix.os }}-debug-render
|
||||||
path: target/release/worlds-sim-rust*
|
path: target/release/worlds-sim-rust*
|
||||||
|
|
||||||
- name: Upload debug & planet_view binary
|
- name: Upload debug & globe_view binary
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ matrix.features == 'debug,planet_view' }}
|
if: ${{ matrix.features == 'debug,globe_view' }}
|
||||||
with:
|
with:
|
||||||
name: worlds-rs-${{ matrix.os }}-debug-planet_view
|
name: worlds-rs-${{ matrix.os }}-debug-globe_view
|
||||||
path: target/release/worlds-sim-rust*
|
path: target/release/worlds-sim-rust*
|
||||||
|
|
||||||
- name: Upload non-debug binary
|
- name: Upload non-debug binary
|
||||||
|
|
|
@ -9,7 +9,7 @@ winit = { version = "0.26.1", features=["x11"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug = ["planet/debug"]
|
debug = ["planet/debug"]
|
||||||
planet_view = ["planet/planet_view", "render"]
|
globe_view = ["planet/globe_view", "render"]
|
||||||
render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/render", "planet/render", "dep:bevy_pancam"]
|
render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/render", "planet/render", "dep:bevy_pancam"]
|
||||||
default = ["render", "debug"]
|
default = ["render", "debug"]
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug = []
|
debug = []
|
||||||
planet_view = ["render"]
|
globe_view = ["render"]
|
||||||
render = ["bevy/render"]
|
render = ["bevy/render"]
|
||||||
default = ["render", "debug", "planet_view"]
|
default = ["render", "debug", "globe_view"]
|
||||||
|
|
||||||
[dependencies.rand]
|
[dependencies.rand]
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
|
use crate::{macros::iterable_enum, World};
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
use bevy::render::color::Color;
|
use bevy::render::color::Color;
|
||||||
use {
|
|
||||||
crate::World,
|
|
||||||
serde::{Deserialize, Serialize},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct BiomeStats {
|
pub struct BiomeStats {
|
||||||
|
@ -18,20 +15,16 @@ pub struct BiomeStats {
|
||||||
pub max_temperature: f32,
|
pub max_temperature: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! biome_enum {
|
iterable_enum!(BiomeType {
|
||||||
($($Variant:ident),*$(,)?) =>
|
IceCap,
|
||||||
{
|
Ocean,
|
||||||
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
|
Grassland,
|
||||||
pub enum BiomeType {
|
Forest,
|
||||||
$($Variant),*,
|
Taiga,
|
||||||
}
|
Tundra,
|
||||||
impl BiomeType {
|
Desert,
|
||||||
pub const BIOMES: &'static [BiomeType] = &[$(BiomeType::$Variant),*];
|
Rainforest
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
biome_enum!(IceCap, Ocean, Grassland, Forest, Taiga, Tundra, Desert, Rainforest);
|
|
||||||
|
|
||||||
impl From<BiomeType> for BiomeStats {
|
impl From<BiomeType> for BiomeStats {
|
||||||
fn from(biome_type: BiomeType) -> BiomeStats {
|
fn from(biome_type: BiomeType) -> BiomeStats {
|
||||||
|
|
|
@ -37,8 +37,8 @@ pub use world::*;
|
||||||
pub mod biome;
|
pub mod biome;
|
||||||
pub use biome::*;
|
pub use biome::*;
|
||||||
pub mod world_manager;
|
pub mod world_manager;
|
||||||
pub use world_manager::*;
|
pub use world_manager::WorldManager;
|
||||||
|
pub(crate) mod macros;
|
||||||
pub mod math_util;
|
pub mod math_util;
|
||||||
pub use math_util::*;
|
|
||||||
pub mod perlin;
|
pub mod perlin;
|
||||||
pub mod saving;
|
pub mod saving;
|
||||||
|
|
18
planet/src/macros.rs
Normal file
18
planet/src/macros.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
macro_rules! iterable_enum {
|
||||||
|
($Name:ident { $($Variant:ident),*$(,)? }) =>
|
||||||
|
{
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub enum $Name {
|
||||||
|
$($Variant),*,
|
||||||
|
}
|
||||||
|
impl $Name {
|
||||||
|
pub const ITEMS: &'static [$Name] = &[$($Name::$Variant),*];
|
||||||
|
pub const ITEM_COUNT: usize = $Name::ITEMS.len();
|
||||||
|
|
||||||
|
pub fn iterator() -> core::slice::Iter<'static, $Name> {
|
||||||
|
$Name::ITEMS.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) use iterable_enum;
|
|
@ -1,16 +1,23 @@
|
||||||
// TODO: Logging doesn't seem to work here? Figure out why and fix
|
// TODO: Logging doesn't seem to work here? Figure out why and fix
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
cartesian_coordinates,
|
math_util::{
|
||||||
mix_values,
|
cartesian_coordinates,
|
||||||
|
mix_values,
|
||||||
|
random_point_in_sphere,
|
||||||
|
CartesianError,
|
||||||
|
RepeatNum,
|
||||||
|
},
|
||||||
perlin,
|
perlin,
|
||||||
random_point_in_sphere,
|
|
||||||
BiomeStats,
|
BiomeStats,
|
||||||
BiomeType,
|
BiomeType,
|
||||||
CartesianError,
|
|
||||||
RepeatNum,
|
|
||||||
},
|
},
|
||||||
bevy::{log::info, math::Vec3A, prelude::Vec2, utils::default},
|
bevy::{
|
||||||
|
log::info,
|
||||||
|
math::Vec3A,
|
||||||
|
prelude::Vec2,
|
||||||
|
utils::{default, HashMap},
|
||||||
|
},
|
||||||
rand::{rngs::StdRng, Rng, SeedableRng},
|
rand::{rngs::StdRng, Rng, SeedableRng},
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
std::{
|
std::{
|
||||||
|
@ -47,6 +54,18 @@ impl Display for WorldGenError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum CompassDirection {
|
||||||
|
North,
|
||||||
|
NorthEast,
|
||||||
|
East,
|
||||||
|
SouthEast,
|
||||||
|
South,
|
||||||
|
SouthWest,
|
||||||
|
West,
|
||||||
|
NorthWest,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
|
@ -456,7 +475,7 @@ impl World {
|
||||||
let mut total_presence = 0.0;
|
let mut total_presence = 0.0;
|
||||||
|
|
||||||
let mut biome_presences = vec![];
|
let mut biome_presences = vec![];
|
||||||
for biome_type in BiomeType::BIOMES {
|
for biome_type in BiomeType::iterator() {
|
||||||
let presence = self.biome_presence(cell, &biome_type.into());
|
let presence = self.biome_presence(cell, &biome_type.into());
|
||||||
|
|
||||||
if presence <= 0.0 {
|
if presence <= 0.0 {
|
||||||
|
@ -526,4 +545,92 @@ impl World {
|
||||||
|
|
||||||
presence
|
presence
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn cell_neighbors(&self, x: usize, y: usize) -> HashMap<CompassDirection, (usize, usize)> {
|
||||||
|
let mut neighbors = HashMap::new();
|
||||||
|
|
||||||
|
let height = self.height as usize;
|
||||||
|
let width = self.width as usize;
|
||||||
|
|
||||||
|
let north_edge = y >= height - 1;
|
||||||
|
let east_edge = x >= width - 1;
|
||||||
|
let south_edge = y == 0;
|
||||||
|
let west_edge = x == 0;
|
||||||
|
|
||||||
|
if !north_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::North, (y + 1, x));
|
||||||
|
}
|
||||||
|
if !north_edge && !east_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::NorthEast, (y + 1, x + 1));
|
||||||
|
}
|
||||||
|
if !east_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::East, (y, x + 1));
|
||||||
|
}
|
||||||
|
if !south_edge && !east_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::SouthEast, (y - 1, x + 1));
|
||||||
|
}
|
||||||
|
if !south_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::South, (y - 1, x));
|
||||||
|
}
|
||||||
|
if !south_edge && !west_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::SouthWest, (y - 1, x - 1));
|
||||||
|
}
|
||||||
|
if !west_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::West, (y, x - 1));
|
||||||
|
};
|
||||||
|
if !north_edge && !west_edge {
|
||||||
|
_ = neighbors.insert_unique_unchecked(CompassDirection::NorthWest, (y + 1, x - 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
neighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_slant(&self, x: usize, y: usize) -> f32 {
|
||||||
|
let neighbors = self.cell_neighbors(x, y);
|
||||||
|
let terrain = &self.terrain;
|
||||||
|
|
||||||
|
let mut west_altitude = f32::MIN;
|
||||||
|
if let Some(neighbor_coords) = neighbors.get(&CompassDirection::North) {
|
||||||
|
west_altitude = f32::max(
|
||||||
|
west_altitude,
|
||||||
|
terrain[neighbor_coords.0][neighbor_coords.1].altitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(neighbor_coords) = neighbors.get(&CompassDirection::NorthWest) {
|
||||||
|
west_altitude = f32::max(
|
||||||
|
west_altitude,
|
||||||
|
terrain[neighbor_coords.0][neighbor_coords.1].altitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(neighbor_coords) = neighbors.get(&CompassDirection::West) {
|
||||||
|
west_altitude = f32::max(
|
||||||
|
west_altitude,
|
||||||
|
terrain[neighbor_coords.0][neighbor_coords.1].altitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut east_altitude = f32::MIN;
|
||||||
|
if let Some(neighbor_coords) = neighbors.get(&CompassDirection::North) {
|
||||||
|
east_altitude = f32::max(
|
||||||
|
east_altitude,
|
||||||
|
terrain[neighbor_coords.0][neighbor_coords.1].altitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(neighbor_coords) = neighbors.get(&CompassDirection::NorthWest) {
|
||||||
|
east_altitude = f32::max(
|
||||||
|
east_altitude,
|
||||||
|
terrain[neighbor_coords.0][neighbor_coords.1].altitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(neighbor_coords) = neighbors.get(&CompassDirection::West) {
|
||||||
|
east_altitude = f32::max(
|
||||||
|
east_altitude,
|
||||||
|
terrain[neighbor_coords.0][neighbor_coords.1].altitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
west_altitude - east_altitude
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,10 @@
|
||||||
use bevy::log::debug;
|
use bevy::log::debug;
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
use bevy::utils::default;
|
use bevy::utils::default;
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
#[cfg(feature = "render")]
|
|
||||||
use {
|
use {
|
||||||
crate::{BiomeStats, TerrainCell},
|
crate::{macros::iterable_enum, World, WorldGenError},
|
||||||
bevy::{
|
|
||||||
asset::{Assets, HandleId},
|
|
||||||
render::render_resource::Extent3d,
|
|
||||||
render::{color::Color, texture::Image},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use {
|
|
||||||
crate::{World, WorldGenError},
|
|
||||||
bevy::log::warn,
|
bevy::log::warn,
|
||||||
rand::random,
|
rand::random,
|
||||||
std::{
|
std::{
|
||||||
|
@ -25,6 +16,15 @@ use {
|
||||||
path::Path,
|
path::Path,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "render")]
|
||||||
|
use {
|
||||||
|
crate::{BiomeStats, TerrainCell},
|
||||||
|
bevy::{
|
||||||
|
asset::{Assets, HandleId},
|
||||||
|
render::render_resource::Extent3d,
|
||||||
|
render::{color::Color, texture::Image},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoadError {
|
pub enum LoadError {
|
||||||
|
@ -89,43 +89,46 @@ impl Display for SaveError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iterable_enum!(PlanetView { Biomes, Altitude });
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WorldManager {
|
pub struct WorldManager {
|
||||||
world: Option<World>,
|
world: Option<World>,
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
pub map_image_handle_id: Option<HandleId>,
|
pub map_image_handle_id: Option<HandleId>,
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
pub planet_image_handle_id: Option<HandleId>,
|
pub globe_image_handle_id: Option<HandleId>,
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
pub planet_material_handle_id: Option<HandleId>,
|
pub globe_material_handle_id: Option<HandleId>,
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
rainfall_visible: bool,
|
rainfall_visible: bool,
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
temperature_visible: bool,
|
temperature_visible: bool,
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
biomes_visible: bool,
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
contours: bool,
|
contours: bool,
|
||||||
|
#[cfg(feature = "render")]
|
||||||
|
view: PlanetView,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorldManager {
|
impl WorldManager {
|
||||||
|
#[must_use]
|
||||||
pub fn new() -> WorldManager {
|
pub fn new() -> WorldManager {
|
||||||
Self {
|
Self {
|
||||||
world: None,
|
world: None,
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
map_image_handle_id: None,
|
map_image_handle_id: None,
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
planet_image_handle_id: None,
|
globe_image_handle_id: None,
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
planet_material_handle_id: None,
|
globe_material_handle_id: None,
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
rainfall_visible: false,
|
rainfall_visible: false,
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
temperature_visible: false,
|
temperature_visible: false,
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
biomes_visible: true,
|
view: PlanetView::Biomes,
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
contours: false,
|
contours: false,
|
||||||
}
|
}
|
||||||
|
@ -228,14 +231,19 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
pub fn toggle_biomes(&mut self) {
|
pub fn cycle_view(&mut self) {
|
||||||
|
let idx = (PlanetView::iterator()
|
||||||
|
.position(|view| *view == self.view)
|
||||||
|
.unwrap()
|
||||||
|
+ 1)
|
||||||
|
% PlanetView::ITEM_COUNT;
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
if self.temperature_visible {
|
debug!(
|
||||||
debug!("Turning biomes off");
|
"Cycling view from {:#?} to {:#?}",
|
||||||
} else {
|
self.view,
|
||||||
debug!("Turning biomes on");
|
PlanetView::ITEMS[idx]
|
||||||
}
|
);
|
||||||
self.biomes_visible = !self.biomes_visible;
|
self.view = PlanetView::ITEMS[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
@ -249,6 +257,7 @@ impl WorldManager {
|
||||||
self.contours = !self.contours;
|
self.contours = !self.contours;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_world(&self) -> Option<&World> {
|
pub fn get_world(&self) -> Option<&World> {
|
||||||
self.world.as_ref()
|
self.world.as_ref()
|
||||||
}
|
}
|
||||||
|
@ -267,8 +276,9 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
#[must_use]
|
||||||
fn generate_color(&self, cell: &TerrainCell) -> Color {
|
fn generate_color(&self, cell: &TerrainCell) -> Color {
|
||||||
if self.biomes_visible {
|
if self.view == PlanetView::Biomes {
|
||||||
return WorldManager::biome_color(cell);
|
return WorldManager::biome_color(cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +326,7 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
#[must_use]
|
||||||
fn altitude_color(altitude: f32) -> Color {
|
fn altitude_color(altitude: f32) -> Color {
|
||||||
if altitude < 0.0 {
|
if altitude < 0.0 {
|
||||||
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
||||||
|
@ -327,6 +338,7 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
#[must_use]
|
||||||
fn altitude_contour_color(altitude: f32) -> Color {
|
fn altitude_contour_color(altitude: f32) -> Color {
|
||||||
if altitude < 0.0 {
|
if altitude < 0.0 {
|
||||||
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
||||||
|
@ -342,6 +354,7 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
#[must_use]
|
||||||
fn rainfall_contour_color(&self, rainfall: f32) -> Color {
|
fn rainfall_contour_color(&self, rainfall: f32) -> Color {
|
||||||
let mut shade_value = 1.0;
|
let mut shade_value = 1.0;
|
||||||
let value = rainfall / self.world().max_rainfall;
|
let value = rainfall / self.world().max_rainfall;
|
||||||
|
@ -354,6 +367,7 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
#[must_use]
|
||||||
fn temperature_contour_color(&self, temperature: f32) -> Color {
|
fn temperature_contour_color(&self, temperature: f32) -> Color {
|
||||||
let mut shade_value = 1.0;
|
let mut shade_value = 1.0;
|
||||||
let value = (temperature - self.world().min_temperature)
|
let value = (temperature - self.world().min_temperature)
|
||||||
|
@ -367,6 +381,7 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
#[must_use]
|
||||||
fn biome_color(cell: &TerrainCell) -> Color {
|
fn biome_color(cell: &TerrainCell) -> Color {
|
||||||
cell.biome_presences
|
cell.biome_presences
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -412,9 +427,9 @@ impl WorldManager {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn planet_colors(&self) -> Vec<Color> {
|
fn globe_colors(&self) -> Vec<Color> {
|
||||||
let world = self.world();
|
let world = self.world();
|
||||||
let width = world.width as usize;
|
let width = world.width as usize;
|
||||||
let height = world.height as usize;
|
let height = world.height as usize;
|
||||||
|
@ -439,10 +454,10 @@ impl WorldManager {
|
||||||
colors
|
colors
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn planet_color_bytes(&self) -> Vec<u8> {
|
pub fn globe_color_bytes(&self) -> Vec<u8> {
|
||||||
self.planet_colors()
|
self.globe_colors()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|color| {
|
.flat_map(|color| {
|
||||||
color
|
color
|
||||||
|
|
|
@ -1,41 +1,27 @@
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
use bevy::ecs::component::Component;
|
use {crate::macros::iterable_enum, bevy::ecs::component::Component};
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(all(feature = "render", not(feature = "globe_view")))]
|
||||||
macro_rules! toolbar_enum {
|
iterable_enum!(ToolbarButton {
|
||||||
($($Variant:ident),*$(,)?) =>
|
|
||||||
{
|
|
||||||
#[derive(Debug, Component, Copy, Clone)]
|
|
||||||
pub enum ToolbarButton {
|
|
||||||
$($Variant),*,
|
|
||||||
}
|
|
||||||
impl ToolbarButton {
|
|
||||||
pub const BUTTONS: &'static [ToolbarButton] = &[$(ToolbarButton::$Variant),*];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "render", not(feature = "planet_view")))]
|
|
||||||
toolbar_enum!(
|
|
||||||
GenerateWorld,
|
GenerateWorld,
|
||||||
SaveWorld,
|
SaveWorld,
|
||||||
LoadWorld,
|
LoadWorld,
|
||||||
Rainfall,
|
Rainfall,
|
||||||
Temperature,
|
Temperature,
|
||||||
Biomes,
|
|
||||||
Contours,
|
|
||||||
);
|
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
|
||||||
toolbar_enum!(
|
|
||||||
GenerateWorld,
|
|
||||||
SaveWorld,
|
|
||||||
LoadWorld,
|
|
||||||
Rainfall,
|
|
||||||
Temperature,
|
|
||||||
Biomes,
|
|
||||||
Contours,
|
|
||||||
PlanetView,
|
PlanetView,
|
||||||
);
|
Contours,
|
||||||
|
});
|
||||||
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
|
iterable_enum!(ToolbarButton {
|
||||||
|
GenerateWorld,
|
||||||
|
SaveWorld,
|
||||||
|
LoadWorld,
|
||||||
|
Rainfall,
|
||||||
|
Temperature,
|
||||||
|
PlanetView,
|
||||||
|
Contours,
|
||||||
|
GlobeView,
|
||||||
|
});
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
impl From<ToolbarButton> for &'static str {
|
impl From<ToolbarButton> for &'static str {
|
||||||
|
@ -44,12 +30,12 @@ impl From<ToolbarButton> for &'static str {
|
||||||
ToolbarButton::Rainfall => "Toggle rainfall",
|
ToolbarButton::Rainfall => "Toggle rainfall",
|
||||||
ToolbarButton::Temperature => "Toggle temperature",
|
ToolbarButton::Temperature => "Toggle temperature",
|
||||||
ToolbarButton::Contours => "Toggle contours",
|
ToolbarButton::Contours => "Toggle contours",
|
||||||
ToolbarButton::Biomes => "Toggle biomes",
|
ToolbarButton::PlanetView => "Cycle view",
|
||||||
ToolbarButton::GenerateWorld => "Generate new world",
|
ToolbarButton::GenerateWorld => "Generate new world",
|
||||||
ToolbarButton::SaveWorld => "Save",
|
ToolbarButton::SaveWorld => "Save",
|
||||||
ToolbarButton::LoadWorld => "Load",
|
ToolbarButton::LoadWorld => "Load",
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
ToolbarButton::PlanetView => "Toggle planet view",
|
ToolbarButton::GlobeView => "Toggle globe",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
src/macros.rs
Normal file
19
src/macros.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#[cfg(feature = "render")]
|
||||||
|
macro_rules! iterable_enum {
|
||||||
|
($Name:ident { $($Variant:ident),*$(,)? }) =>
|
||||||
|
{
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Component)]
|
||||||
|
pub enum $Name {
|
||||||
|
$($Variant),*,
|
||||||
|
}
|
||||||
|
impl $Name {
|
||||||
|
const ITEMS: &'static [$Name] = &[$($Name::$Variant),*];
|
||||||
|
|
||||||
|
pub fn iterator() -> core::slice::Iter<'static, $Name> {
|
||||||
|
$Name::ITEMS.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "render")]
|
||||||
|
pub(crate) use iterable_enum;
|
91
src/main.rs
91
src/main.rs
|
@ -32,10 +32,11 @@
|
||||||
#![warn(unused_results)]
|
#![warn(unused_results)]
|
||||||
#![warn(variant_size_differences)]
|
#![warn(variant_size_differences)]
|
||||||
|
|
||||||
mod components;
|
pub(crate) mod components;
|
||||||
mod plugins;
|
pub(crate) mod macros;
|
||||||
mod resources;
|
pub(crate) mod plugins;
|
||||||
mod ui_helpers;
|
pub(crate) mod resources;
|
||||||
|
pub(crate) mod ui_helpers;
|
||||||
|
|
||||||
#[cfg(all(feature = "debug", feature = "render"))]
|
#[cfg(all(feature = "debug", feature = "render"))]
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
@ -103,7 +104,7 @@ use {
|
||||||
planet::WorldManager,
|
planet::WorldManager,
|
||||||
plugins::WorldPlugins,
|
plugins::WorldPlugins,
|
||||||
};
|
};
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
use {
|
use {
|
||||||
bevy::{
|
bevy::{
|
||||||
asset::Handle,
|
asset::Handle,
|
||||||
|
@ -121,7 +122,7 @@ use {
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
fn refresh_map_texture(
|
fn refresh_map_texture(
|
||||||
images: &mut Assets<Image>,
|
images: &mut Assets<Image>,
|
||||||
#[cfg(feature = "planet_view")] materials: &mut Assets<StandardMaterial>,
|
#[cfg(feature = "globe_view")] materials: &mut Assets<StandardMaterial>,
|
||||||
world_manager: &WorldManager,
|
world_manager: &WorldManager,
|
||||||
) {
|
) {
|
||||||
let world = world_manager.world();
|
let world = world_manager.world();
|
||||||
|
@ -142,11 +143,11 @@ fn refresh_map_texture(
|
||||||
});
|
});
|
||||||
map_image.data = world_manager.map_color_bytes();
|
map_image.data = world_manager.map_color_bytes();
|
||||||
|
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
{
|
{
|
||||||
let planet_image_handle = images.get_handle(
|
let planet_image_handle = images.get_handle(
|
||||||
world_manager
|
world_manager
|
||||||
.planet_image_handle_id
|
.globe_image_handle_id
|
||||||
.expect("No planet image handle"),
|
.expect("No planet image handle"),
|
||||||
);
|
);
|
||||||
let planet_image = images
|
let planet_image = images
|
||||||
|
@ -157,11 +158,11 @@ fn refresh_map_texture(
|
||||||
height: world.height,
|
height: world.height,
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
});
|
});
|
||||||
planet_image.data = world_manager.planet_color_bytes();
|
planet_image.data = world_manager.globe_color_bytes();
|
||||||
|
|
||||||
let planet_material_handle = materials.get_handle(
|
let planet_material_handle = materials.get_handle(
|
||||||
world_manager
|
world_manager
|
||||||
.planet_material_handle_id
|
.globe_material_handle_id
|
||||||
.expect("No planet material handle"),
|
.expect("No planet material handle"),
|
||||||
);
|
);
|
||||||
let planet_material = materials
|
let planet_material = materials
|
||||||
|
@ -188,19 +189,19 @@ fn handle_toolbar_button(
|
||||||
mut windows: ResMut<'_, Windows>,
|
mut windows: ResMut<'_, Windows>,
|
||||||
mut images: ResMut<'_, Assets<Image>>,
|
mut images: ResMut<'_, Assets<Image>>,
|
||||||
mut world_manager: ResMut<'_, WorldManager>,
|
mut world_manager: ResMut<'_, WorldManager>,
|
||||||
#[cfg(feature = "planet_view")] mut camera_3d_query: Query<
|
#[cfg(feature = "globe_view")] mut camera_3d_query: Query<
|
||||||
'_,
|
'_,
|
||||||
'_,
|
'_,
|
||||||
&mut Camera,
|
&mut Camera,
|
||||||
(With<Camera3d>, Without<Camera2d>),
|
(With<Camera3d>, Without<Camera2d>),
|
||||||
>,
|
>,
|
||||||
#[cfg(feature = "planet_view")] mut camera_2d_query: Query<
|
#[cfg(feature = "globe_view")] mut camera_2d_query: Query<
|
||||||
'_,
|
'_,
|
||||||
'_,
|
'_,
|
||||||
&mut Camera,
|
(&mut Camera, &mut PanCam),
|
||||||
(With<Camera2d>, Without<Camera3d>),
|
(With<Camera2d>, Without<Camera3d>),
|
||||||
>,
|
>,
|
||||||
#[cfg(feature = "planet_view")] mut materials: ResMut<'_, Assets<StandardMaterial>>,
|
#[cfg(feature = "globe_view")] mut materials: ResMut<'_, Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
for (interaction, mut color, toolbar_button) in &mut interaction_query {
|
for (interaction, mut color, toolbar_button) in &mut interaction_query {
|
||||||
match *interaction {
|
match *interaction {
|
||||||
|
@ -214,7 +215,7 @@ fn handle_toolbar_button(
|
||||||
world_manager.toggle_rainfall();
|
world_manager.toggle_rainfall();
|
||||||
refresh_map_texture(
|
refresh_map_texture(
|
||||||
&mut images,
|
&mut images,
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
&mut materials,
|
&mut materials,
|
||||||
&world_manager,
|
&world_manager,
|
||||||
);
|
);
|
||||||
|
@ -225,18 +226,18 @@ fn handle_toolbar_button(
|
||||||
world_manager.toggle_temperature();
|
world_manager.toggle_temperature();
|
||||||
refresh_map_texture(
|
refresh_map_texture(
|
||||||
&mut images,
|
&mut images,
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
&mut materials,
|
&mut materials,
|
||||||
&world_manager,
|
&world_manager,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
ToolbarButton::Biomes => {
|
ToolbarButton::PlanetView => {
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
debug!("Toggling biomes");
|
debug!("Cycling planet view");
|
||||||
world_manager.toggle_biomes();
|
world_manager.cycle_view();
|
||||||
refresh_map_texture(
|
refresh_map_texture(
|
||||||
&mut images,
|
&mut images,
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
&mut materials,
|
&mut materials,
|
||||||
&world_manager,
|
&world_manager,
|
||||||
);
|
);
|
||||||
|
@ -247,7 +248,7 @@ fn handle_toolbar_button(
|
||||||
world_manager.toggle_contours();
|
world_manager.toggle_contours();
|
||||||
refresh_map_texture(
|
refresh_map_texture(
|
||||||
&mut images,
|
&mut images,
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
&mut materials,
|
&mut materials,
|
||||||
&world_manager,
|
&world_manager,
|
||||||
);
|
);
|
||||||
|
@ -260,7 +261,7 @@ fn handle_toolbar_button(
|
||||||
.expect("Failed to generate new world");
|
.expect("Failed to generate new world");
|
||||||
refresh_map_texture(
|
refresh_map_texture(
|
||||||
&mut images,
|
&mut images,
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
&mut materials,
|
&mut materials,
|
||||||
&world_manager,
|
&world_manager,
|
||||||
);
|
);
|
||||||
|
@ -276,19 +277,20 @@ fn handle_toolbar_button(
|
||||||
_ = world_manager.load_world("planet.ron", &mut images);
|
_ = world_manager.load_world("planet.ron", &mut images);
|
||||||
refresh_map_texture(
|
refresh_map_texture(
|
||||||
&mut images,
|
&mut images,
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
&mut materials,
|
&mut materials,
|
||||||
&world_manager,
|
&world_manager,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
ToolbarButton::PlanetView => {
|
ToolbarButton::GlobeView => {
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
debug!("Toggling planet view");
|
debug!("Toggling globe view");
|
||||||
let mut camera_3d = camera_3d_query.single_mut();
|
let mut camera_3d = camera_3d_query.single_mut();
|
||||||
camera_3d.is_active = !camera_3d.is_active;
|
camera_3d.is_active = !camera_3d.is_active;
|
||||||
let mut camera_2d = camera_2d_query.single_mut();
|
let (mut camera_2d, mut pancam) = camera_2d_query.single_mut();
|
||||||
camera_2d.is_active = !camera_2d.is_active;
|
camera_2d.is_active = !camera_2d.is_active;
|
||||||
|
pancam.enabled = camera_2d.is_active;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -336,11 +338,11 @@ fn update_cursor_map_position(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
const ROTATION_SPEED: f32 = 0.002;
|
const ROTATION_SPEED: f32 = 0.002;
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
fn rotate_planet(mut planet_transform: Query<'_, '_, &mut Transform, With<Handle<Mesh>>>) {
|
fn rotate_globe(mut globe_transform: Query<'_, '_, &mut Transform, With<Handle<Mesh>>>) {
|
||||||
planet_transform.single_mut().rotate_y(ROTATION_SPEED);
|
globe_transform.single_mut().rotate_y(ROTATION_SPEED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
|
@ -431,8 +433,8 @@ fn generate_graphics(
|
||||||
mut world_manager: ResMut<'_, WorldManager>,
|
mut world_manager: ResMut<'_, WorldManager>,
|
||||||
mut images: ResMut<'_, Assets<Image>>,
|
mut images: ResMut<'_, Assets<Image>>,
|
||||||
mut fonts: ResMut<'_, Assets<Font>>,
|
mut fonts: ResMut<'_, Assets<Font>>,
|
||||||
#[cfg(feature = "planet_view")] mut materials: ResMut<'_, Assets<StandardMaterial>>,
|
#[cfg(feature = "globe_view")] mut materials: ResMut<'_, Assets<StandardMaterial>>,
|
||||||
#[cfg(feature = "planet_view")] mut meshes: ResMut<'_, Assets<Mesh>>,
|
#[cfg(feature = "globe_view")] mut meshes: ResMut<'_, Assets<Mesh>>,
|
||||||
) {
|
) {
|
||||||
let julia_mono_handle = fonts.add(
|
let julia_mono_handle = fonts.add(
|
||||||
Font::try_from_bytes(include_bytes!("../assets/JuliaMono.ttf").to_vec())
|
Font::try_from_bytes(include_bytes!("../assets/JuliaMono.ttf").to_vec())
|
||||||
|
@ -463,11 +465,11 @@ fn generate_graphics(
|
||||||
});
|
});
|
||||||
world_manager.map_image_handle_id = Some(map_image_handle.id);
|
world_manager.map_image_handle_id = Some(map_image_handle.id);
|
||||||
|
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
{
|
{
|
||||||
let world = world_manager.world();
|
let world = world_manager.world();
|
||||||
let planet_image_handle = images.add(Image {
|
let globe_image_handle = images.add(Image {
|
||||||
data: world_manager.planet_color_bytes(),
|
data: world_manager.globe_color_bytes(),
|
||||||
texture_descriptor: TextureDescriptor {
|
texture_descriptor: TextureDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
size: Extent3d {
|
size: Extent3d {
|
||||||
|
@ -483,7 +485,7 @@ fn generate_graphics(
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
world_manager.planet_image_handle_id = Some(planet_image_handle.id);
|
world_manager.globe_image_handle_id = Some(globe_image_handle.id);
|
||||||
|
|
||||||
_ = commands.spawn_bundle(Camera3dBundle {
|
_ = commands.spawn_bundle(Camera3dBundle {
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
|
@ -499,19 +501,20 @@ fn generate_graphics(
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let planet_material_handle = materials.add(
|
let globe_material_handle = materials.add(
|
||||||
images
|
images
|
||||||
.get_handle(world_manager.planet_image_handle_id.unwrap())
|
.get_handle(world_manager.globe_image_handle_id.unwrap())
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
world_manager.planet_material_handle_id = Some(planet_material_handle.id);
|
world_manager.globe_material_handle_id = Some(globe_material_handle.id);
|
||||||
|
|
||||||
|
// TODO: Globe texture is mirrored east-to-west.
|
||||||
_ = commands.spawn_bundle(PbrBundle {
|
_ = commands.spawn_bundle(PbrBundle {
|
||||||
mesh: meshes.add(Mesh::from(UVSphere {
|
mesh: meshes.add(Mesh::from(UVSphere {
|
||||||
radius: 2.0,
|
radius: 2.0,
|
||||||
..default()
|
..default()
|
||||||
})),
|
})),
|
||||||
material: planet_material_handle,
|
material: globe_material_handle,
|
||||||
transform: Transform::from_rotation(Quat::from_rotation_x(FRAC_PI_2)),
|
transform: Transform::from_rotation(Quat::from_rotation_x(FRAC_PI_2)),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
@ -591,7 +594,7 @@ fn generate_graphics(
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|button_box| {
|
.with_children(|button_box| {
|
||||||
ToolbarButton::BUTTONS.iter().for_each(|&button_type| {
|
ToolbarButton::iterator().for_each(|&button_type| {
|
||||||
_ = button_box
|
_ = button_box
|
||||||
.spawn_bundle(toolbar_button())
|
.spawn_bundle(toolbar_button())
|
||||||
.with_children(|button| {
|
.with_children(|button| {
|
||||||
|
@ -630,9 +633,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.add_system(handle_toolbar_button)
|
.add_system(handle_toolbar_button)
|
||||||
.add_system(update_cursor_map_position)
|
.add_system(update_cursor_map_position)
|
||||||
.add_system(update_info_panel);
|
.add_system(update_info_panel);
|
||||||
#[cfg(all(feature = "render", feature = "planet_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
{
|
{
|
||||||
_ = app.add_system(rotate_planet);
|
_ = app.add_system(rotate_globe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "render"))]
|
#[cfg(not(feature = "render"))]
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl PluginGroup for WorldPlugins {
|
||||||
.add(TextPlugin::default())
|
.add(TextPlugin::default())
|
||||||
.add(UiPlugin::default())
|
.add(UiPlugin::default())
|
||||||
.add(PanCamPlugin::default());
|
.add(PanCamPlugin::default());
|
||||||
#[cfg(feature = "planet_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
{
|
{
|
||||||
use bevy::pbr::PbrPlugin;
|
use bevy::pbr::PbrPlugin;
|
||||||
_ = group.add(PbrPlugin::default())
|
_ = group.add(PbrPlugin::default())
|
||||||
|
|
Loading…
Reference in a new issue