1 /**
2  * Renderer
3  */
4 module d2d.sdl2.Renderer;
5 
6 import std.algorithm;
7 import std.math;
8 import d2d.sdl2;
9 
10 /**
11  * Renderers are objects that handle drawing things such as textures or shapes
12  * A renderer can be obtained from a window, and could be used to draw on the window
13  * Renderers draw using buffers; when a renderer draws, it isn't visible to the screen until the present method is called
14  * While most of these functions are ported directly off of LibSDL2, most of them have been renamed into standard OOP convention
15  * Many SDL functions are now property methods (eg. SDL_SetRenderDrawColor => renderer.drawColor = ...)
16  * All functions defined in renderer are based off of SDL functions and SDL documentation can be viewed as well
17  */
18 class Renderer {
19 
20     private SDL_Renderer* renderer;
21 
22     /**
23      * Returns the raw SDL data of this object
24      */
25     @property SDL_Renderer* handle() {
26         return this.renderer;
27     }
28 
29     /**
30      * Sets the color of the renderer will draw with
31      */
32     @property void drawColor(Color color) {
33         ensureSafe(SDL_SetRenderDrawColor(this.renderer, color.r, color.g, color.b, color.a));
34     }
35 
36     /**
37      * Returns the color that the renderer will draw with
38      */
39     @property Color drawColor() {
40         Color color;
41         ensureSafe(SDL_GetRenderDrawColor(this.renderer, &color.r, &color.g,
42                 &color.b, &color.a));
43         return color;
44     }
45 
46     /**
47      * Sets the renderer's draw blend mode that affects how the renderer draws
48      */
49     @property void drawBlendMode(SDL_BlendMode blendMode) {
50         ensureSafe(SDL_SetRenderDrawBlendMode(this.renderer, blendMode));
51     }
52 
53     /**
54      * Gets the renderer's draw blend mode that affects how the renderer draws
55      */
56     @property SDL_BlendMode drawBlendMode() {
57         SDL_BlendMode bMode;
58         ensureSafe(SDL_GetRenderDrawBlendMode(this.renderer, &bMode));
59         return bMode;
60     }
61 
62     /**
63      * Sets the viewport of the renderer to the given rectangle
64      */
65     @property void viewport(iRectangle vPort) {
66         ensureSafe(SDL_RenderSetViewport(this.renderer, vPort.handle));
67     }
68 
69     /**
70      * Gets the viewport of the renderer as a rectangle
71      */
72     @property iRectangle viewport() {
73         SDL_Rect viewPort;
74         SDL_RenderGetViewport(this.renderer, &viewPort);
75         return new iRectangle(viewPort.x, viewPort.y, viewPort.w, viewPort.h);
76     }
77 
78     /**
79      * Sets the clip area for the renderer
80      * Anything that is rendered outside of the clip area gets discarded
81      */
82     @property void clipRect(iRectangle clipArea) {
83         ensureSafe(SDL_RenderSetClipRect(this.renderer, (clipArea is null) ? null : clipArea.handle));
84     }
85 
86     /**
87      * Gets the clip area for the renderer
88      * Anything that is rendered outside of the clip area gets discarded
89      */
90     @property iRectangle clipRect() {
91         SDL_Rect clipArea;
92         SDL_RenderGetClipRect(this.renderer, &clipArea);
93         return (clipArea.w == 0 && clipArea.h == 0) ? null
94             : new iRectangle(clipArea.x, clipArea.y, clipArea.w, clipArea.h);
95     }
96 
97     /**
98      * Sets the renderer's x and y scale to the given point's x and y values
99      */
100     @property void scale(fVector scaling) {
101         ensureSafe(SDL_RenderSetScale(this.renderer, scaling.x, scaling.y));
102     }
103 
104     /**
105      * Gets the renderer's x and y scale as a point with the scales as the x and y coordinates
106      */
107     @property fVector scale() {
108         fVector scaling = new fVector(1, 1);
109         ensureSafe(SDL_RenderGetScale(this.renderer, &scaling.x, &scaling.y));
110         return scaling;
111     }
112 
113     /**
114      * Sets the renderer's logical size
115      * Logical size works in that you only need to give coordinates for one specific resolution, and SDL will handle scaling that to the best resolution matching the logical size's aspect ratio
116      */
117     @property void logicalSize(iVector dimensions) {
118         ensureSafe(SDL_RenderSetLogicalSize(this.renderer, dimensions.x, dimensions.y));
119     }
120 
121     /**
122      * Gets the renderer's logical size
123      * Logical size works in that you only need to give coordinates for one specific resolution, and SDL will handle scaling that to the best resolution matching the logical size's aspect ratio
124      */
125     @property iVector logicalSize() {
126         iVector dimensions = new iVector(0, 0);
127         SDL_RenderGetLogicalSize(this.renderer, &dimensions.x, &dimensions.y);
128         return dimensions;
129     }
130 
131     /**
132      * Gets the renderer's output size
133      */
134     @property iVector outputSize() {
135         iVector size = new iVector(0, 0);
136         ensureSafe(SDL_GetRendererOutputSize(this.renderer, &size.x, &size.y));
137         return size;
138     }
139 
140     /**
141      * Gets the renderer's information and returns it as an SDL_RendererInfo struct
142      */
143     @property SDL_RendererInfo info() {
144         SDL_RendererInfo information;
145         ensureSafe(SDL_GetRendererInfo(this.renderer, &information));
146         return information;
147     }
148 
149     /**
150      * Makes an SDL renderer for a window
151      */
152     this(Window window, uint flags = 0) {
153         this.renderer = ensureSafe(SDL_CreateRenderer(window.handle, -1,
154                 cast(SDL_RendererFlags) flags));
155     }
156 
157     /**
158      * Makes a renderer from an already existing SDL_Renderer
159      */
160     this(SDL_Renderer* alreadyExisting) {
161         this.renderer = alreadyExisting;
162     }
163 
164     /**
165      * Ensures that SDL can properly dispose of the renderer
166      */
167     ~this() {
168         SDL_DestroyRenderer(this.renderer);
169     }
170 
171     /**
172      * Copies a texture to the window at the given point
173      * Uses the dimensions of the given sourceRect or if not given, the dimensions of the original texture
174      */
175     void copy(Texture texture, int x, int y, iRectangle sourceRect = null) {
176         this.copy(texture, new iRectangle(x, y, texture.dimensions.x,
177                 texture.dimensions.y), sourceRect);
178     }
179 
180     /**
181      * Copies a texture to the window at the given rectangle
182      * If sourceRect is null, it will copy the entire texture, otherwise, it will copy the slice defined by sourceRect
183      */
184     void copy(Texture texture, iRectangle destinationRect, iRectangle sourceRect = null) {
185         ensureSafe(SDL_RenderCopy(this.renderer, texture.handle,
186                 (sourceRect is null) ? null : sourceRect.handle, destinationRect.handle));
187     }
188 
189     /**
190      * Copies a texture to the window at the given rectangle with the given angle
191      * If sourceRect is null, it will copy the entire texture, otherwise, it will copy the slice defined by sourceRect
192      * Angles are given in radians
193      */
194     void copy(Texture texture, iRectangle destinationRect, double angle,
195             SDL_RendererFlip flip = SDL_FLIP_NONE, iVector center = null,
196             iRectangle sourceRect = null) {
197         ensureSafe(SDL_RenderCopyEx(this.renderer, texture.handle, (sourceRect is null)
198                 ? null : sourceRect.handle, destinationRect.handle,
199                 angle * 180 / PI, (center is null) ? null : center.handle, flip));
200     }
201 
202     /**
203      * Internally used function that performs an action with a certain color
204      */
205     private void performWithColor(Color color, void delegate() action) {
206         immutable oldColor = this.drawColor;
207         this.drawColor = color;
208         action();
209         this.drawColor = oldColor;
210     }
211 
212     /**
213      * Fills the screen with the existing renderer color
214      */
215     void clear() {
216         ensureSafe(SDL_RenderClear(this.renderer));
217     }
218 
219     /**
220      * Sets the renderer's color and clears the screen
221      */
222     void clear(Color color) {
223         this.performWithColor(color, { this.clear(); });
224     }
225 
226     /**
227      * Draws a line between the given points
228      */
229     void draw(iVector first, iVector second) {
230         ensureSafe(SDL_RenderDrawLine(this.renderer, first.x, first.y, second.x, second.y));
231     }
232 
233     /**
234      * Draws a line of a given color between the given points
235      */
236     void draw(iVector first, iVector second, Color color) {
237         this.performWithColor(color, { this.draw(first, second); });
238     }
239 
240     /**
241      * Draws a line given a segment
242      */
243     void draw(iSegment line) {
244         this.draw(line.initial, line.terminal);
245     }
246 
247     /**
248      * Draws a line with a specific color
249      */
250     void draw(iSegment line, Color color) {
251         this.performWithColor(color, { this.draw(line); });
252     }
253 
254     /**
255      * Draws a point
256      */
257     void draw(int x, int y) {
258         ensureSafe(SDL_RenderDrawPoint(this.renderer, x, y));
259     }
260 
261     /**
262      * Draws a point
263      */
264     void draw(iVector toDraw) {
265         this.draw(toDraw.x, toDraw.y);
266     }
267 
268     /**
269      * Draws a point in the given color
270      */
271     void draw(iVector toDraw, Color color) {
272         this.performWithColor(color, { this.draw(toDraw); });
273     }
274 
275     /**
276      * Draws a rectangle
277      */
278     void draw(iRectangle toDraw) {
279         ensureSafe(SDL_RenderDrawRect(this.renderer, toDraw.handle));
280     }
281 
282     /**
283      * Draws a rectangle with the given color
284      */
285     void draw(iRectangle toDraw, Color color) {
286         this.performWithColor(color, { this.draw(toDraw); });
287     }
288 
289     /**
290      * Draws the given bezier curve with numPoints number of points on the curve
291      * More points is smoother but slower
292      */
293     void draw(uint numPoints = 100)(BezierCurve!(int, 2) curve) {
294         Vector!(int, 2)[numPoints] points = (curve.getPoints!numPoints);
295         foreach (i; 0 .. points.length - 1) {
296             this.draw(new iSegment(points[i], points[i + 1]));
297         }
298     }
299 
300     /**
301      * Draws the given bezier curve with the given color and amount of points on the curve
302      * More points is smoother but slower
303      */
304     void draw(uint numPoints = 100)(BezierCurve!(int, 2) curve, Color color) {
305         this.performWithColor(color, { this.draw!numPoints(curve); });
306     }
307 
308     /**
309      * Fills a rectangle in
310      */
311     void fill(iRectangle toFill) {
312         ensureSafe(SDL_RenderFillRect(this.renderer, toFill.handle));
313     }
314 
315     /**
316      * Fills a rectangle in with the given color
317      */
318     void fill(iRectangle toFill, Color color) {
319         this.performWithColor(color, { this.fill(toFill); });
320     }
321 
322     /**
323      * Draws a polygon
324      */
325     void draw(uint sides)(iPolygon!sides toDraw) {
326         foreach (polygonSide; toDraw.sides) {
327             this.draw(polygonSide);
328         }
329     }
330 
331     /**
332      * Draws a polygon with the given color
333      */
334     void draw(uint sides)(iPolygon!sides toDraw, Color color) {
335         this.performWithColor(color, { this.draw!sides(toDraw); });
336     }
337 
338     /**
339      * Draws the ellipse bounded by the given box between the given angles in radians
340      * More points generally means a slower but more well drawn ellipse
341      */
342     void draw(uint numPoints = 100)(iRectangle bounds, double startAngle, double endAngle) {
343         immutable angleStep = (endAngle - startAngle) / numPoints;
344         iVector previousPoint;
345         iVector currentPoint = new iVector(-1);
346         foreach (i; 0 .. numPoints + 1) {
347             immutable currentAngle = startAngle + angleStep * i;
348             currentPoint.x = cast(int)(bounds.extent.x * cos(currentAngle) / 2);
349             currentPoint.y = cast(int)(bounds.extent.y * sin(currentAngle) / 2);
350             currentPoint += bounds.center;
351             if (previousPoint !is null) {
352                 draw(previousPoint, currentPoint);
353             }
354             previousPoint = new iVector(currentPoint);
355         }
356     }
357 
358     /**
359      * Draws the ellipse bounded by the given box between the given angles in radians with the given color
360      * More points generally means a slower but more well drawn ellipse
361      */
362     void draw(uint numPoints = 100)(iRectangle bounds, double startAngle,
363             double endAngle, Color color) {
364         this.performWithColor(color, {
365             this.draw!numPoints(bounds, startAngle, endAngle);
366         });
367     }
368 
369     /**
370      * Fills the ellipse bounded by the given box between the given angles in radians
371      * Fills the ellipse between the arc endpoints: fills ellipse as arc rather than filling as ellipse (not a pizza slice)
372      * More points generally means a slower but more well drawn ellipse
373      */
374     void fill(uint numPoints = 100)(iRectangle bounds, double startAngle, double endAngle) {
375         immutable angleStep = (endAngle - startAngle) / numPoints;
376         iPolygon!numPoints ellipseSlice = new iPolygon!numPoints();
377         foreach (i; 0 .. numPoints) {
378             immutable currentAngle = startAngle + angleStep * i;
379             ellipseSlice.vertices[i] = bounds.center + new iVector(
380                     cast(int)(bounds.extent.x * cos(currentAngle) / 2),
381                     cast(int)(bounds.extent.y * sin(currentAngle) / 2));
382         }
383         this.fill!numPoints(ellipseSlice);
384     }
385 
386     /**
387      * Fills the ellipse bounded by the given box between the given angles in radians with the given color
388      * Fills the ellipse between the arc endpoints: fills ellipse as arc rather than filling as ellipse (not a pizza slice)
389      * More points generally means a slower but more well drawn ellipse
390      */
391     void fill(uint numPoints = 100)(iRectangle bounds, double startAngle,
392             double endAngle, Color color) {
393         this.performWithColor(color, {
394             this.fill!numPoints(bounds, startAngle, endAngle);
395         });
396     }
397 
398     /**
399      * Fills a polygon
400      * Uses scanlining
401      */
402     void fill(uint sides)(iPolygon!sides toDraw) {
403         iRectangle bounds = bound(toDraw);
404         int[][int] intersections; //Stores a list of x coordinates of intersections accessed by the y value
405         foreach (polygonSide; toDraw.sides) {
406             foreach (y; bounds.initialPoint.y .. bounds.bottomLeft.y) {
407                 //Checks that the y value exists within the segment
408                 if ((y - polygonSide.initial.y) * (y - polygonSide.terminal.y) > 0) {
409                     continue;
410                 }
411                 //If the segment is a horizontal line at this y, draws the horizontal line and then breaks
412                 if (y == polygonSide.initial.y && polygonSide.initial.y == polygonSide.terminal.y) {
413                     this.draw(polygonSide.initial, polygonSide.terminal);
414                     continue;
415                 }
416                 //Vertical lines
417                 if (polygonSide.initial.x == polygonSide.terminal.x) {
418                     intersections[y] ~= polygonSide.initial.x;
419                     continue;
420                 }
421                 //Finds the intersection of the horizontal y = line with the polygon side using point slope form of a line
422                 iVector sideDirection = polygonSide.direction;
423                 immutable dy = y - polygonSide.initial.y;
424                 intersections[y] ~= (dy * sideDirection.x + polygonSide.initial.x * sideDirection.y) / sideDirection
425                     .y;
426 
427             }
428         }
429         foreach (y, xValues; intersections) {
430             foreach (i; 0 .. xValues.sort.length - 1) {
431                 this.draw(new iVector(xValues[i], y), new iVector(xValues[i + 1], y));
432             }
433         }
434     }
435 
436     /**
437      * Fills a polygon with a given color
438      */
439     void fill(uint sides)(iPolygon!sides toDraw, Color color) {
440         this.performWithColor(color, { this.fill!sides(toDraw); });
441     }
442 
443     /**
444      * Updates what the renderer has drawn by actually outputting or presenting it
445      */
446     void present() {
447         SDL_RenderPresent(this.renderer);
448         this.clear();
449     }
450 
451 }