diff --git a/src/game.rs b/src/game.rs index 4c5eaeb..0aec53c 100644 --- a/src/game.rs +++ b/src/game.rs @@ -2,8 +2,6 @@ use crate::{debug, game_setup}; use bevy::prelude::*; use bevy_ecs_ldtk::{LdtkWorldBundle, LevelSelection}; -use self::darkness::SpotLight2D; - pub mod camera; pub mod darkness; pub mod ldtk; @@ -26,15 +24,4 @@ fn setup(mut commands: Commands, assets: Res) { ldtk_handle: assets.load("levels/world.ldtk"), ..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, - }, - )); } diff --git a/src/game/darkness.rs b/src/game/darkness.rs index 77c0cec..d0ed28b 100644 --- a/src/game/darkness.rs +++ b/src/game/darkness.rs @@ -8,8 +8,11 @@ use bevy::{ sprite::{Material2dPlugin, Mesh2dHandle}, }; use bevy_ecs_ldtk::{LdtkLevel, LevelEvent}; +use bevy_prototype_debug_lines::DebugLines; use bevy_rapier2d::prelude::{Collider, QueryFilter, RapierContext}; +use crate::debug::DebugMode; + mod material; pub use material::*; @@ -174,6 +177,8 @@ fn add_to_level( fn prepare_lights( mut materials: ResMut>, mut images: ResMut>, + mut debug_draw: ResMut, + debug_mode: Res, rapier_context: Res, visibility_blocker_query: Query<&VisibilityBlocker>, transform_query: Query<&GlobalTransform>, @@ -204,7 +209,7 @@ fn prepare_lights( .enumerate() .for_each(|(i, (transform, light))| { let rect = light.aabb(); - let points = get_light_geometry( + let polygon = get_light_geometry( &rapier_context, &visibility_blocker_query, &transform_query, @@ -214,14 +219,32 @@ fn prepare_lights( rect.max + transform.translation().truncate(), ), ); - println!("{points:?}"); + + if debug_mode.enabled { + for (i, arr) in polygon.iter().as_slice().windows(2).enumerate() { + let p1 = arr[0]; + let p2 = arr[1]; + 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 { position: transform.translation().truncate(), 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 = light.radius; let distance_bytes = bytes_of(&distance); shadowmap.data[offset + 0] = distance_bytes[0]; shadowmap.data[offset + 1] = distance_bytes[1]; @@ -295,11 +318,54 @@ fn get_light_geometry( true }); + let center = aabb.center(); points.sort_unstable_by(|a, b| { - f32::atan2(a.y, a.x) - .partial_cmp(&f32::atan2(b.y, b.x)) + f32::atan2(a.y - center.y, a.x - center.x) + .partial_cmp(&f32::atan2(b.y - center.y, b.x - center.x)) .unwrap() }); - points + // Build visibility polygon + let mut polygon: Vec<_> = vec![]; + for point in points.drain(..) { + // We shoot 2 rays offset by this angle from the point. + const ANGLE_OFFSET: f32 = 0.0001; + offset_cast( + aabb.center(), + (point - aabb.center()).normalize_or_zero(), + aabb.size().max_element(), + true, + filter, + ANGLE_OFFSET, + &rapier_context, + ) + .iter() + .for_each(|ray| polygon.push(aabb.center() + *ray)); + } + + polygon.push(*polygon.first().unwrap()); + polygon +} + +fn offset_cast( + ray_origin: Vec2, + ray_dir: Vec2, + max_toi: f32, + solid: bool, + filter: QueryFilter, + angle_offset: f32, + rapier_context: &Res, +) -> Vec { + 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] + } }