Compare commits
No commits in common. "feat/shadows" and "master" have entirely different histories.
feat/shado
...
master
13
src/game.rs
13
src/game.rs
|
|
@ -2,6 +2,8 @@ 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;
|
||||||
|
|
@ -24,4 +26,15 @@ 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, ShadowMesh, SpotLight2D};
|
use super::darkness::{PointLight2D, SpotLight2D};
|
||||||
|
|
||||||
pub struct GameCameraPlugin;
|
pub struct GameCameraPlugin;
|
||||||
|
|
||||||
|
|
@ -95,7 +95,6 @@ fn camera_setup(mut commands: Commands) {
|
||||||
ComputedVisibility::default(),
|
ComputedVisibility::default(),
|
||||||
CameraRoomRestraint,
|
CameraRoomRestraint,
|
||||||
PointLight2D { radius: 30.0 },
|
PointLight2D { radius: 30.0 },
|
||||||
ShadowMesh::default(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,32 +118,13 @@ 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 mut motion = Vec2::ZERO;
|
let mouse_motion = raw_mouse_motion * projection.scale * Vec2::new(-1.0, 1.0);
|
||||||
if mouse_input.pressed(MouseButton::Middle) {
|
if mouse_input.pressed(MouseButton::Middle) {
|
||||||
motion += raw_mouse_motion * projection.scale * Vec2::new(-1.0, 1.0);
|
input_movement(&mut transform, mouse_motion)
|
||||||
}
|
|
||||||
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,10 +8,6 @@ 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;
|
||||||
|
|
||||||
|
|
@ -20,7 +16,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 = 1024;
|
pub const SHADOWMAP_RESOLUTION: usize = 256;
|
||||||
|
|
||||||
pub struct DarknessPlugin;
|
pub struct DarknessPlugin;
|
||||||
|
|
||||||
|
|
@ -28,12 +24,11 @@ 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, prepare_shadows).chain());
|
.add_systems(Last, prepare_lights);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,19 +82,6 @@ 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>,
|
||||||
|
|
@ -164,9 +146,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 {
|
material: materials.add(DarknessMaterial::new(
|
||||||
color: Color::rgba(0.0, 0.0, 0.0, 0.75),
|
Color::rgba(0.0, 0.0, 0.0, 0.75),
|
||||||
shadowmap_texture: Some(images.add(Image::new(
|
Some(images.add(Image::new(
|
||||||
Extent3d {
|
Extent3d {
|
||||||
width: width as u32,
|
width: width as u32,
|
||||||
height: height as u32,
|
height: height as u32,
|
||||||
|
|
@ -176,8 +158,7 @@ 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()
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
@ -190,21 +171,14 @@ 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, Entity)>,
|
point_light_query: Query<(&GlobalTransform, &PointLight2D)>,
|
||||||
spot_light_query: Query<(&GlobalTransform, &SpotLight2D, Entity)>,
|
spot_light_query: Query<(&GlobalTransform, &SpotLight2D)>,
|
||||||
) {
|
) {
|
||||||
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,
|
||||||
|
|
@ -223,62 +197,27 @@ fn prepare_lights(
|
||||||
point_lights
|
point_lights
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(i, (transform, light, entity))| {
|
.for_each(|(i, (transform, light))| {
|
||||||
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, entity))| {
|
.for_each(|(i, (transform, light))| {
|
||||||
material.spot_lights[i] = GpuSpotLight2D {
|
material.spot_lights[i] = GpuSpotLight2D {
|
||||||
position: transform.translation().truncate(),
|
position: transform.translation().truncate(),
|
||||||
radius: light.radius,
|
radius: light.radius,
|
||||||
|
|
@ -288,7 +227,6 @@ 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;
|
||||||
|
|
@ -301,172 +239,3 @@ 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,23 +124,6 @@ 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