CHUNK GENERATION IS REAL
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
use bevy_ecs::{entity::Entity, system::Commands};
|
||||
use nalgebra::Vector3;
|
||||
use ndarray::{s, Array3, Axis};
|
||||
use simdnoise::NoiseBuilder;
|
||||
|
||||
use crate::{
|
||||
client::render::voxel::VoxelColor,
|
||||
common::component::{chunk, ChunkBundle, ChunkData, ChunkMesh, ChunkPos},
|
||||
util::{
|
||||
oct_tree::OctTree,
|
||||
thread::{ExitType, ThreadChannel, ThreadHandle},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ChunkManager {
|
||||
handles: Vec<ThreadHandle<ChunkLoaderMsg, ServerChunkMsg>>,
|
||||
i: usize,
|
||||
n: usize,
|
||||
map: HashMap<ChunkPos, Entity>,
|
||||
generating: HashSet<ChunkPos>,
|
||||
}
|
||||
|
||||
impl ChunkManager {
|
||||
pub fn new() -> Self {
|
||||
let n = 4;
|
||||
Self {
|
||||
handles: std::iter::repeat_with(|| ThreadHandle::spawn(chunk_loader_main))
|
||||
.take(n)
|
||||
.collect(),
|
||||
i: 0,
|
||||
n,
|
||||
map: HashMap::new(),
|
||||
generating: HashSet::new(),
|
||||
}
|
||||
}
|
||||
pub fn entity_at(&self, pos: &ChunkPos) -> Option<&Entity> {
|
||||
self.map.get(pos)
|
||||
}
|
||||
pub fn is_generating(&self, pos: &ChunkPos) -> bool {
|
||||
self.generating.contains(pos)
|
||||
}
|
||||
pub fn queue(&mut self, pos: ChunkPos) {
|
||||
if !self.is_generating(&pos) {
|
||||
self.handles[self.i].send(ChunkLoaderMsg::Generate(pos));
|
||||
self.i = (self.i + 1) % self.n;
|
||||
self.generating.insert(pos);
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, commands: &mut Commands) {
|
||||
for msg in self.handles.iter_mut().flat_map(|h| h.recv()) {
|
||||
match msg {
|
||||
ServerChunkMsg::ChunkGenerated(chunk) => {
|
||||
let id = commands
|
||||
.spawn(ChunkBundle {
|
||||
pos: chunk.pos,
|
||||
data: chunk.data,
|
||||
mesh: chunk.mesh,
|
||||
})
|
||||
.id();
|
||||
self.map.insert(chunk.pos, id);
|
||||
self.generating.remove(&chunk.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GeneratedChunk {
|
||||
pub pos: ChunkPos,
|
||||
pub data: ChunkData,
|
||||
pub mesh: ChunkMesh,
|
||||
}
|
||||
|
||||
impl Drop for ChunkManager {
|
||||
fn drop(&mut self) {
|
||||
for h in &mut self.handles {
|
||||
h.send(ChunkLoaderMsg::Exit);
|
||||
h.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ServerChunkMsg {
|
||||
ChunkGenerated(GeneratedChunk),
|
||||
}
|
||||
|
||||
enum ChunkLoaderMsg {
|
||||
Generate(ChunkPos),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl ExitType for ChunkLoaderMsg {
|
||||
fn exit() -> Self {
|
||||
Self::Exit
|
||||
}
|
||||
}
|
||||
|
||||
fn chunk_loader_main(channel: ThreadChannel<ServerChunkMsg, ChunkLoaderMsg>) {
|
||||
let mut to_generate = VecDeque::new();
|
||||
'outer: loop {
|
||||
let msg = channel.recv_wait();
|
||||
match msg {
|
||||
ChunkLoaderMsg::Generate(pos) => {
|
||||
to_generate.push_back(pos);
|
||||
}
|
||||
ChunkLoaderMsg::Exit => {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
if let Some(pos) = to_generate.pop_front() {
|
||||
let data = generate(pos);
|
||||
let mesh = ChunkMesh::from_data(&data);
|
||||
let data = if pos.y > 0 || pos.y < -1 {
|
||||
ChunkData::empty()
|
||||
} else {
|
||||
ChunkData::from_tree(OctTree::from_arr(data.slice(s![
|
||||
1..data.len_of(Axis(0)) - 1,
|
||||
1..data.len_of(Axis(1)) - 1,
|
||||
1..data.len_of(Axis(2)) - 1
|
||||
])))
|
||||
};
|
||||
channel.send(ServerChunkMsg::ChunkGenerated(GeneratedChunk {
|
||||
pos,
|
||||
data,
|
||||
mesh,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate(pos: ChunkPos) -> Array3<VoxelColor> {
|
||||
let shape = [chunk::SIDE_LENGTH + 2; 3];
|
||||
if pos.y > 0 {
|
||||
return Array3::from_elem(shape, VoxelColor::none());
|
||||
}
|
||||
if pos.y < -1 {
|
||||
return Array3::from_elem(shape, VoxelColor::none());
|
||||
}
|
||||
let posf: Vector3<f32> = (pos.cast() * chunk::SIDE_LENGTH as f32) - Vector3::from_element(1.0);
|
||||
let (a, b, c, d) = (0.0, 50.0, 100.0, 127.0);
|
||||
let (noise, ..) = NoiseBuilder::gradient_2d_offset(
|
||||
posf.x,
|
||||
chunk::SIDE_LENGTH + 2,
|
||||
posf.z,
|
||||
chunk::SIDE_LENGTH + 2,
|
||||
)
|
||||
.with_seed(0)
|
||||
.with_freq(0.005)
|
||||
.generate();
|
||||
Array3::from_shape_fn(shape, |(x, y, z)| {
|
||||
let y = y as f32 + posf.y;
|
||||
let n = (noise[x + z * (chunk::SIDE_LENGTH + 2)] + 0.022) * (1.0 / 0.044) * d;
|
||||
if y < n.max(b) {
|
||||
if y < b {
|
||||
VoxelColor {
|
||||
r: 100,
|
||||
g: 100,
|
||||
b: 255,
|
||||
a: 255,
|
||||
}
|
||||
} else if y < c {
|
||||
VoxelColor {
|
||||
r: 100,
|
||||
g: 255,
|
||||
b: 100,
|
||||
a: 255,
|
||||
}
|
||||
} else {
|
||||
VoxelColor {
|
||||
r: 150,
|
||||
g: 150,
|
||||
b: 150,
|
||||
a: 255,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
VoxelColor::none()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod load;
|
||||
pub use load::*;
|
||||
@@ -0,0 +1,80 @@
|
||||
use std::collections::{hash_map, HashMap};
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{component::Component, entity::Entity, system::Resource};
|
||||
|
||||
use crate::common::{ClientChannel, ClientMessage, ServerMessage};
|
||||
|
||||
pub enum ServerClient {
|
||||
Local(ClientChannel),
|
||||
}
|
||||
|
||||
impl ServerClient {
|
||||
pub fn recv(&mut self) -> Vec<ServerMessage> {
|
||||
match self {
|
||||
Self::Local(ch) => ch.recv().collect(),
|
||||
}
|
||||
}
|
||||
pub fn send(&self, msg: ClientMessage) {
|
||||
match self {
|
||||
Self::Local(ch) => ch.send(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct ServerClients {
|
||||
map: HashMap<Entity, ServerClient>,
|
||||
}
|
||||
|
||||
impl ServerClients {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn add(&mut self, id: Entity, client: ServerClient) {
|
||||
self.map.insert(id, client);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut ServerClients {
|
||||
type Item = (&'a Entity, &'a mut ServerClient);
|
||||
type IntoIter = hash_map::IterMut<'a, Entity, ServerClient>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.map.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
// I don't think it's worth putting a reciever in here rn
|
||||
// and moving that stuff into the ecs but we'll see
|
||||
#[derive(Component)]
|
||||
pub struct ClientComponent {
|
||||
send: Vec<ClientMessage>,
|
||||
}
|
||||
|
||||
impl ClientComponent {
|
||||
pub fn new() -> Self {
|
||||
Self { send: Vec::new() }
|
||||
}
|
||||
pub fn send(&mut self, msg: ClientMessage) {
|
||||
self.send.push(msg);
|
||||
}
|
||||
pub fn take(&mut self) -> Vec<ClientMessage> {
|
||||
std::mem::take(&mut self.send)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ClientBroadcast(Vec<ClientMessage>);
|
||||
impl ClientBroadcast {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
pub fn send(&mut self, msg: ClientMessage) {
|
||||
self.0.push(msg);
|
||||
}
|
||||
pub fn take(&mut self) -> Vec<ClientMessage> {
|
||||
std::mem::take(&mut self.0)
|
||||
}
|
||||
}
|
||||
+107
-43
@@ -1,21 +1,29 @@
|
||||
mod chunk;
|
||||
mod client;
|
||||
mod rsc;
|
||||
mod system;
|
||||
mod test;
|
||||
|
||||
use crate::{
|
||||
sync::{ClientChannel, ClientMessage, ClientSender, ServerMessage},
|
||||
world::{
|
||||
component::{Orientation, Pos, Synced, VoxelGrid, VoxelGridBundle},
|
||||
generation::generate,
|
||||
pub use client::*;
|
||||
|
||||
use crate::common::{
|
||||
component::{
|
||||
ChunkBundle, ChunkData, ChunkMap, ChunkMesh, ChunkPos, Orientation, PlayerBundle, Pos,
|
||||
VoxelGrid, VoxelGridBundle,
|
||||
},
|
||||
ClientChannel, ClientMessage, ServerMessage,
|
||||
};
|
||||
use bevy_ecs::{entity::Entity, query::With, system::SystemId, world::World};
|
||||
use bevy_ecs::{entity::Entity, system::SystemId, world::World};
|
||||
use chunk::ChunkManager;
|
||||
use client::{ClientBroadcast, ServerClient, ServerClients};
|
||||
use rsc::UPDATE_TIME;
|
||||
use std::time::{Duration, Instant};
|
||||
use test::spawn_test_stuff;
|
||||
|
||||
pub struct Server {
|
||||
update_time: Duration,
|
||||
target: Instant,
|
||||
client: ClientChannel,
|
||||
clients: ServerClients,
|
||||
world: World,
|
||||
systems: ServerSystems,
|
||||
mov: Vec<Entity>,
|
||||
@@ -24,23 +32,27 @@ pub struct Server {
|
||||
|
||||
pub struct ServerSystems {
|
||||
sync_pos: SystemId,
|
||||
sync_chunks: SystemId,
|
||||
}
|
||||
|
||||
impl ServerSystems {
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
sync_pos: world.register_system(system::sync::pos),
|
||||
sync_chunks: world.register_system(system::sync::chunks),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(client: ClientChannel) -> Self {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
world.insert_resource(ClientSender(client.sender()));
|
||||
world.insert_resource(ClientBroadcast::new());
|
||||
world.insert_resource(ChunkMap::new());
|
||||
world.insert_non_send_resource(ChunkManager::new());
|
||||
let systems = ServerSystems::new(&mut world);
|
||||
Self {
|
||||
client,
|
||||
clients: ServerClients::new(),
|
||||
world,
|
||||
systems,
|
||||
target: Instant::now(),
|
||||
@@ -50,58 +62,110 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_client(client: ClientChannel) -> Self {
|
||||
let mut s = Self::new();
|
||||
s.add_client(ServerClient::Local(client));
|
||||
s
|
||||
}
|
||||
|
||||
pub fn add_client(&mut self, client: ServerClient) {
|
||||
let id = self.world.spawn(ClientComponent::new()).id();
|
||||
self.clients.add(id, client);
|
||||
}
|
||||
|
||||
pub fn start(ch: ClientChannel) {
|
||||
Self::new(ch).run();
|
||||
Self::from_client(ch).run();
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
generate(&mut self.world);
|
||||
spawn_test_stuff(&mut self.world);
|
||||
loop {
|
||||
self.recv();
|
||||
let now = Instant::now();
|
||||
if now >= self.target {
|
||||
self.target += self.update_time;
|
||||
let mut q = self.world.query::<(Entity, &mut Pos)>();
|
||||
for (e, mut p) in q.iter_mut(&mut self.world) {
|
||||
if self.mov.contains(&e) {
|
||||
p.x += 0.1;
|
||||
}
|
||||
}
|
||||
self.world.run_system(self.systems.sync_pos).unwrap();
|
||||
self.world.clear_trackers();
|
||||
self.tick();
|
||||
}
|
||||
if self.stop {
|
||||
break;
|
||||
}
|
||||
self.send();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
let mut q = self.world.query::<(Entity, &mut Pos)>();
|
||||
for (e, mut p) in q.iter_mut(&mut self.world) {
|
||||
if self.mov.contains(&e) {
|
||||
p.x += 0.1;
|
||||
}
|
||||
}
|
||||
self.world.run_system(self.systems.sync_pos).unwrap();
|
||||
self.world.run_system(self.systems.sync_chunks).unwrap();
|
||||
self.world.clear_trackers();
|
||||
}
|
||||
|
||||
pub fn recv(&mut self) {
|
||||
for msg in self.client.recv() {
|
||||
match msg {
|
||||
ServerMessage::LoadWorld => {
|
||||
let mut q = self
|
||||
.world
|
||||
.query_filtered::<(Entity, &Pos, &Orientation, &VoxelGrid), With<Synced>>();
|
||||
// ePOG
|
||||
for (e, p, o, g) in q.iter(&self.world) {
|
||||
self.client.send(ClientMessage::SpawnVoxelGrid(
|
||||
e,
|
||||
VoxelGridBundle {
|
||||
pos: *p,
|
||||
orientation: *o,
|
||||
grid: g.clone(),
|
||||
},
|
||||
))
|
||||
for (id, client) in &mut self.clients {
|
||||
for msg in client.recv() {
|
||||
match msg {
|
||||
ServerMessage::Join => {
|
||||
let mut q = self
|
||||
.world
|
||||
.query::<(Entity, &Pos, &Orientation, &VoxelGrid)>();
|
||||
// ePOG
|
||||
for (e, p, o, g) in q.iter(&self.world) {
|
||||
client.send(ClientMessage::SpawnVoxelGrid(
|
||||
e,
|
||||
VoxelGridBundle {
|
||||
pos: *p,
|
||||
orientation: *o,
|
||||
grid: g.clone(),
|
||||
},
|
||||
))
|
||||
}
|
||||
let mut q = self
|
||||
.world
|
||||
.query::<(Entity, &ChunkPos, &ChunkData, &ChunkMesh)>();
|
||||
for (e, p, c, m) in q.iter(&self.world) {
|
||||
client.send(ClientMessage::LoadChunk(
|
||||
e,
|
||||
ChunkBundle {
|
||||
pos: *p,
|
||||
data: c.clone(),
|
||||
mesh: m.clone(),
|
||||
},
|
||||
))
|
||||
}
|
||||
self.world.entity_mut(*id).insert(PlayerBundle::new());
|
||||
}
|
||||
ServerMessage::SpawnVoxelGrid(grid) => {
|
||||
let e = self.world.spawn(grid.clone()).id();
|
||||
self.mov.push(e);
|
||||
self.world
|
||||
.resource_mut::<ClientBroadcast>()
|
||||
.send(ClientMessage::SpawnVoxelGrid(e, grid));
|
||||
}
|
||||
ServerMessage::Stop => {
|
||||
self.stop = true;
|
||||
}
|
||||
}
|
||||
ServerMessage::SpawnVoxelGrid(grid) => {
|
||||
let e = self.world.spawn((grid.clone(), Synced)).id();
|
||||
self.mov.push(e);
|
||||
self.client.send(ClientMessage::SpawnVoxelGrid(e, grid));
|
||||
}
|
||||
ServerMessage::Stop => {
|
||||
self.stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self) {
|
||||
let msgs = self.world.resource_mut::<ClientBroadcast>().take();
|
||||
for msg in &msgs {
|
||||
for (_, client) in &mut self.clients {
|
||||
client.send(msg.clone());
|
||||
}
|
||||
}
|
||||
let mut q = self.world.query::<(Entity, &mut ClientComponent)>();
|
||||
for (e, mut c) in q.iter_mut(&mut self.world) {
|
||||
if let Some(sc) = self.clients.get(&e) {
|
||||
for msg in c.take() {
|
||||
sc.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,72 @@
|
||||
use bevy_ecs::{entity::Entity, query::Changed, system::{Query, Res}};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
query::{Changed, With},
|
||||
system::{Commands, NonSendMut, Query, ResMut},
|
||||
};
|
||||
use nalgebra::Vector3;
|
||||
|
||||
use crate::{sync::{ClientMessage, ClientSender}, world::component::{Pos, Synced}};
|
||||
use crate::{
|
||||
common::{
|
||||
component::{
|
||||
chunk::{self, ChunkBundle, LoadedChunks},
|
||||
ChunkData, ChunkMesh, ChunkPos, Player, Pos,
|
||||
},
|
||||
ClientMessage,
|
||||
},
|
||||
server::{chunk::ChunkManager, client::ClientBroadcast, ClientComponent},
|
||||
};
|
||||
|
||||
pub fn pos(query: Query<(Entity, &Synced, &Pos), Changed<Pos>>, client: Res<ClientSender>) {
|
||||
for (e, _, pos) in query.iter() {
|
||||
client.send(ClientMessage::PosUpdate(e, *pos));
|
||||
pub fn pos(query: Query<(Entity, &Pos), Changed<Pos>>, mut clients: ResMut<ClientBroadcast>) {
|
||||
for (e, pos) in query.iter() {
|
||||
clients.send(ClientMessage::PosUpdate(e, *pos));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunks(
|
||||
mut players: Query<(&Pos, &mut LoadedChunks, &mut ClientComponent), With<Player>>,
|
||||
chunks: Query<(&ChunkPos, &ChunkData, &ChunkMesh)>,
|
||||
mut loader: NonSendMut<ChunkManager>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (pos, mut loaded, mut client) in &mut players {
|
||||
let fp = **pos / chunk::SIDE_LENGTH as f32;
|
||||
let player_chunk = Vector3::new(
|
||||
fp.x.floor() as i32,
|
||||
fp.y.floor() as i32,
|
||||
fp.z.floor() as i32,
|
||||
);
|
||||
let radius: i32 = 5;
|
||||
let width = radius * 2 - 1;
|
||||
let mut desired = Vec::new();
|
||||
for i in 0..width.pow(3) {
|
||||
let pos = Vector3::new(i % width, (i / width) % width, i / (width.pow(2)))
|
||||
- Vector3::from_element(radius - 1);
|
||||
let dist = pos.cast::<f32>().norm();
|
||||
if dist < radius as f32 {
|
||||
desired.push((dist, pos));
|
||||
}
|
||||
}
|
||||
desired.sort_by(|(da, ..), (db, ..)| da.total_cmp(db));
|
||||
for (_, pos) in desired {
|
||||
let coords = pos - player_chunk;
|
||||
let pos = ChunkPos(coords);
|
||||
if !loaded.contains(&pos) {
|
||||
if let Some(id) = loader.entity_at(&pos) {
|
||||
let (pos, data, mesh) = chunks.get(*id).unwrap();
|
||||
client.send(ClientMessage::LoadChunk(
|
||||
*id,
|
||||
ChunkBundle {
|
||||
pos: *pos,
|
||||
data: data.clone(),
|
||||
mesh: mesh.clone(),
|
||||
},
|
||||
));
|
||||
loaded.insert(*pos);
|
||||
} else {
|
||||
loader.queue(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loader.update(&mut commands);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
use crate::client::render::voxel::VoxelColor;
|
||||
use crate::common::component::{VoxelGrid, VoxelGridBundle};
|
||||
use bevy_ecs::world::World;
|
||||
use nalgebra::{Rotation3, UnitVector3, Vector3};
|
||||
use ndarray::Array3;
|
||||
|
||||
pub fn spawn_test_stuff(world: &mut World) {
|
||||
let dim = (15, 10, 10);
|
||||
world.spawn(VoxelGridBundle {
|
||||
pos: Vector3::new(0.0, 0.0, 20.0).into(),
|
||||
orientation: Rotation3::from_axis_angle(&Vector3::y_axis(), 0.5).into(),
|
||||
grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| {
|
||||
if x == z && x == y {
|
||||
VoxelColor::white()
|
||||
} else if z == 3 {
|
||||
VoxelColor {
|
||||
r: (x as f32 / dim.0 as f32 * 255.0) as u8,
|
||||
g: (y as f32 / dim.1 as f32 * 255.0) as u8,
|
||||
b: 100,
|
||||
a: 255,
|
||||
}
|
||||
} else if z == 0 {
|
||||
VoxelColor {
|
||||
r: (x as f32 / dim.0 as f32 * 255.0) as u8,
|
||||
g: (y as f32 / dim.1 as f32 * 255.0) as u8,
|
||||
b: 0,
|
||||
a: 100,
|
||||
}
|
||||
} else {
|
||||
VoxelColor::none()
|
||||
}
|
||||
})),
|
||||
});
|
||||
|
||||
// let dim = (1000, 2, 1000);
|
||||
// world.spawn((
|
||||
// VoxelGridBundle {
|
||||
// pos: Vector3::new(0.0, -2.1, 0.0).into(),
|
||||
// orientation: Rotation3::identity().into(),
|
||||
// grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| {
|
||||
// if y == 0 {
|
||||
// VoxelColor::random()
|
||||
// } else if (y == dim.1 - 1) && (x == 0 || x == dim.0 - 1 || z == 0 || z == dim.2 - 1)
|
||||
// {
|
||||
// VoxelColor {
|
||||
// r: 255,
|
||||
// g: 0,
|
||||
// b: 255,
|
||||
// a: 255,
|
||||
// }
|
||||
// } else {
|
||||
// VoxelColor::none()
|
||||
// }
|
||||
// })),
|
||||
// },
|
||||
// Synced,
|
||||
// ));
|
||||
|
||||
let dim = (3, 3, 3);
|
||||
world.spawn(VoxelGridBundle {
|
||||
pos: Vector3::new(0.0, 0.0, 16.5).into(),
|
||||
orientation: (Rotation3::from_axis_angle(&Vector3::y_axis(), std::f32::consts::PI / 4.0)
|
||||
* Rotation3::from_axis_angle(
|
||||
&UnitVector3::new_normalize(Vector3::new(1.0, 0.0, 1.0)),
|
||||
std::f32::consts::PI / 4.0,
|
||||
))
|
||||
.into(),
|
||||
grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(..)| VoxelColor {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 255,
|
||||
a: 255,
|
||||
})),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user