1use crate::{
2 context::DrawContext,
3 sprite::SpriteTexture,
4 utils::{Drawable, ShaderRef, Vertex},
5};
6use smallvec::SmallVec;
7use spitfire_core::{Triangle, VertexStream};
8use spitfire_glow::{
9 graphics::{GraphicsBatch, GraphicsTarget},
10 renderer::{GlowBlending, GlowUniformValue},
11};
12use std::{
13 borrow::Cow,
14 cell::RefCell,
15 collections::HashMap,
16 f32::consts::{PI, TAU},
17};
18use vek::{Rect, Rgba, Vec2};
19
20#[derive(Debug, Default, Clone)]
21pub struct PrimitivesEmitter {
22 pub shader: Option<ShaderRef>,
23 pub textures: SmallVec<[SpriteTexture; 4]>,
24 pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
25 pub blending: Option<GlowBlending>,
26 pub screen_space: bool,
27}
28
29impl PrimitivesEmitter {
30 pub fn single(texture: SpriteTexture) -> Self {
31 Self {
32 textures: vec![texture].into(),
33 ..Default::default()
34 }
35 }
36
37 pub fn shader(mut self, value: ShaderRef) -> Self {
38 self.shader = Some(value);
39 self
40 }
41
42 pub fn texture(mut self, value: SpriteTexture) -> Self {
43 self.textures.push(value);
44 self
45 }
46
47 pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
48 self.uniforms.insert(key, value);
49 self
50 }
51
52 pub fn blending(mut self, value: GlowBlending) -> Self {
53 self.blending = Some(value);
54 self
55 }
56
57 pub fn screen_space(mut self, value: bool) -> Self {
58 self.screen_space = value;
59 self
60 }
61
62 pub fn emit_lines<I: IntoIterator<Item = Vec2<f32>>>(
63 &'_ self,
64 vertices: I,
65 ) -> LinesDraw<'_, I> {
66 LinesDraw {
67 emitter: self,
68 vertices: RefCell::new(Some(vertices)),
69 region: Rect {
70 x: 0.0,
71 y: 0.0,
72 w: 1.0,
73 h: 1.0,
74 },
75 page: 0.0,
76 tint: Rgba::white(),
77 thickness: 1.0,
78 looped: false,
79 }
80 }
81
82 pub fn emit_brush<I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>>(
83 &'_ self,
84 vertices: I,
85 ) -> BrushDraw<'_, I> {
86 BrushDraw {
87 emitter: self,
88 vertices: RefCell::new(Some(vertices)),
89 region: Rect {
90 x: 0.0,
91 y: 0.0,
92 w: 1.0,
93 h: 1.0,
94 },
95 page: 0.0,
96 }
97 }
98
99 pub fn emit_triangles<I: IntoIterator<Item = [Vertex; 3]>>(
100 &'_ self,
101 vertices: I,
102 ) -> TrianglesDraw<'_, I> {
103 TrianglesDraw {
104 emitter: self,
105 vertices: RefCell::new(Some(vertices)),
106 tint: Rgba::white(),
107 }
108 }
109
110 pub fn emit_triangle_fan<I: IntoIterator<Item = Vertex>>(
111 &'_ self,
112 vertices: I,
113 ) -> TriangleFanDraw<'_, I> {
114 TriangleFanDraw {
115 emitter: self,
116 vertices: RefCell::new(Some(vertices)),
117 }
118 }
119
120 pub fn emit_triangle_strip<I: IntoIterator<Item = Vertex>>(
121 &'_ self,
122 vertices: I,
123 ) -> TriangleStripDraw<'_, I> {
124 TriangleStripDraw {
125 emitter: self,
126 vertices: RefCell::new(Some(vertices)),
127 }
128 }
129
130 pub fn emit_regular_polygon(
131 &'_ self,
132 vertices: usize,
133 position: Vec2<f32>,
134 radius: f32,
135 ) -> RegularPolygonDraw<'_> {
136 RegularPolygonDraw {
137 emitter: self,
138 vertices,
139 position,
140 radius,
141 region: Rect {
142 x: 0.0,
143 y: 0.0,
144 w: 1.0,
145 h: 1.0,
146 },
147 page: 0.0,
148 tint: Rgba::white(),
149 }
150 }
151
152 pub fn emit_circle(
153 &'_ self,
154 position: Vec2<f32>,
155 radius: f32,
156 maximum_error: f32,
157 ) -> RegularPolygonDraw<'_> {
158 RegularPolygonDraw {
159 emitter: self,
160 vertices: (PI / (1.0 - maximum_error / radius).acos()).ceil() as _,
161 position,
162 radius,
163 region: Rect {
164 x: 0.0,
165 y: 0.0,
166 w: 1.0,
167 h: 1.0,
168 },
169 page: 0.0,
170 tint: Rgba::white(),
171 }
172 }
173
174 fn stream_transformed(
175 &self,
176 context: &mut DrawContext,
177 graphics: &mut dyn GraphicsTarget<Vertex>,
178 f: impl FnMut(&mut VertexStream<Vertex, GraphicsBatch>),
179 ) {
180 let batch = GraphicsBatch {
181 shader: context.shader(self.shader.as_ref()),
182 uniforms: self
183 .uniforms
184 .iter()
185 .map(|(k, v)| (k.clone(), v.to_owned()))
186 .chain(std::iter::once((
187 "u_projection_view".into(),
188 GlowUniformValue::M4(
189 if self.screen_space {
190 graphics.state().main_camera.screen_matrix()
191 } else {
192 graphics.state().main_camera.world_matrix()
193 }
194 .into_col_array(),
195 ),
196 )))
197 .chain(self.textures.iter().enumerate().map(|(index, texture)| {
198 (texture.sampler.clone(), GlowUniformValue::I1(index as _))
199 }))
200 .collect(),
201 textures: self
202 .textures
203 .iter()
204 .filter_map(|texture| {
205 Some((context.texture(Some(&texture.texture))?, texture.filtering))
206 })
207 .collect(),
208 blending: self.blending.unwrap_or_else(|| context.top_blending()),
209 scissor: None,
210 wireframe: context.wireframe,
211 };
212 graphics.state_mut().stream.batch_optimized(batch);
213 let transform = context.top_transform();
214 graphics.state_mut().stream.transformed(f, |vertex| {
215 let point = transform.mul_point(Vec2::from(vertex.position));
216 vertex.position[0] = point.x;
217 vertex.position[1] = point.y;
218 });
219 }
220}
221
222pub struct LinesDraw<'a, I: IntoIterator<Item = Vec2<f32>>> {
223 emitter: &'a PrimitivesEmitter,
224 vertices: RefCell<Option<I>>,
225 pub region: Rect<f32, f32>,
226 pub page: f32,
227 pub tint: Rgba<f32>,
228 pub thickness: f32,
229 pub looped: bool,
230}
231
232impl<I: IntoIterator<Item = Vec2<f32>>> LinesDraw<'_, I> {
233 pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
234 self.region = region;
235 self.page = page;
236 self
237 }
238
239 pub fn tint(mut self, value: Rgba<f32>) -> Self {
240 self.tint = value;
241 self
242 }
243
244 pub fn thickness(mut self, value: f32) -> Self {
245 self.thickness = value;
246 self
247 }
248
249 pub fn looped(mut self, value: bool) -> Self {
250 self.looped = value;
251 self
252 }
253}
254
255impl<I: IntoIterator<Item = Vec2<f32>>> Drawable for LinesDraw<'_, I> {
256 fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
257 fn push(
258 stream: &mut VertexStream<Vertex, GraphicsBatch>,
259 region: Rect<f32, f32>,
260 page: f32,
261 color: [f32; 4],
262 prev: Vec2<f32>,
263 next: Vec2<f32>,
264 normal: Vec2<f32>,
265 ) {
266 stream.extend(
267 [
268 Vertex {
269 position: (prev - normal).into_array(),
270 uv: [region.x, region.y, page],
271 color,
272 },
273 Vertex {
274 position: (prev + normal).into_array(),
275 uv: [region.x + region.w, region.y, page],
276 color,
277 },
278 Vertex {
279 position: (next + normal).into_array(),
280 uv: [region.x + region.w, region.y + region.h, page],
281 color,
282 },
283 Vertex {
284 position: (next - normal).into_array(),
285 uv: [region.x, region.y + region.h, page],
286 color,
287 },
288 ],
289 [Triangle { a: 0, b: 1, c: 2 }, Triangle { a: 2, b: 3, c: 0 }],
290 );
291 }
292
293 self.emitter
294 .stream_transformed(context, graphics, |stream| {
295 if let Some(vertices) = self.vertices.borrow_mut().take() {
296 let mut vertices = vertices.into_iter();
297 let Some(mut prev) = vertices.next() else {
298 return;
299 };
300 let start = prev;
301 let color = self.tint.into_array();
302 for next in vertices {
303 let tangent = next - prev;
304 let normal = Vec2 {
305 x: tangent.y,
306 y: -tangent.x,
307 }
308 .try_normalized()
309 .unwrap_or_default()
310 * self.thickness;
311 push(stream, self.region, self.page, color, prev, next, normal);
312 prev = next;
313 }
314 if self.looped {
315 let tangent = start - prev;
316 let normal = Vec2 {
317 x: tangent.y,
318 y: -tangent.x,
319 }
320 .try_normalized()
321 .unwrap_or_default()
322 * self.thickness;
323 push(stream, self.region, self.page, color, prev, start, normal);
324 }
325 }
326 });
327 }
328}
329
330pub struct BrushDraw<'a, I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>> {
331 emitter: &'a PrimitivesEmitter,
332 vertices: RefCell<Option<I>>,
333 pub region: Rect<f32, f32>,
334 pub page: f32,
335}
336
337impl<I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>> BrushDraw<'_, I> {
338 pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
339 self.region = region;
340 self.page = page;
341 self
342 }
343}
344
345impl<I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>> Drawable for BrushDraw<'_, I> {
346 fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
347 fn push(
348 stream: &mut VertexStream<Vertex, GraphicsBatch>,
349 region: Rect<f32, f32>,
350 page: f32,
351 prev: (Vec2<f32>, f32, Rgba<f32>),
352 next: (Vec2<f32>, f32, Rgba<f32>),
353 normal_prev: Vec2<f32>,
354 normal_next: Vec2<f32>,
355 ) {
356 stream.extend(
357 [
358 Vertex {
359 position: ((prev.0 + next.0) * 0.5).into_array(),
360 uv: [region.x, region.y, page],
361 color: ((prev.2 + next.2) * 0.5).into_array(),
362 },
363 Vertex {
364 position: (prev.0 - normal_prev * prev.1).into_array(),
365 uv: [region.x, region.y, page],
366 color: prev.2.into_array(),
367 },
368 Vertex {
369 position: (prev.0 + normal_prev * prev.1).into_array(),
370 uv: [region.x + region.w, region.y, page],
371 color: prev.2.into_array(),
372 },
373 Vertex {
374 position: (next.0 + normal_next * next.1).into_array(),
375 uv: [region.x + region.w, region.y + region.h, page],
376 color: next.2.into_array(),
377 },
378 Vertex {
379 position: (next.0 - normal_next * next.1).into_array(),
380 uv: [region.x, region.y + region.h, page],
381 color: next.2.into_array(),
382 },
383 ],
384 [
385 Triangle { a: 0, b: 1, c: 2 },
386 Triangle { a: 0, b: 2, c: 3 },
387 Triangle { a: 0, b: 3, c: 4 },
388 Triangle { a: 0, b: 4, c: 1 },
389 ],
390 );
391 }
392
393 self.emitter
394 .stream_transformed(context, graphics, |stream| {
395 if let Some(vertices) = self.vertices.borrow_mut().take() {
396 let mut vertices = vertices.into_iter().peekable();
397 let Some(mut prev) = vertices.next() else {
398 return;
399 };
400 let mut prev_tangent = Option::<Vec2<f32>>::None;
401 while let Some(curr) = vertices.next() {
402 let next = vertices.peek().copied();
403 let curr_tangent = (curr.0 - prev.0).try_normalized().unwrap_or_default();
404 let tangent = prev_tangent
405 .replace(curr_tangent)
406 .and_then(|tangent| (curr_tangent + tangent).try_normalized())
407 .unwrap_or(curr_tangent);
408 let next_tangent = next
409 .and_then(|next| (next.0 - curr.0).try_normalized())
410 .and_then(|tangent| (curr_tangent + tangent).try_normalized())
411 .unwrap_or(curr_tangent);
412 let normal_prev = Vec2 {
413 x: tangent.y,
414 y: -tangent.x,
415 }
416 .try_normalized()
417 .unwrap_or_default();
418 let normal_next = Vec2 {
419 x: next_tangent.y,
420 y: -next_tangent.x,
421 }
422 .try_normalized()
423 .unwrap_or_default();
424 push(
425 stream,
426 self.region,
427 self.page,
428 prev,
429 curr,
430 normal_prev,
431 normal_next,
432 );
433 prev = curr;
434 }
435 }
436 });
437 }
438}
439
440pub struct TrianglesDraw<'a, I: IntoIterator<Item = [Vertex; 3]>> {
441 emitter: &'a PrimitivesEmitter,
442 vertices: RefCell<Option<I>>,
443 pub tint: Rgba<f32>,
444}
445
446impl<I: IntoIterator<Item = [Vertex; 3]>> Drawable for TrianglesDraw<'_, I> {
447 fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
448 self.emitter
449 .stream_transformed(context, graphics, |stream| {
450 if let Some(vertices) = self.vertices.borrow_mut().take() {
451 unsafe {
452 let start = stream.vertices().len();
453 stream.extend_vertices(vertices.into_iter().flatten());
454 let end = stream.vertices().len();
455 stream.extend_triangles(
456 false,
457 (start..end).step_by(3).map(|index| Triangle {
458 a: index as u32,
459 b: index as u32 + 1,
460 c: index as u32 + 2,
461 }),
462 );
463 }
464 }
465 });
466 }
467}
468
469pub struct TriangleFanDraw<'a, I: IntoIterator<Item = Vertex>> {
470 emitter: &'a PrimitivesEmitter,
471 vertices: RefCell<Option<I>>,
472}
473
474impl<I: IntoIterator<Item = Vertex>> Drawable for TriangleFanDraw<'_, I> {
475 fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
476 self.emitter
477 .stream_transformed(context, graphics, |stream| {
478 if let Some(vertices) = self.vertices.borrow_mut().take() {
479 stream.triangle_fan(vertices);
480 }
481 });
482 }
483}
484
485pub struct TriangleStripDraw<'a, I: IntoIterator<Item = Vertex>> {
486 emitter: &'a PrimitivesEmitter,
487 vertices: RefCell<Option<I>>,
488}
489
490impl<I: IntoIterator<Item = Vertex>> Drawable for TriangleStripDraw<'_, I> {
491 fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
492 self.emitter
493 .stream_transformed(context, graphics, |stream| {
494 if let Some(vertices) = self.vertices.borrow_mut().take() {
495 stream.triangle_strip(vertices);
496 }
497 });
498 }
499}
500
501pub struct RegularPolygonDraw<'a> {
502 emitter: &'a PrimitivesEmitter,
503 vertices: usize,
504 position: Vec2<f32>,
505 radius: f32,
506 pub region: Rect<f32, f32>,
507 pub page: f32,
508 pub tint: Rgba<f32>,
509}
510
511impl RegularPolygonDraw<'_> {
512 pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
513 self.region = region;
514 self.page = page;
515 self
516 }
517
518 pub fn tint(mut self, value: Rgba<f32>) -> Self {
519 self.tint = value;
520 self
521 }
522}
523
524impl Drawable for RegularPolygonDraw<'_> {
525 fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
526 let color = self.tint.into_array();
527 self.emitter
528 .stream_transformed(context, graphics, |stream| {
529 stream.triangle_fan((0..=self.vertices).map(|index| {
530 let angle = TAU / self.vertices as f32 * index as f32;
531 let (y, x) = angle.sin_cos();
532 let u = (x + 1.0) * 0.5;
533 let v = (y + 1.0) * 0.5;
534 Vertex {
535 position: [
536 self.position.x + x * self.radius,
537 self.position.y + y * self.radius,
538 ],
539 uv: [
540 self.region.x + self.region.w * u,
541 self.region.y + self.region.h * v,
542 self.page,
543 ],
544 color,
545 }
546 }));
547 });
548 }
549}