1use crate::{
2 context::DrawContext,
3 sprite::SpriteTexture,
4 utils::{Drawable, ShaderRef, Vertex},
5};
6use smallvec::SmallVec;
7use spitfire_glow::{
8 graphics::{Graphics, GraphicsBatch},
9 renderer::{GlowBlending, GlowUniformValue},
10};
11use std::{
12 borrow::Cow,
13 cell::RefCell,
14 collections::{HashMap, HashSet},
15 ops::{Index, IndexMut},
16};
17use vek::{Mat4, Quaternion, Rect, Rgba, Transform, Vec2, Vec3};
18
19#[derive(Debug, Clone)]
20pub struct TileSetItem {
21 pub region: Rect<f32, f32>,
22 pub page: f32,
23 pub tint: Rgba<f32>,
24 pub size: Vec2<usize>,
25 pub offset: Vec2<isize>,
26}
27
28impl Default for TileSetItem {
29 fn default() -> Self {
30 Self {
31 region: Rect::new(0.0, 0.0, 1.0, 1.0),
32 page: 0.0,
33 tint: Rgba::white(),
34 size: Vec2::new(1, 1),
35 offset: Default::default(),
36 }
37 }
38}
39
40impl TileSetItem {
41 pub fn region(mut self, value: Rect<f32, f32>) -> Self {
42 self.region = value;
43 self
44 }
45
46 pub fn page(mut self, value: f32) -> Self {
47 self.page = value;
48 self
49 }
50
51 pub fn tint(mut self, value: Rgba<f32>) -> Self {
52 self.tint = value;
53 self
54 }
55
56 pub fn size(mut self, value: Vec2<usize>) -> Self {
57 self.size = value;
58 self
59 }
60
61 pub fn offset(mut self, value: Vec2<isize>) -> Self {
62 self.offset = value;
63 self
64 }
65}
66
67#[derive(Debug, Default, Clone)]
68pub struct TileSet {
69 pub shader: Option<ShaderRef>,
70 pub textures: SmallVec<[SpriteTexture; 4]>,
71 pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
72 pub blending: Option<GlowBlending>,
73 pub mappings: HashMap<usize, TileSetItem>,
74}
75
76impl TileSet {
77 pub fn single(texture: SpriteTexture) -> Self {
78 Self {
79 textures: vec![texture].into(),
80 ..Default::default()
81 }
82 }
83
84 pub fn shader(mut self, value: ShaderRef) -> Self {
85 self.shader = Some(value);
86 self
87 }
88
89 pub fn texture(mut self, value: SpriteTexture) -> Self {
90 self.textures.push(value);
91 self
92 }
93
94 pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
95 self.uniforms.insert(key, value);
96 self
97 }
98
99 pub fn blending(mut self, value: GlowBlending) -> Self {
100 self.blending = Some(value);
101 self
102 }
103
104 pub fn mapping(mut self, id: usize, item: TileSetItem) -> Self {
105 self.mappings.insert(id, item);
106 self
107 }
108
109 pub fn mappings(mut self, iter: impl IntoIterator<Item = (usize, TileSetItem)>) -> Self {
110 self.mappings.extend(iter);
111 self
112 }
113}
114
115#[derive(Debug, Default, Clone)]
116pub struct TilesEmitter {
117 pub transform: Transform<f32, f32, f32>,
118 pub tile_size: Vec2<f32>,
119 pub screen_space: bool,
120}
121
122impl TilesEmitter {
123 pub fn transform(mut self, value: Transform<f32, f32, f32>) -> Self {
124 self.transform = value;
125 self
126 }
127
128 pub fn position(mut self, value: Vec2<f32>) -> Self {
129 self.transform.position = value.into();
130 self
131 }
132
133 pub fn orientation(mut self, value: Quaternion<f32>) -> Self {
134 self.transform.orientation = value;
135 self
136 }
137
138 pub fn rotation(mut self, angle_radians: f32) -> Self {
139 self.transform.orientation = Quaternion::rotation_z(angle_radians);
140 self
141 }
142
143 pub fn scale(mut self, value: Vec2<f32>) -> Self {
144 self.transform.scale = Vec3::new(value.x, value.y, 1.0);
145 self
146 }
147
148 pub fn tile_size(mut self, value: Vec2<f32>) -> Self {
149 self.tile_size = value;
150 self
151 }
152
153 pub fn screen_space(mut self, value: bool) -> Self {
154 self.screen_space = value;
155 self
156 }
157
158 pub fn emit<'a, I: IntoIterator<Item = TileInstance>>(
159 &'a self,
160 set: &'a TileSet,
161 instances: I,
162 ) -> TilesDraw<'a, I> {
163 TilesDraw {
164 emitter: self,
165 tileset: set,
166 instances: RefCell::new(Some(instances)),
167 }
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct TileInstance {
173 pub id: usize,
174 pub location: Vec2<usize>,
175}
176
177impl TileInstance {
178 pub fn new(id: usize, location: Vec2<usize>) -> Self {
179 Self { id, location }
180 }
181}
182
183pub struct TilesDraw<'a, I: IntoIterator<Item = TileInstance>> {
184 emitter: &'a TilesEmitter,
185 tileset: &'a TileSet,
186 instances: RefCell<Option<I>>,
187}
188
189impl<I: IntoIterator<Item = TileInstance>> Drawable for TilesDraw<'_, I> {
190 fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
191 let batch = GraphicsBatch {
192 shader: context.shader(self.tileset.shader.as_ref()),
193 uniforms: self
194 .tileset
195 .uniforms
196 .iter()
197 .map(|(k, v)| (k.clone(), v.to_owned()))
198 .chain(std::iter::once((
199 "u_projection_view".into(),
200 GlowUniformValue::M4(
201 if self.emitter.screen_space {
202 graphics.main_camera.screen_matrix()
203 } else {
204 graphics.main_camera.world_matrix()
205 }
206 .into_col_array(),
207 ),
208 )))
209 .chain(
210 self.tileset
211 .textures
212 .iter()
213 .enumerate()
214 .map(|(index, texture)| {
215 (texture.sampler.clone(), GlowUniformValue::I1(index as _))
216 }),
217 )
218 .collect(),
219 textures: self
220 .tileset
221 .textures
222 .iter()
223 .filter_map(|texture| {
224 Some((context.texture(Some(&texture.texture))?, texture.filtering))
225 })
226 .collect(),
227 blending: self
228 .tileset
229 .blending
230 .unwrap_or_else(|| context.top_blending()),
231 scissor: None,
232 };
233 graphics.stream.batch_optimized(batch);
234 let transform = Mat4::from(context.top_transform()) * Mat4::from(self.emitter.transform);
235 graphics.stream.transformed(
236 move |stream| {
237 let instances = match self.instances.borrow_mut().take() {
238 Some(instances) => instances,
239 None => return,
240 };
241 for instance in instances {
242 if let Some(tile) = self.tileset.mappings.get(&instance.id) {
243 let offset = Vec2 {
244 x: (instance.location.x as isize + tile.offset.x) as f32,
245 y: (instance.location.y as isize + tile.offset.y) as f32,
246 } * self.emitter.tile_size;
247 let size = Vec2 {
248 x: tile.size.x as f32,
249 y: tile.size.y as f32,
250 } * self.emitter.tile_size;
251 let color = tile.tint.into_array();
252 stream.quad([
253 Vertex {
254 position: [offset.x, offset.y],
255 uv: [tile.region.x, tile.region.y, tile.page],
256 color,
257 },
258 Vertex {
259 position: [offset.x + size.x, offset.y],
260 uv: [tile.region.x + tile.region.w, tile.region.y, tile.page],
261 color,
262 },
263 Vertex {
264 position: [offset.x + size.x, offset.y + size.y],
265 uv: [
266 tile.region.x + tile.region.w,
267 tile.region.y + tile.region.h,
268 tile.page,
269 ],
270 color,
271 },
272 Vertex {
273 position: [offset.x, offset.y + size.y],
274 uv: [tile.region.x, tile.region.y + tile.region.h, tile.page],
275 color,
276 },
277 ]);
278 }
279 }
280 },
281 |vertex| {
282 let point = transform.mul_point(Vec2::from(vertex.position));
283 vertex.position[0] = point.x;
284 vertex.position[1] = point.y;
285 },
286 );
287 }
288}
289
290#[derive(Debug, Clone)]
291pub struct TileMap {
292 pub include_ids: HashSet<usize>,
293 pub exclude_ids: HashSet<usize>,
294 size: Vec2<usize>,
295 buffer: Vec<usize>,
296}
297
298impl TileMap {
299 pub fn new(size: Vec2<usize>, fill_id: usize) -> Self {
300 Self {
301 include_ids: Default::default(),
302 exclude_ids: Default::default(),
303 size,
304 buffer: vec![fill_id; size.x * size.y],
305 }
306 }
307
308 pub fn with_buffer(size: Vec2<usize>, buffer: Vec<usize>) -> Option<Self> {
309 if buffer.len() == size.x * size.y {
310 Some(Self {
311 include_ids: Default::default(),
312 exclude_ids: Default::default(),
313 size,
314 buffer,
315 })
316 } else {
317 None
318 }
319 }
320
321 pub fn size(&self) -> Vec2<usize> {
322 self.size
323 }
324
325 pub fn buffer(&self) -> &[usize] {
326 &self.buffer
327 }
328
329 pub fn buffer_mut(&mut self) -> &mut [usize] {
330 &mut self.buffer
331 }
332
333 pub fn index(&self, location: impl Into<Vec2<usize>>) -> usize {
334 let location = location.into();
335 (location.y % self.size.y) * self.size.x + (location.x % self.size.x)
336 }
337
338 pub fn location(&self, index: usize) -> Vec2<usize> {
339 Vec2 {
340 x: index % self.size.x,
341 y: (index / self.size.y) % self.size.y,
342 }
343 }
344
345 pub fn get(&self, location: impl Into<Vec2<usize>>) -> Option<usize> {
346 let index = self.index(location);
347 self.buffer.get(index).copied()
348 }
349
350 pub fn set(&mut self, location: impl Into<Vec2<usize>>, id: usize) {
351 let index = self.index(location);
352 if let Some(item) = self.buffer.get_mut(index) {
353 *item = id;
354 }
355 }
356
357 pub fn fill(&mut self, from: impl Into<Vec2<usize>>, to: impl Into<Vec2<usize>>, id: usize) {
358 let from = from.into();
359 let to = to.into();
360 for y in from.y..to.y {
361 for x in from.x..to.x {
362 self.set(Vec2::new(x, y), id);
363 }
364 }
365 }
366
367 pub fn is_id_valid(&self, id: usize) -> bool {
368 (self.include_ids.is_empty() || self.include_ids.contains(&id))
369 && (self.exclude_ids.is_empty() || !self.exclude_ids.contains(&id))
370 }
371
372 pub fn emit(&self) -> impl Iterator<Item = TileInstance> + '_ {
373 self.buffer.iter().enumerate().filter_map(|(index, id)| {
374 if self.is_id_valid(*id) {
375 Some(TileInstance {
376 id: *id,
377 location: self.location(index),
378 })
379 } else {
380 None
381 }
382 })
383 }
384
385 pub fn emit_region(
386 &self,
387 region: impl Into<Rect<usize, usize>>,
388 repeating: bool,
389 ) -> impl Iterator<Item = TileInstance> + '_ {
390 let mut region = region.into();
391 if !repeating {
392 if region.x + region.w > self.size.x {
393 region.w = self.size.x.saturating_sub(region.x);
394 }
395 if region.y + region.h > self.size.y {
396 region.h = self.size.y.saturating_sub(region.y);
397 }
398 }
399 (region.y..(region.y + region.h)).flat_map(move |y| {
400 (region.x..(region.x + region.w)).filter_map(move |x| {
401 let location = Vec2 { x, y };
402 if let Some(id) = self.get(location) {
403 if self.is_id_valid(id) {
404 return Some(TileInstance { id, location });
405 }
406 }
407 None
408 })
409 })
410 }
411}
412
413impl<T: Into<Vec2<usize>>> Index<T> for TileMap {
414 type Output = usize;
415
416 fn index(&self, location: T) -> &Self::Output {
417 let location = location.into();
418 let index = self.index(location);
419 self.buffer
420 .get(index)
421 .unwrap_or_else(|| panic!("Invalid location: {}", location))
422 }
423}
424
425impl<T: Into<Vec2<usize>>> IndexMut<T> for TileMap {
426 fn index_mut(&mut self, location: T) -> &mut Self::Output {
427 let location = location.into();
428 let index = self.index(location);
429 self.buffer
430 .get_mut(index)
431 .unwrap_or_else(|| panic!("Invalid location: {}", location))
432 }
433}