Skip to content

Commit 0edb0cf

Browse files
authored
Split (#130)
* Tons of stuff in this commit. Initial asteroid splitting implementation. * Stopped sub-splits. Recalculated radius per-split. * Got rid of "Gaps" in split asteroids * Spin changes; shape refinements * Removed some unused variables * Removed unused function * Clippy stuff... * More clippy * More clippy!!! * Update asteroid.rs * Update asteroid.rs * Refactored indexing so it works for arbitrary numbers of chunks * Avoided using a "for" index to access an array * Rustfmt * Removed extra Positioned features and fixed PI * Removed comment
1 parent c68e2c3 commit 0edb0cf

File tree

3 files changed

+157
-1
lines changed

3 files changed

+157
-1
lines changed

src/game/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ impl Updateable for Game {
154154
if let Some(index) = asteroids
155155
.iter()
156156
.position(|asteroid| asteroid.collides_with(bullet)) {
157+
if asteroids[index].can_split() {
158+
let new_asteroids = asteroids[index].split(bullet);
159+
asteroids.extend(new_asteroids);
160+
}
157161
asteroids.remove(index);
158162
*score += 10;
159163
music::play_sound(&Sound::AsteroidExplosion, music::Repeat::Times(0));

src/game/models/asteroid.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! This module defines the asteroid component.
22
use std::{cmp, f64};
3+
use std::f64::consts::PI;
34

45
use opengl_graphics::GlGraphics;
56
use piston_window::{Context, polygon, Size, Transformed, UpdateArgs};
@@ -97,6 +98,32 @@ fn generate_jagged_shape(radius: f64, num_segments: usize) -> Vec<[f64; 2]> {
9798
randomize_shape(new_shape, max_mut)
9899
}
99100

101+
fn center_mass(mut shape: &mut Vec<[f64; 2]>) -> Vector {
102+
let mut average = Vector::default();
103+
for vertex in &mut shape.iter() {
104+
// Here, we are adding the new vertex location into what will be our average location.
105+
average += (*vertex).into();
106+
}
107+
// Now we divide the 'average' by the number of segments to convert it from a sum of coordinates
108+
// into an average of each coordinate. This isn't a real center-of-mass calculation,
109+
// but it's good enough for this purpose (because we aren't mutating *that* far from a circle)
110+
average /= shape.len() as f64;
111+
for mut vertex in &mut shape.iter_mut() {
112+
vertex[0] -= average.x;
113+
vertex[1] -= average.y;
114+
}
115+
average
116+
}
117+
118+
fn calculate_radius(shape: &[[f64; 2]]) -> f64 {
119+
let mut avg_magnitude: f64 = 0.0;
120+
for vertex in &mut shape.iter() {
121+
let vert_as_vect: Vector = (*vertex).into();
122+
avg_magnitude += vert_as_vect.magnitude()
123+
}
124+
avg_magnitude / shape.len() as f64
125+
}
126+
100127
impl Asteroid {
101128
pub fn new(window_size: Size) -> Self {
102129

@@ -144,6 +171,80 @@ impl Asteroid {
144171
on_screen: false,
145172
}
146173
}
174+
175+
pub fn can_split(&self) -> bool {
176+
self.shape.len() > 10
177+
}
178+
179+
pub fn split<P: Positioned>(&mut self, other: &P) -> Vec<Asteroid> {
180+
self.normalize_rotation();
181+
let index_nearest = self.index_nearest_point(other);
182+
let num_pieces = 3;
183+
let mut chunks: Vec<Asteroid> = Vec::new();
184+
let chunk_size = self.shape.len() / num_pieces;
185+
let mut transformed_shape = self.shape.split_off(index_nearest);
186+
transformed_shape.extend(self.shape.iter().cloned());
187+
let last_element = transformed_shape[transformed_shape.len() - 1];
188+
let first_element = transformed_shape[0];
189+
transformed_shape.push(first_element);
190+
transformed_shape.insert(0, last_element);
191+
let mut first_indices: Vec<usize> = (0..num_pieces - 1)
192+
.map(|idx| idx + 1)
193+
.map(|idx| idx * chunk_size)
194+
.map(|idx| idx as usize)
195+
.collect();
196+
let mut last_indices = first_indices.clone();
197+
first_indices.insert(0, 0);
198+
last_indices.push(transformed_shape.len() - 2);
199+
let zipped_indices: Vec<(&usize, &usize)> =
200+
first_indices.iter().zip(last_indices.iter()).collect();
201+
for pair in zipped_indices {
202+
let mut new_shape = transformed_shape[*pair.0..*pair.1 + 1].to_vec();
203+
new_shape.push([0.0, 0.0]);
204+
let average_pos = center_mass(&mut new_shape);
205+
let new_radius = calculate_radius(&new_shape);
206+
chunks.push(Asteroid {
207+
pos: self.pos + average_pos,
208+
vel: self.vel + average_pos.rotate(PI / 2.0) * self.spin +
209+
average_pos * 0.005,
210+
rot: 0.0,
211+
spin: self.spin * 0.5,
212+
radius: new_radius,
213+
shape: new_shape,
214+
window_size: self.window_size,
215+
on_screen: true,
216+
})
217+
}
218+
chunks
219+
}
220+
221+
fn normalize_rotation(&mut self) {
222+
let mut norm_shape = self.shape.clone();
223+
for vert in &mut norm_shape {
224+
let v: Vector = (*vert).into();
225+
let rotated = v.rotate(self.rot);
226+
vert[0] = rotated.x;
227+
vert[1] = rotated.y;
228+
}
229+
self.shape = norm_shape.clone();
230+
self.rot = 0.0;
231+
}
232+
233+
fn index_nearest_point<P: Positioned>(&mut self, other: &P) -> usize {
234+
let other_pos = other.pos();
235+
let nearest_point = self.shape
236+
.iter()
237+
.map(|vert| {
238+
Vector {
239+
x: vert[0],
240+
y: vert[1],
241+
}
242+
})
243+
.map(|vert| other_pos.distance(vert.rotate(self.rot) + self.pos))
244+
.enumerate()
245+
.min_by_key(|&(_, b)| b as i64);
246+
nearest_point.unwrap().0
247+
}
147248
}
148249

149250
impl Updateable for Asteroid {

src/game/models/vector.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::f64::consts::PI;
2-
use std::ops::{Add, AddAssign, Rem, RemAssign, Sub, SubAssign, Div, DivAssign};
2+
use std::ops::{Add, AddAssign, Rem, RemAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign};
33

44
use piston_window::Size;
55
use rand;
@@ -31,6 +31,24 @@ impl Vector {
3131
}
3232
angle_to_point
3333
}
34+
35+
pub fn magnitude(self) -> f64 {
36+
(self.x.powi(2) + self.y.powi(2)).sqrt()
37+
}
38+
39+
pub fn distance(self, other: Vector) -> f64 {
40+
(self - other).magnitude()
41+
}
42+
43+
pub fn rotate(self, angle: f64) -> Vector {
44+
let old_angle = (self.angle_to_vector(Vector { x: 0.0, y: 0.0 }) + PI) % (PI * 2.0);
45+
let magnitude = self.magnitude();
46+
let new_angle = (angle + old_angle) % (PI * 2.0);
47+
Vector {
48+
x: magnitude * new_angle.cos(),
49+
y: magnitude * new_angle.sin(),
50+
}
51+
}
3452
}
3553

3654
impl Add for Vector {
@@ -107,6 +125,39 @@ impl Div<f64> for Vector {
107125
}
108126
}
109127

128+
impl Mul<Vector> for Vector {
129+
type Output = Self;
130+
131+
fn mul(self, other: Self) -> Self {
132+
Vector {
133+
x: self.x * other.x,
134+
y: self.y * other.y,
135+
}
136+
}
137+
}
138+
139+
impl Mul<f64> for Vector {
140+
type Output = Self;
141+
142+
fn mul(self, other: f64) -> Self {
143+
Vector {
144+
x: self.x * other,
145+
y: self.y * other,
146+
}
147+
}
148+
}
149+
150+
impl MulAssign<Vector> for Vector {
151+
fn mul_assign(&mut self, other: Self) {
152+
*self = *self * other;
153+
}
154+
}
155+
156+
impl MulAssign<f64> for Vector {
157+
fn mul_assign(&mut self, other: f64) {
158+
*self = *self * other;
159+
}
160+
}
110161
impl DivAssign<Vector> for Vector {
111162
fn div_assign(&mut self, other: Self) {
112163
*self = *self / other;

0 commit comments

Comments
 (0)