1use crate::{
2 context::DrawContext,
3 sprite::SpriteTexture,
4 utils::{Drawable, ShaderRef, Vertex, transform_to_matrix},
5};
6use smallvec::SmallVec;
7use spitfire_glow::{
8 graphics::{GraphicsBatch, GraphicsTarget},
9 renderer::{GlowBlending, GlowUniformValue},
10};
11use std::{
12 borrow::Cow,
13 cell::RefCell,
14 collections::{HashMap, HashSet},
15 ops::{Index, IndexMut},
16};
17use vek::{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 pub offset: Vec2<isize>,
176}
177
178impl TileInstance {
179 pub fn new(id: usize, location: Vec2<usize>) -> Self {
180 Self {
181 id,
182 location,
183 offset: Default::default(),
184 }
185 }
186
187 pub fn offset(mut self, value: Vec2<isize>) -> Self {
188 self.offset = value;
189 self
190 }
191}
192
193pub struct TilesDraw<'a, I: IntoIterator<Item = TileInstance>> {
194 emitter: &'a TilesEmitter,
195 tileset: &'a TileSet,
196 instances: RefCell<Option<I>>,
197}
198
199impl<I: IntoIterator<Item = TileInstance>> Drawable for TilesDraw<'_, I> {
200 fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
201 let batch = GraphicsBatch {
202 shader: context.shader(self.tileset.shader.as_ref()),
203 uniforms: self
204 .tileset
205 .uniforms
206 .iter()
207 .map(|(k, v)| (k.clone(), v.to_owned()))
208 .chain(std::iter::once((
209 "u_projection_view".into(),
210 GlowUniformValue::M4(
211 if self.emitter.screen_space {
212 graphics.state().main_camera.screen_matrix()
213 } else {
214 graphics.state().main_camera.world_matrix()
215 }
216 .into_col_array(),
217 ),
218 )))
219 .chain(
220 self.tileset
221 .textures
222 .iter()
223 .enumerate()
224 .map(|(index, texture)| {
225 (texture.sampler.clone(), GlowUniformValue::I1(index as _))
226 }),
227 )
228 .collect(),
229 textures: self
230 .tileset
231 .textures
232 .iter()
233 .filter_map(|texture| {
234 Some((context.texture(Some(&texture.texture))?, texture.filtering))
235 })
236 .collect(),
237 blending: self
238 .tileset
239 .blending
240 .unwrap_or_else(|| context.top_blending()),
241 scissor: None,
242 wireframe: context.wireframe,
243 };
244 graphics.state_mut().stream.batch_optimized(batch);
245 let transform = context.top_transform() * transform_to_matrix(self.emitter.transform);
246 graphics.state_mut().stream.transformed(
247 move |stream| {
248 let instances = match self.instances.borrow_mut().take() {
249 Some(instances) => instances,
250 None => return,
251 };
252 for instance in instances {
253 if let Some(tile) = self.tileset.mappings.get(&instance.id) {
254 let offset = Vec2 {
255 x: (instance.location.x as isize + instance.offset.x + tile.offset.x)
256 as f32,
257 y: (instance.location.y as isize + instance.offset.y + tile.offset.y)
258 as f32,
259 } * self.emitter.tile_size;
260 let size = Vec2 {
261 x: tile.size.x as f32,
262 y: tile.size.y as f32,
263 } * self.emitter.tile_size;
264 let color = tile.tint.into_array();
265 stream.quad([
266 Vertex {
267 position: [offset.x, offset.y],
268 uv: [tile.region.x, tile.region.y, tile.page],
269 color,
270 },
271 Vertex {
272 position: [offset.x + size.x, offset.y],
273 uv: [tile.region.x + tile.region.w, tile.region.y, tile.page],
274 color,
275 },
276 Vertex {
277 position: [offset.x + size.x, offset.y + size.y],
278 uv: [
279 tile.region.x + tile.region.w,
280 tile.region.y + tile.region.h,
281 tile.page,
282 ],
283 color,
284 },
285 Vertex {
286 position: [offset.x, offset.y + size.y],
287 uv: [tile.region.x, tile.region.y + tile.region.h, tile.page],
288 color,
289 },
290 ]);
291 }
292 }
293 },
294 |vertex| {
295 let point = transform.mul_point(Vec2::from(vertex.position));
296 vertex.position[0] = point.x;
297 vertex.position[1] = point.y;
298 },
299 );
300 }
301}
302
303#[derive(Debug, Clone)]
304pub struct TileMap {
305 pub include_ids: HashSet<usize>,
306 pub exclude_ids: HashSet<usize>,
307 size: Vec2<usize>,
308 buffer: Vec<usize>,
309}
310
311impl TileMap {
312 pub fn new(size: Vec2<usize>, fill_id: usize) -> Self {
313 Self {
314 include_ids: Default::default(),
315 exclude_ids: Default::default(),
316 size,
317 buffer: vec![fill_id; size.x * size.y],
318 }
319 }
320
321 pub fn with_buffer(size: Vec2<usize>, buffer: Vec<usize>) -> Option<Self> {
322 if buffer.len() == size.x * size.y {
323 Some(Self {
324 include_ids: Default::default(),
325 exclude_ids: Default::default(),
326 size,
327 buffer,
328 })
329 } else {
330 None
331 }
332 }
333
334 pub fn size(&self) -> Vec2<usize> {
335 self.size
336 }
337
338 pub fn buffer(&self) -> &[usize] {
339 &self.buffer
340 }
341
342 pub fn buffer_mut(&mut self) -> &mut [usize] {
343 &mut self.buffer
344 }
345
346 pub fn index(&self, location: impl Into<Vec2<usize>>) -> usize {
347 let location = location.into();
348 (location.y % self.size.y) * self.size.x + (location.x % self.size.x)
349 }
350
351 pub fn location(&self, index: usize) -> Vec2<usize> {
352 Vec2 {
353 x: index % self.size.x,
354 y: (index / self.size.y) % self.size.y,
355 }
356 }
357
358 pub fn get(&self, location: impl Into<Vec2<usize>>) -> Option<usize> {
359 let index = self.index(location);
360 self.buffer.get(index).copied()
361 }
362
363 pub fn set(&mut self, location: impl Into<Vec2<usize>>, id: usize) {
364 let index = self.index(location);
365 if let Some(item) = self.buffer.get_mut(index) {
366 *item = id;
367 }
368 }
369
370 pub fn fill(&mut self, from: impl Into<Vec2<usize>>, to: impl Into<Vec2<usize>>, id: usize) {
371 let from = from.into();
372 let to = to.into();
373 for y in from.y..to.y {
374 for x in from.x..to.x {
375 self.set(Vec2::new(x, y), id);
376 }
377 }
378 }
379
380 pub fn is_id_valid(&self, id: usize) -> bool {
381 (self.include_ids.is_empty() || self.include_ids.contains(&id))
382 && (self.exclude_ids.is_empty() || !self.exclude_ids.contains(&id))
383 }
384
385 pub fn emit(&self) -> impl Iterator<Item = TileInstance> + '_ {
386 self.buffer.iter().enumerate().filter_map(|(index, id)| {
387 if self.is_id_valid(*id) {
388 Some(TileInstance {
389 id: *id,
390 location: self.location(index),
391 offset: Default::default(),
392 })
393 } else {
394 None
395 }
396 })
397 }
398
399 pub fn emit_region(
400 &self,
401 region: impl Into<Rect<usize, usize>>,
402 repeating: bool,
403 ) -> impl Iterator<Item = TileInstance> + '_ {
404 let mut region = region.into();
405 if !repeating {
406 if region.x + region.w > self.size.x {
407 region.w = self.size.x.saturating_sub(region.x);
408 }
409 if region.y + region.h > self.size.y {
410 region.h = self.size.y.saturating_sub(region.y);
411 }
412 }
413 (region.y..(region.y + region.h)).flat_map(move |y| {
414 (region.x..(region.x + region.w)).filter_map(move |x| {
415 let location = Vec2 { x, y };
416 if let Some(id) = self.get(location)
417 && self.is_id_valid(id)
418 {
419 return Some(TileInstance {
420 id,
421 location,
422 offset: Default::default(),
423 });
424 }
425 None
426 })
427 })
428 }
429}
430
431impl<T: Into<Vec2<usize>>> Index<T> for TileMap {
432 type Output = usize;
433
434 fn index(&self, location: T) -> &Self::Output {
435 let location = location.into();
436 let index = self.index(location);
437 self.buffer
438 .get(index)
439 .unwrap_or_else(|| panic!("Invalid location: {location}"))
440 }
441}
442
443impl<T: Into<Vec2<usize>>> IndexMut<T> for TileMap {
444 fn index_mut(&mut self, location: T) -> &mut Self::Output {
445 let location = location.into();
446 let index = self.index(location);
447 self.buffer
448 .get_mut(index)
449 .unwrap_or_else(|| panic!("Invalid location: {location}"))
450 }
451}