1use bevy::prelude::{Component, Quat, Transform, Vec2, Vec3};
3
4use crate::physics::Collider;
5
6#[derive(Clone, Component, Debug, PartialEq)]
9pub struct Sprite {
10 pub label: String,
12 pub filepath: PathBuf,
14 pub collider_filepath: PathBuf,
18 pub translation: Vec2,
21 pub layer: f32,
23 pub rotation: f32,
25 pub scale: f32,
27 pub collision: bool,
29 pub collider: Collider,
31 pub collider_dirty: bool,
36}
37
38fn read_collider_from_file(filepath: &Path) -> Collider {
40 match File::open(filepath) {
41 Ok(fh) => match ron::de::from_reader::<_, Collider>(fh) {
42 Ok(collider) => collider,
43 Err(e) => {
44 eprintln!("failed deserializing collider from file: {}", e);
45 Collider::NoCollider
46 }
47 },
48 Err(e) => {
49 eprintln!("failed to open collider file: {}", e);
50 Collider::NoCollider
51 }
52 }
53}
54
55impl Sprite {
56 pub fn new<S: Into<String>, P: Into<PathBuf>>(label: S, file_or_preset: P) -> Self {
65 let label = label.into();
66 let filepath = file_or_preset.into();
67 let mut collider_filepath = filepath.clone();
68 collider_filepath.set_extension("collider");
69 let actual_collider_filepath = PathBuf::from("assets").join(&collider_filepath);
70 let collider = if actual_collider_filepath.exists() {
71 read_collider_from_file(actual_collider_filepath.as_path())
72 } else {
73 eprintln!(
74 "warning: could not find collider file {} -- consider creating one with the `collider` example.",
75 actual_collider_filepath.to_string_lossy()
76 );
77 Collider::NoCollider
78 };
79 Self {
80 label,
81 filepath,
82 collider_filepath,
83 translation: Vec2::default(),
84 layer: f32::default(),
85 rotation: f32::default(),
86 scale: 1.0,
87 collision: false,
88 collider,
89 collider_dirty: true,
90 }
91 }
92
93 #[doc(hidden)]
95 pub fn bevy_transform(&self) -> Transform {
96 let mut transform = Transform::from_translation(self.translation.extend(self.layer));
97 transform.rotation = Quat::from_axis_angle(Vec3::Z, self.rotation);
98 transform.scale = Vec3::splat(self.scale);
99 transform
100 }
101
102 pub fn write_collider(&self) -> bool {
105 if self.collider == Collider::NoCollider {
106 return false;
107 }
108 let filepath = PathBuf::from("assets").join(self.collider_filepath.clone());
110 let mut fh = match File::create(filepath) {
111 Ok(fh) => fh,
112 Err(e) => {
113 eprintln!("failed creating collider file: {}", e);
114 return false;
115 }
116 };
117
118 let collider_ron = match ron::ser::to_string_pretty(&self.collider, Default::default()) {
119 Ok(r) => r,
120 Err(e) => {
121 eprintln!("failed converting collider to ron: {}", e);
122 return false;
123 }
124 };
125 match fh.write_all(collider_ron.as_bytes()) {
126 Ok(_) => true,
127 Err(e) => {
128 eprintln!("failed writing collider file: {}", e);
129 false
130 }
131 }
132 }
133 pub fn add_collider_point(&mut self, mut p: Vec2) {
136 self.collider_dirty = true;
137 if self.collider == Collider::NoCollider {
139 self.collider = Collider::Poly(Vec::new());
140 }
141 if let Collider::Poly(points) = &mut self.collider {
143 p -= self.translation;
145 p *= 1.0 / self.scale;
147 let mut p2 = Vec2::ZERO;
149 let sin = (-self.rotation).sin();
150 let cos = (-self.rotation).cos();
151 p2.x = p.x * cos - p.y * sin;
152 p2.y = p.x * sin + p.y * cos;
153 points.push(p2);
154 }
155 }
156 pub fn change_last_collider_point(&mut self, mut p: Vec2) {
159 self.collider_dirty = true;
160 if self.collider == Collider::NoCollider {
162 self.collider = Collider::Poly(vec![Vec2::ZERO]);
163 }
164 if let Collider::Poly(points) = &mut self.collider {
166 if points.is_empty() {
168 points.push(Vec2::ZERO);
169 }
170 p -= self.translation;
172 p *= 1.0 / self.scale;
174 let length = points.len();
176 let p2 = points.get_mut(length - 1).unwrap(); let sin = (-self.rotation).sin();
178 let cos = (-self.rotation).cos();
179 p2.x = p.x * cos - p.y * sin;
180 p2.y = p.x * sin + p.y * cos;
181 }
182 }
183}
184
185use std::{
186 array::IntoIter,
187 fs::File,
188 io::Write,
189 path::{Path, PathBuf},
190};
191
192#[derive(Copy, Clone, Debug, PartialEq, Eq)]
194pub enum SpritePreset {
195 RacingBarrelBlue,
196 RacingBarrelRed,
197 RacingBarrierRed,
198 RacingBarrierWhite,
199 RacingCarBlack,
200 RacingCarBlue,
201 RacingCarGreen,
202 RacingCarRed,
203 RacingCarYellow,
204 RacingConeStraight,
205 RollingBallBlue,
206 RollingBallBlueAlt,
207 RollingBallRed,
208 RollingBallRedAlt,
209 RollingBlockCorner,
210 RollingBlockNarrow,
211 RollingBlockSmall,
212 RollingBlockSquare,
213 RollingHoleEnd,
214 RollingHoleStart,
215}
216
217impl SpritePreset {
218 pub fn filepath(&self) -> PathBuf {
222 match self {
223 SpritePreset::RacingBarrelBlue => "sprite/racing/barrel_blue.png",
224 SpritePreset::RacingBarrelRed => "sprite/racing/barrel_red.png",
225 SpritePreset::RacingBarrierRed => "sprite/racing/barrier_red.png",
226 SpritePreset::RacingBarrierWhite => "sprite/racing/barrier_white.png",
227 SpritePreset::RacingCarBlack => "sprite/racing/car_black.png",
228 SpritePreset::RacingCarBlue => "sprite/racing/car_blue.png",
229 SpritePreset::RacingCarGreen => "sprite/racing/car_green.png",
230 SpritePreset::RacingCarRed => "sprite/racing/car_red.png",
231 SpritePreset::RacingCarYellow => "sprite/racing/car_yellow.png",
232 SpritePreset::RacingConeStraight => "sprite/racing/cone_straight.png",
233 SpritePreset::RollingBallBlue => "sprite/rolling/ball_blue.png",
234 SpritePreset::RollingBallBlueAlt => "sprite/rolling/ball_blue_alt.png",
235 SpritePreset::RollingBallRed => "sprite/rolling/ball_red.png",
236 SpritePreset::RollingBallRedAlt => "sprite/rolling/ball_red_alt.png",
237 SpritePreset::RollingBlockCorner => "sprite/rolling/block_corner.png",
238 SpritePreset::RollingBlockNarrow => "sprite/rolling/block_narrow.png",
239 SpritePreset::RollingBlockSmall => "sprite/rolling/block_small.png",
240 SpritePreset::RollingBlockSquare => "sprite/rolling/block_square.png",
241 SpritePreset::RollingHoleEnd => "sprite/rolling/hole_end.png",
242 SpritePreset::RollingHoleStart => "sprite/rolling/hole_start.png",
243 }
244 .into()
245 }
246
247 pub fn variant_iter() -> IntoIter<SpritePreset, 20> {
250 static SPRITE_PRESETS: [SpritePreset; 20] = [
251 SpritePreset::RacingBarrelBlue,
252 SpritePreset::RacingBarrelRed,
253 SpritePreset::RacingBarrierRed,
254 SpritePreset::RacingBarrierWhite,
255 SpritePreset::RacingCarBlack,
256 SpritePreset::RacingCarBlue,
257 SpritePreset::RacingCarGreen,
258 SpritePreset::RacingCarRed,
259 SpritePreset::RacingCarYellow,
260 SpritePreset::RacingConeStraight,
261 SpritePreset::RollingBallBlueAlt,
262 SpritePreset::RollingBallBlue,
263 SpritePreset::RollingBallRedAlt,
264 SpritePreset::RollingBallRed,
265 SpritePreset::RollingBlockCorner,
266 SpritePreset::RollingBlockNarrow,
267 SpritePreset::RollingBlockSmall,
268 SpritePreset::RollingBlockSquare,
269 SpritePreset::RollingHoleEnd,
270 SpritePreset::RollingHoleStart,
271 ];
272 SPRITE_PRESETS.into_iter()
273 }
274
275 fn shifted_by(&self, amount: isize) -> SpritePreset {
277 let len = SpritePreset::variant_iter().len();
278 let index = SpritePreset::variant_iter()
279 .enumerate()
280 .find(|(_, a)| *a == *self)
281 .unwrap()
282 .0;
283 let mut new_index_isize = index as isize + amount;
284 while new_index_isize < 0 {
285 new_index_isize += len as isize;
286 }
287 let new_index = (new_index_isize as usize) % len;
288 SpritePreset::variant_iter().nth(new_index).unwrap()
289 }
290
291 pub fn next(&self) -> SpritePreset {
293 self.shifted_by(-1)
294 }
295
296 pub fn prev(&self) -> SpritePreset {
298 self.shifted_by(1)
299 }
300}
301
302impl From<SpritePreset> for PathBuf {
303 fn from(sprite_preset: SpritePreset) -> Self {
304 sprite_preset.filepath()
305 }
306}