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 }