Compare commits
8 Commits
master
...
feat/shado
| Author | SHA1 | Date |
|---|---|---|
|
|
c96625b8eb | |
|
|
499e6ded41 | |
|
|
b13ee649be | |
|
|
ca65416c73 | |
|
|
ddce5075ce | |
|
|
c1894235ba | |
|
|
69c176ed88 | |
|
|
3fbe81d388 |
13
src/game.rs
13
src/game.rs
|
|
@ -2,8 +2,6 @@ use crate::{debug, game_setup};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_ecs_ldtk::{LdtkWorldBundle, LevelSelection};
|
use bevy_ecs_ldtk::{LdtkWorldBundle, LevelSelection};
|
||||||
|
|
||||||
use self::darkness::SpotLight2D;
|
|
||||||
|
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod darkness;
|
pub mod darkness;
|
||||||
pub mod ldtk;
|
pub mod ldtk;
|
||||||
|
|
@ -26,15 +24,4 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
|
||||||
ldtk_handle: assets.load("levels/world.ldtk"),
|
ldtk_handle: assets.load("levels/world.ldtk"),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.spawn((
|
|
||||||
Name::new("Spot light"),
|
|
||||||
SpatialBundle::from_transform(Transform::from_xyz(32.0, 31.0, 0.0).with_rotation(
|
|
||||||
Quat::from_euler(EulerRot::YXZ, 0.0, 0.0, f32::to_radians(-90.0)),
|
|
||||||
)),
|
|
||||||
SpotLight2D {
|
|
||||||
radius: 100.0,
|
|
||||||
angle: 1.0,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use bevy::render::camera::ScalingMode;
|
||||||
use bevy::{input::mouse::MouseMotion, transform::TransformSystem};
|
use bevy::{input::mouse::MouseMotion, transform::TransformSystem};
|
||||||
use bevy_ecs_ldtk::prelude::*;
|
use bevy_ecs_ldtk::prelude::*;
|
||||||
|
|
||||||
use super::darkness::{PointLight2D, SpotLight2D};
|
use super::darkness::{PointLight2D, ShadowMesh, SpotLight2D};
|
||||||
|
|
||||||
pub struct GameCameraPlugin;
|
pub struct GameCameraPlugin;
|
||||||
|
|
||||||
|
|
@ -95,6 +95,7 @@ fn camera_setup(mut commands: Commands) {
|
||||||
ComputedVisibility::default(),
|
ComputedVisibility::default(),
|
||||||
CameraRoomRestraint,
|
CameraRoomRestraint,
|
||||||
PointLight2D { radius: 30.0 },
|
PointLight2D { radius: 30.0 },
|
||||||
|
ShadowMesh::default(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,13 +119,32 @@ fn free_system(
|
||||||
mut free_query: Query<(&mut Transform, &GameCamera, &OrthographicProjection)>,
|
mut free_query: Query<(&mut Transform, &GameCamera, &OrthographicProjection)>,
|
||||||
mut mouse_events: EventReader<MouseMotion>,
|
mut mouse_events: EventReader<MouseMotion>,
|
||||||
mouse_input: Res<Input<MouseButton>>,
|
mouse_input: Res<Input<MouseButton>>,
|
||||||
|
key_input: Res<Input<KeyCode>>,
|
||||||
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
let raw_mouse_motion: Vec2 = mouse_events.iter().map(|e| e.delta).sum();
|
let raw_mouse_motion: Vec2 = mouse_events.iter().map(|e| e.delta).sum();
|
||||||
for (mut transform, camera, projection) in free_query.iter_mut() {
|
for (mut transform, camera, projection) in free_query.iter_mut() {
|
||||||
if camera.mode == CameraMode::Free {
|
if camera.mode == CameraMode::Free {
|
||||||
let mouse_motion = raw_mouse_motion * projection.scale * Vec2::new(-1.0, 1.0);
|
let mut motion = Vec2::ZERO;
|
||||||
if mouse_input.pressed(MouseButton::Middle) {
|
if mouse_input.pressed(MouseButton::Middle) {
|
||||||
input_movement(&mut transform, mouse_motion)
|
motion += raw_mouse_motion * projection.scale * Vec2::new(-1.0, 1.0);
|
||||||
|
}
|
||||||
|
motion.x += match (key_input.pressed(KeyCode::A), key_input.pressed(KeyCode::D)) {
|
||||||
|
(true, false) => -1.0,
|
||||||
|
(false, true) => 1.0,
|
||||||
|
(_, _) => 0.0,
|
||||||
|
} * projection.scale
|
||||||
|
* time.delta_seconds()
|
||||||
|
* 100.0;
|
||||||
|
motion.y += match (key_input.pressed(KeyCode::S), key_input.pressed(KeyCode::W)) {
|
||||||
|
(true, false) => -1.0,
|
||||||
|
(false, true) => 1.0,
|
||||||
|
(_, _) => 0.0,
|
||||||
|
} * projection.scale
|
||||||
|
* time.delta_seconds()
|
||||||
|
* 100.0;
|
||||||
|
if motion != Vec2::ZERO {
|
||||||
|
input_movement(&mut transform, motion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ use bevy::{
|
||||||
sprite::{Material2dPlugin, Mesh2dHandle},
|
sprite::{Material2dPlugin, Mesh2dHandle},
|
||||||
};
|
};
|
||||||
use bevy_ecs_ldtk::{LdtkLevel, LevelEvent};
|
use bevy_ecs_ldtk::{LdtkLevel, LevelEvent};
|
||||||
|
use bevy_prototype_debug_lines::DebugLines;
|
||||||
|
use bevy_rapier2d::prelude::{Collider, QueryFilter, RapierContext};
|
||||||
|
|
||||||
|
use crate::{debug::DebugMode, util::vec2_intersection};
|
||||||
|
|
||||||
mod material;
|
mod material;
|
||||||
|
|
||||||
|
|
@ -16,7 +20,7 @@ pub use material::*;
|
||||||
// Needs to be the same as in darkness.wgsl
|
// Needs to be the same as in darkness.wgsl
|
||||||
pub const MAX_POINT_LIGHTS: usize = 64;
|
pub const MAX_POINT_LIGHTS: usize = 64;
|
||||||
pub const MAX_SPOT_LIGHTS: usize = 64;
|
pub const MAX_SPOT_LIGHTS: usize = 64;
|
||||||
pub const SHADOWMAP_RESOLUTION: usize = 256;
|
pub const SHADOWMAP_RESOLUTION: usize = 1024;
|
||||||
|
|
||||||
pub struct DarknessPlugin;
|
pub struct DarknessPlugin;
|
||||||
|
|
||||||
|
|
@ -24,11 +28,12 @@ impl Plugin for DarknessPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<PointLight2D>()
|
app.register_type::<PointLight2D>()
|
||||||
.register_type::<SpotLight2D>()
|
.register_type::<SpotLight2D>()
|
||||||
|
.register_type::<ShadowMesh>()
|
||||||
.register_type::<VisibilityBlocker>()
|
.register_type::<VisibilityBlocker>()
|
||||||
.register_asset_reflect::<DarknessMaterial>()
|
.register_asset_reflect::<DarknessMaterial>()
|
||||||
.add_plugins(Material2dPlugin::<DarknessMaterial>::default())
|
.add_plugins(Material2dPlugin::<DarknessMaterial>::default())
|
||||||
.add_systems(Update, add_to_level)
|
.add_systems(Update, add_to_level)
|
||||||
.add_systems(Last, prepare_lights);
|
.add_systems(Last, (prepare_lights, prepare_shadows).chain());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,6 +87,19 @@ pub(crate) struct GpuSpotLight2D {
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct VisibilityBlocker;
|
pub struct VisibilityBlocker;
|
||||||
|
|
||||||
|
#[derive(Reflect, Default, Debug, Clone, Copy)]
|
||||||
|
pub struct ShadowVertex {
|
||||||
|
pub point: Vec2,
|
||||||
|
pub angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug, Clone)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct ShadowMesh {
|
||||||
|
pub light_index: usize,
|
||||||
|
pub vertices: Vec<ShadowVertex>,
|
||||||
|
}
|
||||||
|
|
||||||
fn add_to_level(
|
fn add_to_level(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut level_events: EventReader<LevelEvent>,
|
mut level_events: EventReader<LevelEvent>,
|
||||||
|
|
@ -146,9 +164,9 @@ fn add_to_level(
|
||||||
DarknessMeshBundle {
|
DarknessMeshBundle {
|
||||||
transform: Transform::from_xyz(0.0, 0.0, 100.0),
|
transform: Transform::from_xyz(0.0, 0.0, 100.0),
|
||||||
mesh: Mesh2dHandle(meshes.add(plane)),
|
mesh: Mesh2dHandle(meshes.add(plane)),
|
||||||
material: materials.add(DarknessMaterial::new(
|
material: materials.add(DarknessMaterial {
|
||||||
Color::rgba(0.0, 0.0, 0.0, 0.75),
|
color: Color::rgba(0.0, 0.0, 0.0, 0.75),
|
||||||
Some(images.add(Image::new(
|
shadowmap_texture: Some(images.add(Image::new(
|
||||||
Extent3d {
|
Extent3d {
|
||||||
width: width as u32,
|
width: width as u32,
|
||||||
height: height as u32,
|
height: height as u32,
|
||||||
|
|
@ -158,7 +176,8 @@ fn add_to_level(
|
||||||
vec![0; width * height * 4],
|
vec![0; width * height * 4],
|
||||||
bevy::render::render_resource::TextureFormat::R32Float,
|
bevy::render::render_resource::TextureFormat::R32Float,
|
||||||
))),
|
))),
|
||||||
)),
|
..default()
|
||||||
|
}),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
@ -171,14 +190,21 @@ fn add_to_level(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_lights(
|
fn prepare_lights(
|
||||||
|
mut shadow_mesh_query: Query<&mut ShadowMesh>,
|
||||||
mut materials: ResMut<Assets<DarknessMaterial>>,
|
mut materials: ResMut<Assets<DarknessMaterial>>,
|
||||||
mut images: ResMut<Assets<Image>>,
|
mut images: ResMut<Assets<Image>>,
|
||||||
|
mut debug_draw: ResMut<DebugLines>,
|
||||||
|
debug_mode: Res<DebugMode>,
|
||||||
|
rapier_context: Res<RapierContext>,
|
||||||
|
visibility_blocker_query: Query<&VisibilityBlocker>,
|
||||||
|
transform_query: Query<&GlobalTransform>,
|
||||||
|
collider_query: Query<&Collider>,
|
||||||
material_query: Query<&Handle<DarknessMaterial>>,
|
material_query: Query<&Handle<DarknessMaterial>>,
|
||||||
point_light_query: Query<(&GlobalTransform, &PointLight2D)>,
|
point_light_query: Query<(&GlobalTransform, &PointLight2D, Entity)>,
|
||||||
spot_light_query: Query<(&GlobalTransform, &SpotLight2D)>,
|
spot_light_query: Query<(&GlobalTransform, &SpotLight2D, Entity)>,
|
||||||
) {
|
) {
|
||||||
let point_lights: Vec<(_, _)> = point_light_query.iter().collect();
|
let point_lights: Vec<(_, _, _)> = point_light_query.iter().collect();
|
||||||
let spot_lights: Vec<(_, _)> = spot_light_query.iter().collect();
|
let spot_lights: Vec<(_, _, _)> = spot_light_query.iter().collect();
|
||||||
for handle in &material_query {
|
for handle in &material_query {
|
||||||
let material = match materials.get_mut(handle) {
|
let material = match materials.get_mut(handle) {
|
||||||
Some(material) => material,
|
Some(material) => material,
|
||||||
|
|
@ -197,27 +223,62 @@ fn prepare_lights(
|
||||||
point_lights
|
point_lights
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(i, (transform, light))| {
|
.for_each(|(i, (transform, light, entity))| {
|
||||||
|
let rect = light.aabb();
|
||||||
|
let polygon = get_light_geometry(
|
||||||
|
&rapier_context,
|
||||||
|
&visibility_blocker_query,
|
||||||
|
&transform_query,
|
||||||
|
&collider_query,
|
||||||
|
Rect::from_corners(
|
||||||
|
rect.min + transform.translation().truncate(),
|
||||||
|
rect.max + transform.translation().truncate(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Ok(mut shadow_mesh) = shadow_mesh_query.get_mut(*entity) {
|
||||||
|
shadow_mesh.light_index = i;
|
||||||
|
shadow_mesh.vertices = polygon
|
||||||
|
.iter()
|
||||||
|
.map(|point| ShadowVertex {
|
||||||
|
point: *point,
|
||||||
|
angle: f32::atan2(
|
||||||
|
point.y - transform.translation().y,
|
||||||
|
point.x - transform.translation().x,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug_mode.enabled {
|
||||||
|
for i in 0..polygon.len() {
|
||||||
|
let p1 = polygon[i];
|
||||||
|
let p2 = polygon[(i + 1) % polygon.len()];
|
||||||
|
let t1 = i as f32 / polygon.len() as f32;
|
||||||
|
let t2 = (i + 1) as f32 / polygon.len() as f32;
|
||||||
|
let color1 = Color::rgba(1.0 - t1, t1, 0.0, 1.0);
|
||||||
|
let color2 = Color::rgba(1.0 - t2, t2, 0.0, 1.0);
|
||||||
|
debug_draw.line_gradient(
|
||||||
|
p1.extend(0.0),
|
||||||
|
p2.extend(0.0),
|
||||||
|
0.0,
|
||||||
|
color1,
|
||||||
|
color2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
material.point_lights[i] = GpuPointLight2D {
|
material.point_lights[i] = GpuPointLight2D {
|
||||||
position: transform.translation().truncate(),
|
position: transform.translation().truncate(),
|
||||||
radius: light.radius,
|
radius: light.radius,
|
||||||
};
|
};
|
||||||
for x in 0..SHADOWMAP_RESOLUTION {
|
|
||||||
let offset = (i * SHADOWMAP_RESOLUTION + x) * 4;
|
|
||||||
let distance = x as f32 / SHADOWMAP_RESOLUTION as f32 * light.radius;
|
|
||||||
let distance_bytes = bytes_of(&distance);
|
|
||||||
shadowmap.data[offset + 0] = distance_bytes[0];
|
|
||||||
shadowmap.data[offset + 1] = distance_bytes[1];
|
|
||||||
shadowmap.data[offset + 2] = distance_bytes[2];
|
|
||||||
shadowmap.data[offset + 3] = distance_bytes[3];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
material.spot_light_count = spot_lights.len() as i32;
|
material.spot_light_count = spot_lights.len() as i32;
|
||||||
spot_lights
|
spot_lights
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(i, (transform, light))| {
|
.for_each(|(i, (transform, light, entity))| {
|
||||||
material.spot_lights[i] = GpuSpotLight2D {
|
material.spot_lights[i] = GpuSpotLight2D {
|
||||||
position: transform.translation().truncate(),
|
position: transform.translation().truncate(),
|
||||||
radius: light.radius,
|
radius: light.radius,
|
||||||
|
|
@ -227,6 +288,7 @@ fn prepare_lights(
|
||||||
padding2: 0,
|
padding2: 0,
|
||||||
padding3: 0,
|
padding3: 0,
|
||||||
};
|
};
|
||||||
|
// TODO: Remove when shadowmapping is done
|
||||||
for x in 0..SHADOWMAP_RESOLUTION {
|
for x in 0..SHADOWMAP_RESOLUTION {
|
||||||
let offset = ((i + MAX_POINT_LIGHTS) * SHADOWMAP_RESOLUTION + x) * 4;
|
let offset = ((i + MAX_POINT_LIGHTS) * SHADOWMAP_RESOLUTION + x) * 4;
|
||||||
let distance = x as f32 / SHADOWMAP_RESOLUTION as f32 * light.radius;
|
let distance = x as f32 / SHADOWMAP_RESOLUTION as f32 * light.radius;
|
||||||
|
|
@ -239,3 +301,172 @@ fn prepare_lights(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_shadows(
|
||||||
|
mut materials: ResMut<Assets<DarknessMaterial>>,
|
||||||
|
mut images: ResMut<Assets<Image>>,
|
||||||
|
shadow_mesh_query: Query<(&ShadowMesh, &GlobalTransform), Changed<ShadowMesh>>,
|
||||||
|
material_query: Query<&Handle<DarknessMaterial>>,
|
||||||
|
) {
|
||||||
|
// TODO: Check that the shadow mesh overlaps the darkness material
|
||||||
|
for handle in &material_query {
|
||||||
|
let material = match materials.get_mut(handle) {
|
||||||
|
Some(material) => material,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let shadowmap = match &material.shadowmap_texture {
|
||||||
|
Some(handle) => match images.get_mut(&handle) {
|
||||||
|
Some(image) => image,
|
||||||
|
None => continue,
|
||||||
|
},
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (shadow_mesh, global_transform) in &shadow_mesh_query {
|
||||||
|
let shadow_map = calculate_shadow_map(
|
||||||
|
global_transform.translation().truncate(),
|
||||||
|
&shadow_mesh.vertices,
|
||||||
|
);
|
||||||
|
for x in 0..SHADOWMAP_RESOLUTION {
|
||||||
|
let offset = (shadow_mesh.light_index * SHADOWMAP_RESOLUTION + x) * 4;
|
||||||
|
let distance_bytes = bytes_of(&shadow_map[x]);
|
||||||
|
shadowmap.data[offset + 0] = distance_bytes[0];
|
||||||
|
shadowmap.data[offset + 1] = distance_bytes[1];
|
||||||
|
shadowmap.data[offset + 2] = distance_bytes[2];
|
||||||
|
shadowmap.data[offset + 3] = distance_bytes[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_shadow_map(
|
||||||
|
global_position: Vec2,
|
||||||
|
vertices: &Vec<ShadowVertex>,
|
||||||
|
) -> [f32; SHADOWMAP_RESOLUTION] {
|
||||||
|
let mut shadow_map = [0.0; SHADOWMAP_RESOLUTION];
|
||||||
|
let mut next_index = 0;
|
||||||
|
for x in 0..SHADOWMAP_RESOLUTION {
|
||||||
|
let angle = (x as f32 / SHADOWMAP_RESOLUTION as f32) * 2.0 * std::f32::consts::PI
|
||||||
|
- std::f32::consts::PI;
|
||||||
|
while angle >= vertices[next_index % vertices.len()].angle && next_index < vertices.len() {
|
||||||
|
next_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev_vertex = if next_index == 0 {
|
||||||
|
vertices[vertices.len() - 1]
|
||||||
|
} else {
|
||||||
|
vertices[(next_index - 1) % vertices.len()]
|
||||||
|
};
|
||||||
|
let next_vertex = vertices[next_index % vertices.len()];
|
||||||
|
|
||||||
|
let distance = match vec2_intersection(
|
||||||
|
global_position,
|
||||||
|
global_position
|
||||||
|
+ Vec2 {
|
||||||
|
x: angle.cos(),
|
||||||
|
y: angle.sin(),
|
||||||
|
},
|
||||||
|
prev_vertex.point,
|
||||||
|
next_vertex.point,
|
||||||
|
) {
|
||||||
|
Some(point) => global_position.distance(point),
|
||||||
|
None => global_position.distance(prev_vertex.point),
|
||||||
|
};
|
||||||
|
shadow_map[x] = distance;
|
||||||
|
}
|
||||||
|
shadow_map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_light_geometry(
|
||||||
|
rapier_context: &Res<RapierContext>,
|
||||||
|
visibility_blocker_query: &Query<&VisibilityBlocker>,
|
||||||
|
transform_query: &Query<&GlobalTransform>,
|
||||||
|
collider_query: &Query<&Collider>,
|
||||||
|
aabb: Rect,
|
||||||
|
) -> Vec<Vec2> {
|
||||||
|
let mut points = vec![
|
||||||
|
Vec2::new(aabb.min.x, aabb.min.y),
|
||||||
|
Vec2::new(aabb.max.x, aabb.min.y),
|
||||||
|
Vec2::new(aabb.max.x, aabb.max.y),
|
||||||
|
Vec2::new(aabb.min.x, aabb.max.y),
|
||||||
|
];
|
||||||
|
|
||||||
|
let collider = Collider::cuboid(aabb.half_size().x, aabb.half_size().y);
|
||||||
|
let mut filter = QueryFilter::new().exclude_sensors();
|
||||||
|
let predicate = |coll_entity| visibility_blocker_query.get(coll_entity).is_ok();
|
||||||
|
filter.predicate = Some(&predicate);
|
||||||
|
|
||||||
|
rapier_context.intersections_with_shape(aabb.center(), 0.0, &collider, filter, |coll| {
|
||||||
|
let transform = transform_query
|
||||||
|
.get(coll)
|
||||||
|
.expect("Collider should have GlobalTransform");
|
||||||
|
if let Ok(collider) = collider_query.get(coll) {
|
||||||
|
if let Some(cuboid) = collider.as_cuboid() {
|
||||||
|
let rect = Rect::from_center_half_size(
|
||||||
|
transform.translation().truncate(),
|
||||||
|
cuboid.half_extents(),
|
||||||
|
);
|
||||||
|
points.push(rect.min);
|
||||||
|
points.push(rect.max);
|
||||||
|
points.push(Vec2::new(rect.min.x, rect.max.y));
|
||||||
|
points.push(Vec2::new(rect.max.x, rect.min.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: light source may not always be at bounding box center
|
||||||
|
// for example: optimal spot light bounding box
|
||||||
|
let center = aabb.center();
|
||||||
|
points.sort_unstable_by(|a, b| {
|
||||||
|
f32::atan2(a.y - center.y, a.x - center.x)
|
||||||
|
.partial_cmp(&f32::atan2(b.y - center.y, b.x - center.x))
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build visibility polygon
|
||||||
|
let mut polygon: Vec<_> = vec![];
|
||||||
|
for point in points.drain(..) {
|
||||||
|
/// We shoot 2 rays offset by this angle from the edge to catch what is beyond it.
|
||||||
|
const ANGLE_OFFSET: f32 = 0.0001;
|
||||||
|
/// Multiplier for hypotenuse when the other two sides are the same length. Or sqrt(2).
|
||||||
|
const HYPOTENUSE_MULT: f32 = 1.4142135623730951;
|
||||||
|
offset_cast(
|
||||||
|
aabb.center(),
|
||||||
|
(point - aabb.center()).normalize_or_zero(),
|
||||||
|
aabb.half_size().max_element() * HYPOTENUSE_MULT,
|
||||||
|
true,
|
||||||
|
filter,
|
||||||
|
ANGLE_OFFSET,
|
||||||
|
&rapier_context,
|
||||||
|
)
|
||||||
|
.iter()
|
||||||
|
.for_each(|ray| polygon.push(aabb.center() + *ray));
|
||||||
|
}
|
||||||
|
|
||||||
|
polygon
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_cast(
|
||||||
|
ray_origin: Vec2,
|
||||||
|
ray_dir: Vec2,
|
||||||
|
max_toi: f32,
|
||||||
|
solid: bool,
|
||||||
|
filter: QueryFilter,
|
||||||
|
angle_offset: f32,
|
||||||
|
rapier_context: &Res<RapierContext>,
|
||||||
|
) -> Vec<Vec2> {
|
||||||
|
let dir1 = Vec2::new((-angle_offset).cos(), (-angle_offset).sin()).rotate(ray_dir);
|
||||||
|
let dir2 = Vec2::new(angle_offset.cos(), angle_offset.sin()).rotate(ray_dir);
|
||||||
|
let ray1 = rapier_context.cast_ray(ray_origin, dir1, max_toi, solid, filter);
|
||||||
|
let ray2 = rapier_context.cast_ray(ray_origin, dir2, max_toi, solid, filter);
|
||||||
|
let toi1 = ray1.map_or(max_toi, |(_, toi)| toi);
|
||||||
|
let toi2 = ray2.map_or(max_toi, |(_, toi)| toi);
|
||||||
|
|
||||||
|
if (toi1 - toi2).abs() > toi1 * 0.1 {
|
||||||
|
vec![dir1 * toi1, dir2 * toi2]
|
||||||
|
} else {
|
||||||
|
vec![(dir1 * toi1 + dir2 * toi2) / 2.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
17
src/util.rs
17
src/util.rs
|
|
@ -124,6 +124,23 @@ pub fn move_towards_vec3(from: Vec3, to: Vec3, amount: f32) -> Vec3 {
|
||||||
from + diff.normalize() * length.min(amount)
|
from + diff.normalize() * length.min(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the intersection point (if any) of 2d lines a and b.
|
||||||
|
/// Lines are defined by 2 points on the line
|
||||||
|
pub fn vec2_intersection(a1: Vec2, a2: Vec2, b1: Vec2, b2: Vec2) -> Option<Vec2> {
|
||||||
|
let a_dir = a2 - a1;
|
||||||
|
let b_dir = b2 - b1;
|
||||||
|
let determinant = a_dir.perp_dot(b_dir);
|
||||||
|
if determinant.abs() <= f32::EPSILON {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(
|
||||||
|
Vec2 {
|
||||||
|
x: a_dir.x * (b1.x * b2.y - b1.y * b2.x) - (a1.x * a2.y - a1.y * a2.x) * b_dir.x,
|
||||||
|
y: (a1.x * a2.y - a1.y * a2.x) * -b_dir.y + a_dir.y * (b1.x * b2.y - b1.y * b2.x),
|
||||||
|
} / determinant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn loop_value(from: f32, to: f32, value: f32) -> f32 {
|
pub fn loop_value(from: f32, to: f32, value: f32) -> f32 {
|
||||||
let range = to - from;
|
let range = to - from;
|
||||||
if !range.is_normal() {
|
if !range.is_normal() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue