feat: reactive collision/sprite updates
parent
ff2ab7c3a2
commit
21255120c5
|
|
@ -627,6 +627,15 @@ dependencies = [
|
||||||
"radsort",
|
"radsort",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_prototype_debug_lines"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d9e933f5bf971fbc7e83b447db6758ed3b091faf4cd8e524a9406ae7acca096"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_ptr"
|
name = "bevy_ptr"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
@ -2071,6 +2080,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bevy",
|
"bevy",
|
||||||
"bevy-inspector-egui",
|
"bevy-inspector-egui",
|
||||||
|
"bevy_prototype_debug_lines",
|
||||||
"bevy_rapier2d",
|
"bevy_rapier2d",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"noise",
|
"noise",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.9.0", features = ["dynamic"] }
|
bevy = { version = "0.9.0", features = ["dynamic"] }
|
||||||
bevy-inspector-egui = "0.14.0"
|
bevy-inspector-egui = "0.14.0"
|
||||||
|
bevy_prototype_debug_lines = "0.9.0"
|
||||||
bevy_rapier2d = { path = "../bevy_rapier/bevy_rapier2d" }
|
bevy_rapier2d = { path = "../bevy_rapier/bevy_rapier2d" }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
noise = "0.8.2"
|
noise = "0.8.2"
|
||||||
|
|
|
||||||
19
src/game.rs
19
src/game.rs
|
|
@ -1,5 +1,6 @@
|
||||||
use bevy::{input::mouse::MouseWheel, prelude::*};
|
use bevy::{input::mouse::MouseWheel, prelude::*};
|
||||||
use bevy_inspector_egui::*;
|
use bevy_inspector_egui::*;
|
||||||
|
use bevy_prototype_debug_lines::DebugLinesPlugin;
|
||||||
use bevy_rapier2d::prelude::*;
|
use bevy_rapier2d::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -14,13 +15,13 @@ use self::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod chunk;
|
|
||||||
pub mod kinematic;
|
pub mod kinematic;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugin(DebugLinesPlugin::default())
|
||||||
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
|
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
|
||||||
.add_plugin(RapierDebugRenderPlugin::default())
|
.add_plugin(RapierDebugRenderPlugin::default())
|
||||||
.add_plugin(WorldInspectorPlugin::new())
|
.add_plugin(WorldInspectorPlugin::new())
|
||||||
|
|
@ -53,11 +54,17 @@ fn setup_debug_terrain(mut commands: Commands, mut terrain: ResMut<Terrain2D>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.spawn(Name::new("Left wall"))
|
commands
|
||||||
|
.spawn(Name::new("Left wall"))
|
||||||
.insert(Collider::halfspace(Vec2::X).unwrap())
|
.insert(Collider::halfspace(Vec2::X).unwrap())
|
||||||
.insert(TransformBundle::from_transform(Transform::from_translation(Vec3::new(0.0, 0.0, 0.0))));
|
.insert(TransformBundle::from_transform(
|
||||||
|
Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
|
||||||
commands.spawn(Name::new("Right wall"))
|
));
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn(Name::new("Right wall"))
|
||||||
.insert(Collider::halfspace(Vec2::NEG_X).unwrap())
|
.insert(Collider::halfspace(Vec2::NEG_X).unwrap())
|
||||||
.insert(TransformBundle::from_transform(Transform::from_translation(Vec3::new(WORLD_WIDTH as f32, 0.0, 0.0))));
|
.insert(TransformBundle::from_transform(
|
||||||
|
Transform::from_translation(Vec3::new(WORLD_WIDTH as f32, 0.0, 0.0)),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
use crate::terrain2d::Chunk2DIndex;
|
|
||||||
|
|
||||||
pub struct ChunkPlugin {}
|
|
||||||
|
|
||||||
#[derive(Reflect, Component, Default)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct Chunk {
|
|
||||||
pub index: Chunk2DIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Bundle)]
|
|
||||||
pub struct ChunkBundle {
|
|
||||||
pub chunk: Chunk,
|
|
||||||
pub sprite_bundle: SpriteBundle,
|
|
||||||
}
|
|
||||||
|
|
@ -19,30 +19,32 @@ pub struct Terrain2DPlugin;
|
||||||
|
|
||||||
impl Plugin for Terrain2DPlugin {
|
impl Plugin for Terrain2DPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<Chunk2DHandler>()
|
app.register_type::<TerrainChunk2D>()
|
||||||
.insert_resource(Terrain2D::new())
|
.insert_resource(Terrain2D::new())
|
||||||
.add_event::<TerrainEvent>()
|
.add_event::<TerrainEvent2D>()
|
||||||
.add_system(emit_terrain_events)
|
.add_system_to_stage(CoreStage::PostUpdate, emit_terrain_events)
|
||||||
.add_system(chunk_spawner);
|
.add_system(chunk_spawner)
|
||||||
|
.add_system(chunk_sprite_sync)
|
||||||
|
.add_system(chunk_collision_sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_terrain_events(
|
fn emit_terrain_events(
|
||||||
mut terrain: ResMut<Terrain2D>,
|
mut terrain: ResMut<Terrain2D>,
|
||||||
mut terrain_events: EventWriter<TerrainEvent>,
|
mut terrain_events: EventWriter<TerrainEvent2D>,
|
||||||
) {
|
) {
|
||||||
for event in terrain.events.drain(..) {
|
for event in terrain.events.drain(..) {
|
||||||
terrain_events.send(event)
|
terrain_events.send(event)
|
||||||
}
|
}
|
||||||
for (chunk_index, mut chunk) in terrain.chunk_iter_mut() {
|
for (chunk_index, chunk) in terrain.chunk_iter_mut() {
|
||||||
if let Some(rect) = &chunk.dirty_rect {
|
if let Some(rect) = &chunk.dirty_rect {
|
||||||
terrain_events.send(TerrainEvent::TexelsUpdated(*chunk_index, *rect));
|
terrain_events.send(TerrainEvent2D::TexelsUpdated(*chunk_index, *rect));
|
||||||
chunk.dirty_rect = None;
|
chunk.mark_clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum TerrainEvent {
|
pub enum TerrainEvent2D {
|
||||||
ChunkAdded(Chunk2DIndex),
|
ChunkAdded(Chunk2DIndex),
|
||||||
ChunkRemoved(Chunk2DIndex),
|
ChunkRemoved(Chunk2DIndex),
|
||||||
TexelsUpdated(Chunk2DIndex, ChunkRect),
|
TexelsUpdated(Chunk2DIndex, ChunkRect),
|
||||||
|
|
@ -51,7 +53,7 @@ pub enum TerrainEvent {
|
||||||
#[derive(Default, Resource)]
|
#[derive(Default, Resource)]
|
||||||
pub struct Terrain2D {
|
pub struct Terrain2D {
|
||||||
chunk_map: HashMap<Chunk2DIndex, Chunk2D>,
|
chunk_map: HashMap<Chunk2DIndex, Chunk2D>,
|
||||||
events: Vec<TerrainEvent>,
|
events: Vec<TerrainEvent2D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Terrain2D {
|
impl Terrain2D {
|
||||||
|
|
@ -64,11 +66,11 @@ impl Terrain2D {
|
||||||
|
|
||||||
pub fn add_chunk(&mut self, index: Chunk2DIndex, chunk: Chunk2D) {
|
pub fn add_chunk(&mut self, index: Chunk2DIndex, chunk: Chunk2D) {
|
||||||
self.chunk_map.insert(index, chunk);
|
self.chunk_map.insert(index, chunk);
|
||||||
self.events.push(TerrainEvent::ChunkAdded(index))
|
self.events.push(TerrainEvent2D::ChunkAdded(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_chunk(&mut self, index: Chunk2DIndex) {
|
pub fn remove_chunk(&mut self, index: Chunk2DIndex) {
|
||||||
self.events.push(TerrainEvent::ChunkRemoved(index));
|
self.events.push(TerrainEvent2D::ChunkRemoved(index));
|
||||||
self.chunk_map.remove(&index);
|
self.chunk_map.remove(&index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
local_to_texel_index, texel_index_to_local, Terrain2D, TerrainEvent, Texel2D, TexelID,
|
local_to_texel_index, texel_index_to_local, Terrain2D, TerrainEvent2D, Texel2D, TexelID,
|
||||||
NEIGHBOUR_INDEX_MAP,
|
NEIGHBOUR_INDEX_MAP,
|
||||||
};
|
};
|
||||||
use crate::util::{Segment2I, Vector2I};
|
use crate::util::{Segment2I, Vector2I};
|
||||||
|
|
@ -64,14 +64,30 @@ lazy_static! {
|
||||||
|
|
||||||
#[derive(Reflect, Component, Default)]
|
#[derive(Reflect, Component, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct Chunk2DHandler {
|
pub struct TerrainChunk2D {
|
||||||
pub index: Chunk2DIndex,
|
pub index: Chunk2DIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Component, Default)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TerrainChunkSpriteSync2D;
|
||||||
|
|
||||||
|
#[derive(Reflect, Component, Default)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TerrainChunkCollisionSync2D;
|
||||||
|
|
||||||
#[derive(Bundle, Default)]
|
#[derive(Bundle, Default)]
|
||||||
pub struct ChunkBundle {
|
pub struct ChunkSpriteBundle {
|
||||||
pub chunk: Chunk2DHandler,
|
pub chunk: TerrainChunk2D,
|
||||||
pub sprite_bundle: SpriteBundle,
|
pub sync_flag: TerrainChunkSpriteSync2D,
|
||||||
|
pub sprite: SpriteBundle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Bundle, Default)]
|
||||||
|
pub struct ChunkColliderBundle {
|
||||||
|
pub chunk: TerrainChunk2D,
|
||||||
|
pub sync_flag: TerrainChunkCollisionSync2D,
|
||||||
|
pub transform: TransformBundle,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Chunk2DIndex = Vector2I;
|
pub type Chunk2DIndex = Vector2I;
|
||||||
|
|
@ -191,6 +207,10 @@ impl Chunk2D {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mark_clean(&mut self) {
|
||||||
|
self.dirty_rect = None;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_texel(&self, position: &Vector2I) -> Option<Texel2D> {
|
pub fn get_texel(&self, position: &Vector2I) -> Option<Texel2D> {
|
||||||
local_to_texel_index(position).map(|i| self.texels[i])
|
local_to_texel_index(position).map(|i| self.texels[i])
|
||||||
}
|
}
|
||||||
|
|
@ -224,40 +244,171 @@ impl Chunk2D {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_texture_data(&self) -> Vec<u8> {
|
||||||
|
let mut image_data = Vec::with_capacity(Chunk2D::SIZE_X * Chunk2D::SIZE_Y * 4);
|
||||||
|
let fallback: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
|
||||||
|
for y in (0..Chunk2D::SIZE_Y).rev() {
|
||||||
|
for x in 0..Chunk2D::SIZE_X {
|
||||||
|
image_data.append(
|
||||||
|
&mut COLOR_MAP
|
||||||
|
.get(
|
||||||
|
&self
|
||||||
|
.get_texel(&Vector2I::new(x as i32, y as i32))
|
||||||
|
.unwrap()
|
||||||
|
.id,
|
||||||
|
)
|
||||||
|
.unwrap_or(&fallback)
|
||||||
|
.to_vec()
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image_data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_collision_data(&self) -> Vec<Vec<Vec2>> {
|
||||||
|
let mut islands: Vec<Island> = Vec::new();
|
||||||
|
for i in 0..self.texels.len() {
|
||||||
|
let local = texel_index_to_local(i);
|
||||||
|
|
||||||
|
let edge_mask: u8 = if local.y == Chunk2D::SIZE.y - 1 {
|
||||||
|
1 << 0
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
} | if local.x == Chunk2D::SIZE.x - 1 {
|
||||||
|
1 << 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
} | if local.y == 0 { 1 << 2 } else { 0 }
|
||||||
|
| if local.x == 0 { 1 << 3 } else { 0 };
|
||||||
|
|
||||||
|
let mut sides: Vec<Segment2I>;
|
||||||
|
if self.texels[i].is_empty() {
|
||||||
|
sides = MST_CASE_MAP[self.texels[i].neighbour_mask as usize]
|
||||||
|
.iter()
|
||||||
|
.clone()
|
||||||
|
.map(|side| Segment2I {
|
||||||
|
from: side.from + local,
|
||||||
|
to: side.to + local,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
} else if !self.texels[i].is_empty() && edge_mask != 0 {
|
||||||
|
sides = Vec::with_capacity(Chunk2D::SIZE_X * 2 + Chunk2D::SIZE_Y * 2);
|
||||||
|
for i in 0..MST_EDGE_CASE_MAP.len() {
|
||||||
|
if edge_mask & (1 << i) != 0 {
|
||||||
|
let edge = MST_EDGE_CASE_MAP[i];
|
||||||
|
sides.push(Segment2I {
|
||||||
|
from: edge.from + local,
|
||||||
|
to: edge.to + local,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for side in sides {
|
||||||
|
// Check if the side can be attached to any island
|
||||||
|
// The naming of front and back are kind of misleading, and come from the VecDeque type.
|
||||||
|
// You can think of the front as the beginning of the island loop, and back the end.
|
||||||
|
|
||||||
|
// Connect to an island if possible, otherwise create a new island
|
||||||
|
{
|
||||||
|
let mut connected_to: Option<&mut Island> = None;
|
||||||
|
for island in islands.iter_mut() {
|
||||||
|
if island.back().is_some() && island.back().unwrap().to == side.from {
|
||||||
|
connected_to = Some(island);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match connected_to {
|
||||||
|
Some(back) => {
|
||||||
|
back.push_back(side);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut island: Island = Island::new();
|
||||||
|
island.push_back(side);
|
||||||
|
islands.push(island);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find connected islands
|
||||||
|
loop {
|
||||||
|
let mut merge_index: Option<usize> = None;
|
||||||
|
'outer: for i in 0..islands.len() {
|
||||||
|
for j in 0..islands.len() {
|
||||||
|
if i == j {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if islands[i].back().is_some()
|
||||||
|
&& islands[j].front().is_some()
|
||||||
|
&& islands[i].back().unwrap().to == islands[j].front().unwrap().from
|
||||||
|
{
|
||||||
|
merge_index = Some(i);
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge connected islands
|
||||||
|
match merge_index {
|
||||||
|
Some(index) => {
|
||||||
|
let mut merge_from = islands.swap_remove(index);
|
||||||
|
match islands.iter_mut().find(|island| match island.front() {
|
||||||
|
Some(front) => front.from == merge_from.back().unwrap().to,
|
||||||
|
None => false,
|
||||||
|
}) {
|
||||||
|
Some(merge_to) => loop {
|
||||||
|
match merge_from.pop_back() {
|
||||||
|
Some(segment) => merge_to.push_front(segment),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result: Vec<Vec<Vec2>> = Vec::with_capacity(islands.len());
|
||||||
|
for island in islands {
|
||||||
|
if island.len() < 4 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut points: Vec<Vec2> = Vec::with_capacity(island.len() + 1);
|
||||||
|
points.push(Vec2::from(island.front().unwrap().from));
|
||||||
|
let mut current_angle: Option<f32> = None;
|
||||||
|
for side in island {
|
||||||
|
if current_angle.is_some() && (current_angle.unwrap() - side.angle()).abs() < 0.1 {
|
||||||
|
let len = points.len();
|
||||||
|
points[len - 1] = Vec2::from(side.to)
|
||||||
|
} else {
|
||||||
|
current_angle = Some(side.angle());
|
||||||
|
points.push(Vec2::from(side.to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(points);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chunk_spawner(
|
pub fn chunk_spawner(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut terrain_events: EventReader<TerrainEvent>,
|
mut terrain_events: EventReader<TerrainEvent2D>,
|
||||||
mut images: ResMut<Assets<Image>>,
|
mut images: ResMut<Assets<Image>>,
|
||||||
terrain: Res<Terrain2D>,
|
chunk_query: Query<(Entity, &TerrainChunk2D)>,
|
||||||
chunk_query: Query<(Entity, &Chunk2DHandler)>,
|
|
||||||
) {
|
) {
|
||||||
for terrain_event in terrain_events.iter() {
|
for terrain_event in terrain_events.iter() {
|
||||||
match terrain_event {
|
match terrain_event {
|
||||||
TerrainEvent::ChunkAdded(chunk_index) => {
|
TerrainEvent2D::ChunkAdded(chunk_index) => {
|
||||||
let chunk = terrain.index_to_chunk(chunk_index).unwrap();
|
// Create unique handle for the image
|
||||||
|
// TODO: recycling image data would be nice
|
||||||
// Chunk sprite
|
|
||||||
// TODO: Move to separate function
|
|
||||||
let mut image_data = Vec::with_capacity(Chunk2D::SIZE_X * Chunk2D::SIZE_Y * 4);
|
|
||||||
let fallback: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
|
|
||||||
for y in (0..Chunk2D::SIZE_Y).rev() {
|
|
||||||
for x in 0..Chunk2D::SIZE_X {
|
|
||||||
image_data.append(
|
|
||||||
&mut COLOR_MAP
|
|
||||||
.get(
|
|
||||||
&chunk
|
|
||||||
.get_texel(&Vector2I::new(x as i32, y as i32))
|
|
||||||
.unwrap()
|
|
||||||
.id,
|
|
||||||
)
|
|
||||||
.unwrap_or(&fallback)
|
|
||||||
.to_vec()
|
|
||||||
.clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut image = Image::new(
|
let mut image = Image::new(
|
||||||
Extent3d {
|
Extent3d {
|
||||||
width: Chunk2D::SIZE_X as u32,
|
width: Chunk2D::SIZE_X as u32,
|
||||||
|
|
@ -265,24 +416,19 @@ pub fn chunk_spawner(
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
bevy::render::render_resource::TextureDimension::D2,
|
bevy::render::render_resource::TextureDimension::D2,
|
||||||
image_data,
|
vec![0x00; Chunk2D::SIZE_X * Chunk2D::SIZE_Y * 4],
|
||||||
bevy::render::render_resource::TextureFormat::Rgba8Unorm,
|
bevy::render::render_resource::TextureFormat::Rgba8Unorm,
|
||||||
);
|
);
|
||||||
|
|
||||||
image.sampler_descriptor = ImageSampler::nearest();
|
image.sampler_descriptor = ImageSampler::nearest();
|
||||||
let texture = images.add(image);
|
let texture = images.add(image);
|
||||||
|
|
||||||
// Chunk collision
|
|
||||||
// TODO: Move to separate function
|
|
||||||
let collision_islands = generate_collision(chunk);
|
|
||||||
|
|
||||||
let pos = Vec2::from(*chunk_index * Chunk2D::SIZE);
|
let pos = Vec2::from(*chunk_index * Chunk2D::SIZE);
|
||||||
commands
|
commands
|
||||||
.spawn(ChunkBundle {
|
.spawn(ChunkSpriteBundle {
|
||||||
chunk: Chunk2DHandler {
|
chunk: TerrainChunk2D {
|
||||||
index: *chunk_index,
|
index: *chunk_index,
|
||||||
},
|
},
|
||||||
sprite_bundle: SpriteBundle {
|
sprite: SpriteBundle {
|
||||||
sprite: Sprite {
|
sprite: Sprite {
|
||||||
custom_size: Some(Vec2::from(Chunk2D::SIZE)),
|
custom_size: Some(Vec2::from(Chunk2D::SIZE)),
|
||||||
anchor: bevy::sprite::Anchor::BottomLeft,
|
anchor: bevy::sprite::Anchor::BottomLeft,
|
||||||
|
|
@ -292,160 +438,172 @@ pub fn chunk_spawner(
|
||||||
transform: Transform::from_translation(Vec3::new(pos.x, pos.y, 0.0)),
|
transform: Transform::from_translation(Vec3::new(pos.x, pos.y, 0.0)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
..default()
|
||||||
})
|
})
|
||||||
.insert(Name::new(format!(
|
.insert(Name::new(format!(
|
||||||
"Chunk {},{}",
|
"Chunk Sprite {},{}",
|
||||||
chunk_index.x, chunk_index.y
|
chunk_index.x, chunk_index.y
|
||||||
)))
|
)));
|
||||||
.with_children(|builder| {
|
|
||||||
let mut index = 1;
|
commands
|
||||||
for island in collision_islands.iter() {
|
.spawn(ChunkColliderBundle {
|
||||||
builder
|
chunk: TerrainChunk2D {
|
||||||
.spawn(Collider::polyline(island.clone(), None))
|
index: *chunk_index,
|
||||||
.insert(TransformBundle::default())
|
},
|
||||||
.insert(Name::new(format!("Collision #{index}")));
|
transform: TransformBundle::from_transform(Transform::from_translation(
|
||||||
index += 1;
|
Vec3::new(pos.x, pos.y, 0.0),
|
||||||
}
|
)),
|
||||||
});
|
..default()
|
||||||
|
})
|
||||||
|
.insert(Name::new(format!(
|
||||||
|
"Chunk Collider {},{}",
|
||||||
|
chunk_index.x, chunk_index.y
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
TerrainEvent::ChunkRemoved(chunk_index) => {
|
TerrainEvent2D::ChunkRemoved(chunk_index) => {
|
||||||
for (entity, chunk) in chunk_query.iter() {
|
for (entity, chunk) in chunk_query.iter() {
|
||||||
if chunk.index == *chunk_index {
|
if chunk.index == *chunk_index {
|
||||||
commands.entity(entity).despawn_recursive();
|
commands.entity(entity).despawn_recursive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TerrainEvent::TexelsUpdated(chunk_index, rect) => {}
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_collision(chunk: &Chunk2D) -> Vec<Vec<Vec2>> {
|
/**
|
||||||
let mut islands: Vec<Island> = Vec::new();
|
Update the chunk sprite as needed
|
||||||
for i in 0..chunk.texels.len() {
|
*/
|
||||||
let local = texel_index_to_local(i);
|
pub fn chunk_sprite_sync(
|
||||||
|
mut terrain_events: EventReader<TerrainEvent2D>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut images: ResMut<Assets<Image>>,
|
||||||
|
mut sprite_query: Query<&mut Sprite>,
|
||||||
|
terrain: Res<Terrain2D>,
|
||||||
|
added_chunk_query: Query<
|
||||||
|
(Entity, &TerrainChunk2D),
|
||||||
|
(With<TerrainChunkSpriteSync2D>, Changed<TerrainChunk2D>),
|
||||||
|
>,
|
||||||
|
chunk_query: Query<(Entity, &TerrainChunk2D), (With<TerrainChunkSpriteSync2D>, With<Sprite>)>,
|
||||||
|
texture_query: Query<&Handle<Image>>,
|
||||||
|
) {
|
||||||
|
let mut updated_chunks: Vec<(Entity, &TerrainChunk2D, Option<ChunkRect>)> = vec![];
|
||||||
|
|
||||||
let edge_mask: u8 = if local.y == Chunk2D::SIZE.y - 1 {
|
// Check for added components
|
||||||
1 << 0
|
for (added_entity, added_chunk) in added_chunk_query.iter() {
|
||||||
} else {
|
updated_chunks.push((added_entity, added_chunk, None));
|
||||||
0
|
}
|
||||||
} | if local.x == Chunk2D::SIZE.x - 1 {
|
|
||||||
1 << 1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
} | if local.y == 0 { 1 << 2 } else { 0 }
|
|
||||||
| if local.x == 0 { 1 << 3 } else { 0 };
|
|
||||||
|
|
||||||
let mut sides: Vec<Segment2I>;
|
// Check for terrain events
|
||||||
if chunk.texels[i].is_empty() {
|
for event in terrain_events.iter() {
|
||||||
sides = MST_CASE_MAP[chunk.texels[i].neighbour_mask as usize]
|
for (entity, chunk) in chunk_query.iter() {
|
||||||
.iter()
|
let (chunk_index, rect) = match event {
|
||||||
.clone()
|
TerrainEvent2D::ChunkAdded(chunk_index) => {
|
||||||
.map(|side| Segment2I {
|
// The entity should not have the time to react to the event since it was just made
|
||||||
from: side.from + local,
|
println!("[chunk_sprite_sync -> TerrainEvent2D::ChunkAdded] This probably shouldn't be firing, maybe the chunk was destroyed and immediately created? chunk: {chunk_index:?}");
|
||||||
to: side.to + local,
|
(chunk_index, None)
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
} else if !chunk.texels[i].is_empty() && edge_mask != 0 {
|
|
||||||
sides = Vec::with_capacity(Chunk2D::SIZE_X * 2 + Chunk2D::SIZE_Y * 2);
|
|
||||||
for i in 0..MST_EDGE_CASE_MAP.len() {
|
|
||||||
if edge_mask & (1 << i) != 0 {
|
|
||||||
let edge = MST_EDGE_CASE_MAP[i];
|
|
||||||
sides.push(Segment2I {
|
|
||||||
from: edge.from + local,
|
|
||||||
to: edge.to + local,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
TerrainEvent2D::TexelsUpdated(chunk_index, rect) => (chunk_index, Some(*rect)),
|
||||||
} else {
|
_ => continue,
|
||||||
continue;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
for side in sides {
|
if *chunk_index != chunk.index {
|
||||||
// Check if the side can be attached to any island
|
continue;
|
||||||
// The naming of front and back are kind of misleading, and come from the VecDeque type.
|
};
|
||||||
// You can think of the front as the beginning of the island loop, and back the end.
|
|
||||||
|
|
||||||
// Connect to an island if possible, otherwise create a new island
|
updated_chunks.push((entity, chunk, rect));
|
||||||
{
|
|
||||||
let mut connected_to: Option<&mut Island> = None;
|
|
||||||
for island in islands.iter_mut() {
|
|
||||||
if island.back().is_some() && island.back().unwrap().to == side.from {
|
|
||||||
connected_to = Some(island);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match connected_to {
|
|
||||||
Some(back) => {
|
|
||||||
back.push_back(side);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut island: Island = Island::new();
|
|
||||||
island.push_back(side);
|
|
||||||
islands.push(island);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find connected islands
|
|
||||||
loop {
|
|
||||||
let mut merge_index: Option<usize> = None;
|
|
||||||
'outer: for i in 0..islands.len() {
|
|
||||||
for j in 0..islands.len() {
|
|
||||||
if i == j {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if islands[i].back().is_some()
|
|
||||||
&& islands[j].front().is_some()
|
|
||||||
&& islands[i].back().unwrap().to == islands[j].front().unwrap().from
|
|
||||||
{
|
|
||||||
merge_index = Some(i);
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge connected islands
|
|
||||||
match merge_index {
|
|
||||||
Some(index) => {
|
|
||||||
let mut merge_from = islands.swap_remove(index);
|
|
||||||
match islands.iter_mut().find(|island| match island.front() {
|
|
||||||
Some(front) => front.from == merge_from.back().unwrap().to,
|
|
||||||
None => false,
|
|
||||||
}) {
|
|
||||||
Some(merge_to) => loop {
|
|
||||||
match merge_from.pop_back() {
|
|
||||||
Some(segment) => merge_to.push_front(segment),
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result: Vec<Vec<Vec2>> = Vec::with_capacity(islands.len());
|
// Update sprite
|
||||||
for island in islands {
|
for (entity, chunk, rect) in updated_chunks {
|
||||||
if island.len() < 4 {
|
let chunk = terrain.index_to_chunk(&chunk.index).unwrap();
|
||||||
continue;
|
let rect = rect.unwrap_or(ChunkRect {
|
||||||
}
|
min: Vector2I::ZERO,
|
||||||
let mut points: Vec<Vec2> = Vec::with_capacity(island.len() + 1);
|
max: Chunk2D::SIZE - Vector2I::ONE,
|
||||||
points.push(Vec2::from(island.front().unwrap().from));
|
});
|
||||||
let mut current_angle: Option<f32> = None;
|
|
||||||
for side in island {
|
let mut sprite = match sprite_query.get_mut(entity) {
|
||||||
if current_angle.is_some() && (current_angle.unwrap() - side.angle()).abs() < 0.1 {
|
Ok(sprite) => sprite,
|
||||||
let len = points.len();
|
Err(err) => {
|
||||||
points[len - 1] = Vec2::from(side.to)
|
println!("[chunk_sprite_sync] Sprite component not found for entity:");
|
||||||
} else {
|
commands.entity(entity).log_components();
|
||||||
current_angle = Some(side.angle());
|
println!("{err:?}");
|
||||||
points.push(Vec2::from(side.to));
|
continue;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
result.push(points);
|
|
||||||
|
let handle = texture_query.get(entity).unwrap();
|
||||||
|
let mut image = images.get_mut(handle).unwrap();
|
||||||
|
let image_data = chunk.create_texture_data();
|
||||||
|
image.data = image_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create and update colliders for chunk as needed
|
||||||
|
*/
|
||||||
|
pub fn chunk_collision_sync(
|
||||||
|
mut terrain_events: EventReader<TerrainEvent2D>,
|
||||||
|
mut commands: Commands,
|
||||||
|
terrain: Res<Terrain2D>,
|
||||||
|
added_chunk_query: Query<
|
||||||
|
(Entity, &TerrainChunk2D),
|
||||||
|
(With<TerrainChunkCollisionSync2D>, Changed<TerrainChunk2D>),
|
||||||
|
>,
|
||||||
|
chunk_query: Query<(Entity, &TerrainChunk2D), With<TerrainChunkCollisionSync2D>>,
|
||||||
|
child_collider_query: Query<&Children, With<Collider>>,
|
||||||
|
) {
|
||||||
|
let mut updated_chunks = vec![];
|
||||||
|
|
||||||
|
// Check for added components
|
||||||
|
for (added_entity, added_chunk) in added_chunk_query.iter() {
|
||||||
|
updated_chunks.push((added_entity, added_chunk));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for terrain events
|
||||||
|
for event in terrain_events.iter() {
|
||||||
|
for (entity, chunk) in chunk_query.iter() {
|
||||||
|
let chunk_index = match event {
|
||||||
|
TerrainEvent2D::ChunkAdded(chunk_index) => {
|
||||||
|
// The entity should not have the time to react to the event since it was just made
|
||||||
|
println!("[chunk_collision_sync -> TerrainEvent2D::ChunkAdded] This probably shouldn't be firing, maybe the chunk was destroyed and immediately created? chunk: {chunk_index:?}");
|
||||||
|
chunk_index
|
||||||
|
}
|
||||||
|
TerrainEvent2D::TexelsUpdated(chunk_index, _) => chunk_index,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if *chunk_index != chunk.index {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
updated_chunks.push((entity, chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (entity, chunk) in updated_chunks.iter() {
|
||||||
|
// Remove old colliders
|
||||||
|
for children in child_collider_query.get(*entity) {
|
||||||
|
for child in children {
|
||||||
|
commands.entity(*child).despawn_recursive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = terrain.index_to_chunk(&chunk.index).unwrap();
|
||||||
|
|
||||||
|
// Add new colliders
|
||||||
|
let collision_islands = chunk.create_collision_data();
|
||||||
|
commands.entity(*entity).with_children(|builder| {
|
||||||
|
let mut index = 1;
|
||||||
|
for island in collision_islands.iter() {
|
||||||
|
builder
|
||||||
|
.spawn(Collider::polyline(island.clone(), None))
|
||||||
|
.insert(TransformBundle::default())
|
||||||
|
.insert(Name::new(format!("Island #{index}")));
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue