this repo has no description
1// Copyright (c) 2021 ezequias2d <ezequiasmoises@gmail.com> and the Peridot contributors
2// This code is licensed under MIT license (see LICENSE for details)
3
4using System.Drawing;
5using System.Numerics;
6
7namespace Peridot;
8
9/// <summary>
10/// A class for drawing sprites in one or more optimized batches.
11/// </summary>
12/// <typeparam name="TImage">The image type to renderer.</typeparam>
13public abstract class SpriteBatch<TImage> : ISpriteBatch<TImage> where TImage : notnull
14{
15 /// <summary>
16 /// The batcher with all entities to renderer.
17 /// </summary>
18 protected readonly Batcher<TImage> m_batcher;
19
20 protected readonly Image m_whiteImage;
21
22 private bool m_beginCalled;
23
24 /// <summary>
25 /// Creates a new <see cref="SpriteBatch{TImage}"/>.
26 /// </summary>
27 public SpriteBatch(Image whiteImage)
28 {
29 m_batcher = new();
30 m_whiteImage = whiteImage;
31 m_beginCalled = false;
32 IsDisposed = false;
33 ResetScissor();
34 }
35
36 /// <summary>
37 /// Deconstructor of <see cref="SpriteBatch{TImage}"/>.
38 /// </summary>
39 ~SpriteBatch()
40 {
41 CoreDispose(false);
42 }
43
44 /// <summary>
45 /// The view matrix to use to renderer.
46 /// </summary>
47 public Matrix4x4 ViewMatrix { get; set; }
48
49 /// <inheritdoc/>
50 public bool IsDisposed { get; protected set; }
51
52 /// <inheritdoc/>
53 public RectangleF Scissor { get; set; }
54
55 /// <summary>
56 /// Begins the sprite branch.
57 /// </summary>
58 /// <exception cref="InvalidOperationException">Thrown if <see cref="Begin"/> is called next time without previous <see cref="End"/>.</exception>
59 public void Begin()
60 {
61 if (m_beginCalled)
62 throw new InvalidOperationException("Begin cannot be called again until End has been successfully called.");
63
64 ViewMatrix = Matrix4x4.Identity;
65 m_beginCalled = true;
66 m_batcher.Clear();
67 }
68
69 /// <summary>
70 /// Flushes all batched text and sprites to the screen.
71 /// </summary>
72 /// <exception cref="InvalidOperationException">This command should be called after <see cref="Begin"/> and drawing commands.</exception>
73 public void End()
74 {
75 if (!m_beginCalled)
76 throw new InvalidOperationException("Begin must be called before calling End.");
77
78 m_beginCalled = false;
79 }
80
81 /// <inheritdoc/>
82 public void Draw(Image image, RectangleF destinationRectangle, RectangleF sourceRectangle, Color color, float rotation, Vector2 origin, SpriteOptions options, float layerDepth) =>
83 Draw(GetHandle(image), destinationRectangle, sourceRectangle, color, rotation, origin, options, layerDepth);
84
85 /// <inheritdoc/>
86 public void Draw(Image image, Vector2 position, RectangleF sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteOptions options, float layerDepth) =>
87 Draw(GetHandle(image), position, sourceRectangle, color, rotation, origin, scale, options, layerDepth);
88
89 /// <inheritdoc/>
90 public void Draw(TImage image, RectangleF destinationRectangle, RectangleF sourceRectangle, Color color, float rotation, Vector2 origin, SpriteOptions options, float layerDepth)
91 {
92 CheckValid(image);
93 ref var item = ref m_batcher.Add(image);
94 var size = GetSize(image);
95 var vsize = new Vector2(size.Width, size.Height);
96
97 item = new(vsize, destinationRectangle, sourceRectangle, color, rotation, origin, layerDepth, Transform(Scissor, ViewMatrix), options);
98 }
99
100 /// <inheritdoc/>
101 public void Draw(TImage image, Vector2 position, RectangleF sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteOptions options, float layerDepth)
102 {
103 CheckValid(image);
104 ref var item = ref m_batcher.Add(image);
105 var size = GetSize(image);
106 var vsize = new Vector2(size.Width, size.Height);
107
108 item = new(vsize, position, sourceRectangle, color, rotation, origin, scale, layerDepth, Transform(Scissor, ViewMatrix), options);
109 }
110
111 /// <inheritdoc/>
112 public void Dispose()
113 {
114 CoreDispose(true);
115 GC.SuppressFinalize(this);
116 }
117
118 private void CoreDispose(bool disposing)
119 {
120 if (IsDisposed)
121 return;
122 IsDisposed = true;
123
124 Dispose(disposing);
125 }
126
127 /// <summary>
128 /// Disposes resources.
129 /// </summary>
130 /// <param name="disposing">If called by <see cref="Dispose()"/></param>
131 protected abstract void Dispose(bool disposing);
132
133 private void CheckValid(TImage image)
134 {
135 if (image == null)
136 throw new ArgumentNullException(nameof(image));
137 if (!m_beginCalled)
138 throw new InvalidOperationException("Draw was called, but Begin has not yet been called. Begin must be called successfully before you can call Draw.");
139 }
140
141 /// <inheritdoc/>
142 public void ResetScissor()
143 {
144 const float v = 1 << 23;
145 const float s = -(1 << 22);
146 Scissor = new RectangleF(s, s, v, v);
147 }
148
149 /// <inheritdoc/>
150 public void IntersectScissor(RectangleF clip)
151 {
152 var scissor = Scissor;
153 scissor.Intersect(clip);
154 Scissor = scissor;
155 }
156
157 private static RectangleF Transform(RectangleF rect, Matrix4x4 matrix)
158 {
159 var pos = Vector4.Transform(new Vector4(rect.X, rect.Y, 0, 1), matrix);
160 var size = Vector4.Transform(new Vector4(rect.X + rect.Width, rect.Y + rect.Height, 0, 1), matrix);
161 return new(pos.X, pos.Y, size.X - pos.X, size.Y - pos.Y);
162 }
163
164 #region ISpriteBatch
165 /// <inheritdoc/>
166 public void Draw(Image image,
167 RectangleF destinationRectangle,
168 RectangleF sourceRectangle,
169 Color color,
170 float rotation,
171 Vector2 origin,
172 float layerDepth)
173 {
174 Draw(image, destinationRectangle, sourceRectangle, color, rotation, origin, SpriteOptions.None, layerDepth);
175 }
176
177 /// <inheritdoc/>
178 public void Draw(Image image,
179 Vector2 position,
180 RectangleF sourceRectangle,
181 Color color,
182 float rotation,
183 Vector2 origin,
184 Vector2 scale,
185 float layerDepth)
186 {
187 Draw(image, position, sourceRectangle, color, rotation, origin, scale, SpriteOptions.None, layerDepth);
188 }
189
190 /// <inheritdoc/>
191 public void Draw(Image image,
192 RectangleF destinationRectangle,
193 RectangleF? sourceRectangle,
194 Color color,
195 float rotation,
196 Vector2 origin,
197 SpriteOptions options,
198 float layerDepth)
199 {
200 var srcRect = sourceRectangle ?? new(0, 0, image.Width, image.Height);
201 Draw(image, destinationRectangle, srcRect, color, rotation, origin, options, layerDepth);
202 }
203
204 /// <inheritdoc/>
205 public void Draw(Image image,
206 RectangleF destinationRectangle,
207 RectangleF? sourceRectangle,
208 Color color,
209 float rotation,
210 Vector2 origin,
211 float layerDepth)
212 {
213 Draw(image, destinationRectangle, sourceRectangle, color, rotation, origin, SpriteOptions.None, layerDepth);
214 }
215
216 /// <inheritdoc/>
217 public void Draw(Image image,
218 Vector2 position,
219 RectangleF? sourceRectangle,
220 Color color,
221 float rotation,
222 Vector2 origin,
223 Vector2 scale,
224 SpriteOptions options,
225 float layerDepth)
226 {
227 Draw(image: image,
228 position: position,
229 sourceRectangle: sourceRectangle ?? new()
230 {
231 X = 0,
232 Y = 0,
233 Width = image.Width,
234 Height = image.Height,
235 },
236 color: color,
237 rotation: rotation,
238 origin: origin,
239 scale: scale,
240 options: options,
241 layerDepth: layerDepth);
242 }
243
244 /// <inheritdoc/>
245 public void Draw(Image image,
246 Vector2 position,
247 RectangleF? sourceRectangle,
248 Color color,
249 float rotation,
250 Vector2 origin,
251 Vector2 scale,
252 float layerDepth)
253 {
254 Draw(image: image,
255 position: position,
256 sourceRectangle: sourceRectangle,
257 color: color,
258 rotation: rotation,
259 origin: origin,
260 scale: scale,
261 options: SpriteOptions.None,
262 layerDepth: layerDepth);
263 }
264
265 /// <inheritdoc/>
266 public void Draw(Image image,
267 Vector2 position,
268 RectangleF? sourceRectangle,
269 Color color,
270 float rotation,
271 Vector2 origin,
272 float scale,
273 SpriteOptions options,
274 float layerDepth)
275 {
276 Draw(image: image,
277 position: position,
278 sourceRectangle: sourceRectangle,
279 color: color,
280 rotation: rotation,
281 origin: origin,
282 scale: new Vector2(scale),
283 options: options,
284 layerDepth: layerDepth);
285 }
286
287 /// <inheritdoc/>
288 public void Draw(Image image,
289 Vector2 position,
290 RectangleF? sourceRectangle,
291 Color color,
292 float rotation,
293 Vector2 origin,
294 float scale,
295 float layerDepth)
296 {
297 Draw(image: image,
298 position: position,
299 sourceRectangle: sourceRectangle,
300 color: color,
301 rotation: rotation,
302 origin: origin,
303 scale: scale,
304 options: SpriteOptions.None,
305 layerDepth: layerDepth);
306 }
307
308 /// <inheritdoc/>
309 public void Draw(Image image,
310 Vector2 position,
311 RectangleF? sourceRectangle,
312 Color color,
313 SpriteOptions options,
314 float layerDepth)
315 {
316 Draw(image: image,
317 position: position,
318 sourceRectangle: sourceRectangle,
319 color: color,
320 rotation: 0f,
321 origin: default,
322 scale: 1f,
323 options: options,
324 layerDepth: layerDepth);
325 }
326
327 /// <inheritdoc/>
328 public void Draw(Image image,
329 Vector2 position,
330 RectangleF? sourceRectangle,
331 Color color,
332 float layerDepth)
333 {
334 Draw(image: image,
335 position: position,
336 sourceRectangle: sourceRectangle,
337 color: color,
338 options: SpriteOptions.None,
339 layerDepth: layerDepth);
340 }
341
342 /// <inheritdoc/>
343 public void Draw(Image image,
344 RectangleF destinationRectangle,
345 RectangleF? sourceRectangle,
346 Color color,
347 SpriteOptions options,
348 float layerDepth)
349 {
350 Draw(image: image,
351 destinationRectangle: destinationRectangle,
352 sourceRectangle: sourceRectangle,
353 color: color,
354 rotation: 0,
355 origin: default,
356 options: options,
357 layerDepth: layerDepth);
358 }
359
360 /// <inheritdoc/>
361 public void Draw(Image image,
362 RectangleF destinationRectangle,
363 RectangleF? sourceRectangle,
364 Color color,
365 float layerDepth)
366 {
367 Draw(image: image,
368 destinationRectangle: destinationRectangle,
369 sourceRectangle: sourceRectangle,
370 color: color,
371 options: SpriteOptions.None,
372 layerDepth: layerDepth);
373 }
374
375 /// <inheritdoc/>
376 public void Draw(Image image,
377 Vector2 position,
378 Color color,
379 SpriteOptions options,
380 float layerDepth)
381 {
382 Draw(image: image,
383 position: position,
384 sourceRectangle: new Rectangle(default, image.Size),
385 color: color,
386 rotation: 0,
387 origin: default,
388 scale: Vector2.One,
389 options: options,
390 layerDepth: layerDepth);
391 }
392
393 /// <inheritdoc/>
394 public void Draw(Image image,
395 Vector2 position,
396 Color color,
397 float layerDepth)
398 {
399 Draw(image: image,
400 position: position,
401 color: color,
402 options: SpriteOptions.None,
403 layerDepth: layerDepth);
404 }
405
406 /// <inheritdoc/>
407 public void Draw(Image image,
408 RectangleF destinationRectangle,
409 Color color,
410 SpriteOptions options,
411 float layerDepth)
412 {
413 Draw(image: image,
414 destinationRectangle: destinationRectangle,
415 sourceRectangle: new Rectangle(default, image.Size),
416 color: color,
417 rotation: 0,
418 origin: default,
419 options: options,
420 layerDepth: layerDepth);
421 }
422
423 /// <inheritdoc/>
424 public void Draw(Image image,
425 RectangleF destinationRectangle,
426 Color color,
427 float layerDepth)
428 {
429 Draw(image: image,
430 destinationRectangle: destinationRectangle,
431 color: color,
432 options: SpriteOptions.None,
433 layerDepth: layerDepth);
434 }
435 #endregion
436
437 #region ISpriteBatch<TImage>
438
439 /// <inheritdoc/>
440 public void Draw(TImage image,
441 RectangleF destinationRectangle,
442 RectangleF sourceRectangle,
443 Color color,
444 float rotation,
445 Vector2 origin,
446 float layerDepth)
447 {
448 Draw(image: image,
449 destinationRectangle: destinationRectangle,
450 sourceRectangle: sourceRectangle,
451 color: color,
452 rotation: rotation,
453 origin: origin,
454 options: SpriteOptions.None,
455 layerDepth: layerDepth);
456 }
457
458 /// <inheritdoc/>
459 public void Draw(TImage image,
460 Vector2 position,
461 RectangleF sourceRectangle,
462 Color color,
463 float rotation,
464 Vector2 origin,
465 Vector2 scale,
466 float layerDepth)
467 {
468 Draw(image: image,
469 position: position,
470 sourceRectangle: sourceRectangle,
471 color: color,
472 rotation: rotation,
473 origin: origin,
474 scale: scale,
475 options: SpriteOptions.None,
476 layerDepth: layerDepth);
477 }
478
479 /// <inheritdoc/>
480 public void Draw(TImage image,
481 RectangleF destinationRectangle,
482 RectangleF? sourceRectangle,
483 Color color,
484 float rotation,
485 Vector2 origin,
486 SpriteOptions options,
487 float layerDepth)
488 {
489 var size = GetSize(image);
490 Draw(image: image,
491 destinationRectangle: destinationRectangle,
492 sourceRectangle: sourceRectangle ?? new()
493 {
494 X = 0,
495 Y = 0,
496 Width = size.Width,
497 Height = size.Height,
498 },
499 color: color,
500 rotation: rotation,
501 origin: origin,
502 options: options,
503 layerDepth: layerDepth);
504 }
505
506 /// <inheritdoc/>
507 public void Draw(TImage image,
508 RectangleF destinationRectangle,
509 RectangleF? sourceRectangle,
510 Color color,
511 float rotation,
512 Vector2 origin,
513 float layerDepth)
514 {
515 Draw(image, destinationRectangle, sourceRectangle, color, rotation, origin, SpriteOptions.None, layerDepth);
516 }
517
518 /// <inheritdoc/>
519 public void Draw(TImage image,
520 Vector2 position,
521 RectangleF? sourceRectangle,
522 Color color,
523 float rotation,
524 Vector2 origin,
525 Vector2 scale,
526 SpriteOptions options,
527 float layerDepth)
528 {
529 var size = GetSize(image);
530 Draw(image: image,
531 position: position,
532 sourceRectangle: sourceRectangle ?? new()
533 {
534 X = 0,
535 Y = 0,
536 Width = size.Width,
537 Height = size.Height,
538 },
539 color: color,
540 rotation: rotation,
541 origin: origin,
542 scale: scale,
543 options: options,
544 layerDepth: layerDepth);
545 }
546
547 /// <inheritdoc/>
548 public void Draw(TImage image,
549 Vector2 position,
550 RectangleF? sourceRectangle,
551 Color color,
552 float rotation,
553 Vector2 origin,
554 Vector2 scale,
555 float layerDepth)
556 {
557 Draw(image: image,
558 position: position,
559 sourceRectangle: sourceRectangle,
560 color: color,
561 rotation: rotation,
562 origin: origin,
563 scale: scale,
564 options: SpriteOptions.None,
565 layerDepth: layerDepth);
566 }
567
568 /// <inheritdoc/>
569 public void Draw(TImage image,
570 Vector2 position,
571 RectangleF? sourceRectangle,
572 Color color,
573 float rotation,
574 Vector2 origin,
575 float scale,
576 SpriteOptions options,
577 float layerDepth)
578 {
579 Draw(image, position, sourceRectangle, color, rotation, origin, new Vector2(scale), options, layerDepth);
580 }
581
582 /// <inheritdoc/>
583 public void Draw(TImage image,
584 Vector2 position,
585 RectangleF? sourceRectangle,
586 Color color,
587 float rotation,
588 Vector2 origin,
589 float scale,
590 float layerDepth)
591 {
592 Draw(image, position, sourceRectangle, color, rotation, origin, scale, SpriteOptions.None, layerDepth);
593 }
594
595 /// <inheritdoc/>
596 public void Draw(TImage image,
597 Vector2 position,
598 RectangleF? sourceRectangle,
599 Color color,
600 SpriteOptions options,
601 float layerDepth)
602 {
603 Draw(image, position, sourceRectangle, color, 0f, default, 0f, options, layerDepth);
604 }
605
606 /// <inheritdoc/>
607 public void Draw(TImage image,
608 Vector2 position,
609 RectangleF? sourceRectangle,
610 Color color,
611 float layerDepth)
612 {
613 Draw(image, position, sourceRectangle, color, SpriteOptions.None, layerDepth);
614 }
615
616 /// <inheritdoc/>
617 public void Draw(TImage image,
618 RectangleF destinationRectangle,
619 RectangleF? sourceRectangle,
620 Color color,
621 SpriteOptions options,
622 float layerDepth)
623 {
624 Draw(image, destinationRectangle, sourceRectangle, color, 0, default, options, layerDepth);
625 }
626
627 /// <inheritdoc/>
628 public void Draw(TImage image,
629 RectangleF destinationRectangle,
630 RectangleF? sourceRectangle,
631 Color color,
632 float layerDepth)
633 {
634 Draw(image, destinationRectangle, sourceRectangle, color, SpriteOptions.None, layerDepth);
635 }
636
637 /// <inheritdoc/>
638 public void Draw(TImage image,
639 Vector2 position,
640 Color color,
641 SpriteOptions options,
642 float layerDepth)
643 {
644 Draw(image, position, new Rectangle(default, GetSize(image)), color, 0, default, default, options, layerDepth);
645 }
646
647 /// <inheritdoc/>
648 public void Draw(TImage image,
649 Vector2 position,
650 Color color,
651 float layerDepth)
652 {
653 Draw(image, position, new Rectangle(default, GetSize(image)), color, 0, default, default, SpriteOptions.None, layerDepth);
654 }
655
656 /// <inheritdoc/>
657 public void Draw(TImage image,
658 RectangleF destinationRectangle,
659 Color color,
660 SpriteOptions options,
661 float layerDepth)
662 {
663 Draw(image, destinationRectangle, new Rectangle(default, GetSize(image)), color, 0, default, options, layerDepth);
664 }
665
666 /// <inheritdoc/>
667 public void Draw(TImage image,
668 RectangleF destinationRectangle,
669 Color color,
670 float layerDepth)
671 {
672 Draw(image, destinationRectangle, color, SpriteOptions.None, layerDepth);
673 }
674
675 /// <inheritdoc/>
676 public void DrawRect(RectangleF rectangle, Color color, float rotation, Vector2 origin, float layerDepth)
677 {
678 Draw(m_whiteImage, rectangle, new RectangleF(0, 0, 1, 1), color, rotation, origin, layerDepth);
679 }
680
681 /// <inheritdoc/>
682 public void DrawRect(RectangleF rectangle, Color color, float layerDepth)
683 {
684 Draw(m_whiteImage, rectangle, new RectangleF(0, 0, 1, 1), color, layerDepth);
685 }
686
687 /// <inheritdoc/>
688 public void DrawDot(Vector2 position, float size, Color color, float layerDepth)
689 {
690 Draw(m_whiteImage, new RectangleF(position.X, position.Y, size, size), new RectangleF(0, 0, 1, 1), color, layerDepth);
691 }
692
693 /// <inheritdoc/>
694 public void DrawSegment(Vector2 a, Vector2 b, Color color, float thickness, float layerDepth)
695 {
696 var diff = b - a;
697 var length = MathF.Sqrt(Vector2.Dot(diff, diff));
698 var angle = MathF.Atan2(diff.Y, diff.X);
699 DrawSegment(a, length, color, angle, thickness, layerDepth);
700 }
701
702 /// <inheritdoc/>
703 public void DrawSegment(Vector2 start, float length, Color color, float angle, float thickness, float layerDepth)
704 {
705 var rect = new RectangleF(start.X, start.Y, length, thickness);
706 DrawRect(rect, color, angle, new Vector2(0, 0.5f), layerDepth);
707 }
708
709 #endregion
710
711 /// <summary>
712 /// Gets size of a image.
713 /// </summary>
714 /// <param name="image">Image.</param>
715 /// <returns>Image Size.</returns>
716 protected abstract Size GetSize(TImage image);
717
718 /// <summary>
719 /// Gets TImage.
720 /// </summary>
721 /// <param name="image"></param>
722 /// <returns></returns>
723 protected abstract TImage GetHandle(Image image);
724}