1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// An example that demonstrates a basic 2D lighting effect
extern crate nalgebra;
extern crate ncollide2d;
extern crate quicksilver;
use nalgebra::{zero, Isometry2};
use ncollide2d::query::{Ray, RayCast};
use quicksilver::{
Result,
geom::{Rectangle, Vector},
graphics::{Color, GpuTriangle, Mesh, Vertex},
lifecycle::{Event, Settings, State, Window, run},
};
use std::cmp::Ordering;
struct Raycast {
// the rectangles to raycast against
regions: Vec<Rectangle>,
// the points to send rays to
targets: Vec<Vector>,
// the vertices to draw to the screen
mesh: Mesh
}
impl State for Raycast {
fn new() -> Result<Raycast> {
//The different squares that cast shadows
let regions = vec![
Rectangle::new_sized((800, 600)),
// Feel free to add or remove rectangles to this list
// to see the effect on the lighting
Rectangle::new((200, 200), (100, 100)),
Rectangle::new((400, 200), (100, 100)),
Rectangle::new((400, 400), (100, 100)),
Rectangle::new((200, 400), (100, 100)),
Rectangle::new((50, 50), (50, 50)),
Rectangle::new((550, 300), (64, 64))
];
// Build the list of targets to cast rays to
let targets = regions
.iter()
.flat_map(|region| {
vec![
region.top_left(),
region.top_left() + region.size().x_comp(),
region.top_left() + region.size().y_comp(),
region.top_left() + region.size(),
].into_iter()
})
.collect();
Ok(Raycast {
regions,
targets,
mesh: Mesh::new(),
})
}
fn event(&mut self, event: &Event, window: &mut Window) -> Result<()> {
if let &Event::MouseMoved(_) = event {
let mouse = window.mouse().pos();
self.mesh.clear();
let distance_to = |point: &Vector| (*point - mouse).len();
let angle_to = |point: &Vertex| (point.pos - mouse).angle();
// Raycast towards all targets and find the vertices
for i in 0..self.targets.len() {
let angle = (self.targets[i] - mouse).angle();
let mut cast_ray = |direction: f32| {
// Create a Ray from the mouse to the target
let start = mouse.into_point();
let direction = Vector::from_angle(direction).into_vector();
let ray = Ray::new(start, direction);
// Perform the actual raycast, returning the target and an iterator of collisions
let identity = Isometry2::new(zero(), zero());
let cast = self.regions
.iter()
.filter_map(|region| {
region.into_aabb().toi_with_ray(&identity, &ray, false)
})
.map(|toi: f32| (ray.origin + toi * ray.dir).into())
.min_by(|a: &Vector, b: &Vector| {
distance_to(a)
.partial_cmp(&distance_to(b))
.unwrap_or(Ordering::Equal)
});
if let Some(pos) = cast {
self.mesh.vertices.push(Vertex {
pos,
tex_pos: None,
col: Color::WHITE,
});
}
};
// Make sure to cast rays around corners to avoid jitteriness
cast_ray(angle - 0.001);
cast_ray(angle);
cast_ray(angle + 0.001);
}
// Sort the vertices to form a visibility polygon
self.mesh.vertices.sort_by(|a, b| {
angle_to(a)
.partial_cmp(&angle_to(b))
.unwrap_or(Ordering::Equal)
});
// Insert the mouse as a vertex for the center of the polygon
self.mesh.vertices.insert(
0,
Vertex {
pos: mouse,
tex_pos: None,
col: Color::WHITE,
},
);
// Calculate the number of triangles needed to draw the poly
let triangle_count = self.mesh.vertices.len() as u32 - 1;
for index in 0..triangle_count {
self.mesh.triangles.push(GpuTriangle {
z: 0.0,
indices: [
0,
index as u32 + 1,
(index as u32 + 1) % triangle_count + 1
],
image: None
});
}
}
Ok(())
}
fn draw(&mut self, window: &mut Window) -> Result<()> {
window.clear(Color::BLACK)?;
window.mesh().extend(&self.mesh);
Ok(())
}
}
fn main() {
run::<Raycast>("Raycast", Vector::new(800, 600), Settings::default());
}