1 /* 2 ******************************************************************************************* 3 * Dgame (a D game framework) - Copyright (c) Randy Schütt 4 * 5 * This software is provided 'as-is', without any express or implied warranty. 6 * In no event will the authors be held liable for any damages arising from 7 * the use of this software. 8 * 9 * Permission is granted to anyone to use this software for any purpose, 10 * including commercial applications, and to alter it and redistribute it 11 * freely, subject to the following restrictions: 12 * 13 * 1. The origin of this software must not be misrepresented; you must not claim 14 * that you wrote the original software. If you use this software in a product, 15 * an acknowledgment in the product documentation would be appreciated but is 16 * not required. 17 * 18 * 2. Altered source versions must be plainly marked as such, and must not be 19 * misrepresented as being the original software. 20 * 21 * 3. This notice may not be removed or altered from any source distribution. 22 ******************************************************************************************* 23 */ 24 module Dgame.Graphics.Shape; 25 26 private { 27 import std.math : sin, cos, abs; 28 import std.algorithm : remove, min, max; 29 import core.stdc..string : memcpy; 30 31 import derelict.opengl3.gl; 32 33 import Dgame.Graphics.Color; 34 import Dgame.Graphics.Drawable; 35 import Dgame.Graphics.Transformable; 36 import Dgame.Graphics.Texture; 37 import Dgame.Graphics.Blend; 38 import Dgame.Math.Vertex; 39 import Dgame.Math.Rect; 40 import Dgame.System.VertexRenderer; 41 } 42 43 enum PIx2 = 3.14f * 2; 44 45 /** 46 * Smooth wrapper 47 */ 48 struct Smooth { 49 /** 50 * Supported smooth targets. 51 */ 52 enum Target { 53 None, /** No smooth (default). */ 54 Point = GL_POINT_SMOOTH, /** Enable smooth for points. */ 55 Line = GL_LINE_SMOOTH, /** Enable smooth for lines. */ 56 Polygon = GL_POLYGON_SMOOTH /** Enable smooth for polygons. */ 57 } 58 59 /** 60 * The smooth mode 61 */ 62 enum Mode { 63 DontCare = GL_DONT_CARE, /** The OpenGL implementation decide on their own. */ 64 Fastest = GL_FASTEST, /** Fastest kind of smooth (default). */ 65 Nicest = GL_NICEST /** Nicest but slowest kind of smooth. */ 66 } 67 68 Target target; /// The Target 69 Mode mode; /// The Mode 70 GLenum hint; /// The GL Hint (is automatically set) 71 72 this(Target trg, Mode mode) { 73 this.target = target; 74 this.mode = mode; 75 76 final switch (this.target) { 77 case Target.None: break; 78 case Target.Point: 79 this.hint = GL_POINT_SMOOTH_HINT; 80 break; 81 case Target.Line: 82 this.hint = GL_LINE_SMOOTH_HINT; 83 break; 84 case Target.Polygon: 85 this.hint = GL_POLYGON_SMOOTH_HINT; 86 break; 87 } 88 } 89 } 90 91 private struct Range { 92 float min, max; 93 } 94 95 private struct RangePoint { 96 Range x; 97 Range y; 98 } 99 100 private RangePoint min_max(const Vertex[] vertices) pure nothrow { 101 RangePoint rpoint = RangePoint(Range(vertices[0].x, vertices[0].x), 102 Range(vertices[0].y, vertices[0].y)); 103 104 for (size_t i = 1; i < vertices.length; i++) { 105 rpoint.x.min = min(rpoint.x.min, vertices[i].x); 106 rpoint.x.max = max(rpoint.x.max, vertices[i].x); 107 rpoint.y.min = min(rpoint.y.min, vertices[i].y); 108 rpoint.y.max = max(rpoint.y.max, vertices[i].y); 109 } 110 111 return rpoint; 112 } 113 114 /** 115 * Shape defines a drawable convex shape. 116 * It also defines helper functions to draw simple shapes like lines, rectangles, circles, etc. 117 * 118 * Author: rschuett 119 */ 120 class Shape : Transformable, Drawable, Blendable { 121 /** 122 * Supported shape types. 123 */ 124 enum Type { 125 Quad = GL_QUADS, /** Declare that the stored vertices are Quads. */ 126 QuadStrip = GL_QUAD_STRIP, /** Declare that the stored vertices are Quad Strips*/ 127 Triangle = GL_TRIANGLES, /** Declare that the stored vertices are Triangles. */ 128 TriangleStrip = GL_TRIANGLE_STRIP, /** Declare that the stored vertices are Triangles Strips */ 129 TriangleFan = GL_TRIANGLE_FAN, /** Declare that the stored vertices are Triangles Fans. */ 130 Lines = GL_LINES, /** Declare that the stored vertices are Lines. */ 131 LineStrip = GL_LINE_STRIP, /** Declare that the stored vertices are Line Strips. */ 132 LineLoop = GL_LINE_LOOP, /** Declare that the stored vertices are Line Loops. */ 133 Polygon = GL_POLYGON, /** Declare that the stored vertices are Polygons. */ 134 135 Unfilled = LineLoop, /** Unfilled Type. It is identical to LineLoop. */ 136 Circle = TriangleFan /** Circle Type. It is identical to TriangleFan. */ 137 } 138 139 protected: 140 ubyte _lineWidth; 141 bool _isFilled = true; 142 bool _needUpdate; 143 144 Type _type; 145 Smooth _smooth; 146 147 Vertex[] _vertices; 148 149 Texture _tex; 150 ShortRect _texRect; 151 Blend _blend; 152 153 protected: 154 void _render() { 155 if (this._vertices.length == 0) 156 return; 157 158 glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_COLOR_BUFFER_BIT); 159 scope(exit) glPopAttrib(); 160 161 const bool texEnabled = glIsEnabled(GL_TEXTURE_2D) == GL_TRUE; 162 if (this._tex is null && texEnabled) 163 glDisable(GL_TEXTURE_2D); 164 else if (this._tex !is null && !texEnabled) 165 glEnable(GL_TEXTURE_2D); 166 167 if (this._smooth.target != Smooth.Target.None) { 168 if (!glIsEnabled(this._smooth.target)) 169 glEnable(this._smooth.target); 170 glHint(this._smooth.hint, this._smooth.mode); 171 } 172 173 if (this._lineWidth > 1) 174 glLineWidth(this._lineWidth); 175 176 glPushMatrix(); 177 scope(exit) glPopMatrix(); 178 179 if (this._needUpdate && this._tex !is null) { 180 this._needUpdate = false; 181 this._updateTexCoords(); 182 } 183 184 Vertex* vptr = &this._vertices[0]; 185 VertexRenderer.pointTo(Target.Vertex, vptr, Vertex.sizeof, 0); 186 VertexRenderer.pointTo(Target.Color, vptr, Vertex.sizeof, 12); 187 VertexRenderer.pointTo(Target.TexCoords, vptr, Vertex.sizeof, 28); 188 189 if (this._tex !is null) 190 this._tex.bind(); 191 192 if (this._blend !is null) 193 this._blend.applyBlending(); 194 195 scope(exit) { 196 if (this._tex !is null) 197 this._tex.unbind(); 198 199 VertexRenderer.disableAllStates(); 200 } 201 202 super._applyTranslation(); 203 204 const Type type = !this.isFilled() && this._tex is null ? Type.Unfilled : this._type; 205 VertexRenderer.drawArrays(type, this._vertices.length); 206 } 207 208 final void _updateTexCoords() pure nothrow { 209 if (this._vertices.length == 0) 210 return; 211 212 RangePoint rpoint = min_max(this._vertices); 213 214 const float diff_x = abs(rpoint.x.min - rpoint.x.max); 215 const float diff_y = abs(rpoint.y.min - rpoint.y.max); 216 217 foreach (ref Vertex v; this._vertices) { 218 v.tx = ((v.x - rpoint.x.min) / diff_x); 219 v.ty = ((v.y - rpoint.y.min) / diff_y); 220 } 221 222 if (!this._texRect.isCollapsed()) { 223 const float tx = (0f + this._texRect.x) / this._tex.width; 224 const float ty = (0f + this._texRect.y) / this._tex.height; 225 const float tw = (0f + this._texRect.width) / this._tex.width; 226 const float th = (0f + this._texRect.height) / this._tex.height; 227 228 foreach (ref Vertex v; this._vertices) { 229 v.tx = (v.tx * tw) + tx; 230 v.ty = (v.ty * th) + ty; 231 } 232 } 233 } 234 235 public: 236 final: 237 /** 238 * CTor 239 */ 240 this(Type type, Texture tex = null) { 241 this._type = type; 242 this._smooth = Smooth(Smooth.Target.None, Smooth.Mode.Fastest); 243 244 this.bindTexture(tex); 245 } 246 247 /** 248 * Calculate, store and return the center point. 249 */ 250 override ref const(Vector2s) calculateCenter() pure nothrow { 251 const RangePoint rpoint = min_max(this._vertices); 252 super.setCenter(cast(short)(rpoint.x.max / 2), cast(short)(rpoint.y.max / 2)); 253 254 return super.getCenter(); 255 } 256 257 /** 258 * Set (or reset) the current Blend instance. 259 */ 260 void setBlend(Blend blend) pure nothrow { 261 this._blend = blend; 262 } 263 264 /** 265 * Returns the current Blend instance, or null. 266 */ 267 inout(Blend) getBlend() inout pure nothrow { 268 return this._blend; 269 } 270 271 /** 272 * Bind (or unbind) a Texture. 273 */ 274 void bindTexture(Texture tex) { 275 this.setColor(Color.White); 276 277 this._tex = tex; 278 if (tex !is null && this._type == Type.LineLoop) 279 this._type = Type.Polygon; 280 } 281 282 /** 283 * Set a Texture Rect 284 */ 285 void setTextureRect(ref const ShortRect texRect) { 286 this._texRect = texRect; 287 } 288 289 /** 290 * Rvalue version 291 */ 292 void setTextureRect(const ShortRect texRect) { 293 this.setTextureRect(texRect); 294 } 295 296 /** 297 * Returns a pointer to the Texture Rect. 298 * With this you can change the existing Rect without setting a new one. 299 * You can e.g. collapse the Rect with this method. 300 * Example: 301 * --- 302 * Shape s = Shape.make(...); 303 * // A lot of code 304 * s.fetchTextureRect().collapse(); 305 * --- 306 */ 307 inout(ShortRect*) fetchTextureRect() inout pure nothrow { 308 return &this._texRect; 309 } 310 311 /** 312 * Set target and mode of smoothing. 313 */ 314 void setSmooth(Smooth.Target sTarget, Smooth.Mode sMode = Smooth.Mode.Fastest) pure nothrow { 315 this._smooth.target = sTarget; 316 this._smooth.mode = sMode; 317 } 318 319 /** 320 * Set target and mode of smoothing. 321 */ 322 void setSmooth(ref const Smooth smooth) pure nothrow { 323 this.setSmooth(smooth.target, smooth.mode); 324 } 325 326 /** 327 * Return the current smooth 328 */ 329 ref const(Smooth) getSmooth() const pure nothrow { 330 return this._smooth; 331 } 332 333 /** 334 * The current shape will be updated. 335 */ 336 void forceUpdate() pure nothrow { 337 this._needUpdate = true; 338 } 339 340 /** 341 * Set or replace the current Shape type. 342 * 343 * See: Shape.Type enum. 344 */ 345 void setType(Type type) pure nothrow { 346 this._type = type; 347 } 348 349 /** 350 * Returns the Shape Type. 351 * 352 * See: Shape.Type enum. 353 */ 354 Type getType() const pure nothrow { 355 return this._type; 356 } 357 358 /** 359 * Set for <b>all</b> vertices a (new) color. 360 * 361 * Note: This method does not need an update call. 362 */ 363 void setColor(ref const Color col) { 364 foreach (ref Vertex v; this._vertices) { 365 v.setColor(col); 366 } 367 } 368 369 /** 370 * Rvalue version 371 */ 372 void setColor(const Color col) { 373 this.setColor(col); 374 } 375 376 /** 377 * Activate fill mode. 378 * This means the whole shape is drawn and not only the outlines. 379 * 380 * Note: This method does not need an update call. 381 */ 382 void fill(bool fill) pure nothrow { 383 this._isFilled = fill; 384 if (this._type == Type.LineLoop) 385 this._type = Type.Polygon; 386 } 387 388 /** 389 * Returns if the fill mode is active or not. 390 */ 391 bool isFilled() const pure nothrow { 392 return this._isFilled; 393 } 394 395 /** 396 * Set the line width. 397 * 398 * Note: This method does not need an update call. 399 */ 400 void setLineWidth(ubyte width) pure nothrow { 401 this._lineWidth = width; 402 } 403 404 /** 405 * Returns the line width. 406 */ 407 ubyte getLineWidth() const pure nothrow { 408 return this._lineWidth; 409 } 410 411 /** 412 * Stores a Vertex for this Shape. 413 */ 414 void append(ref const Vertex vx) { 415 this._needUpdate = true; 416 417 this._vertices ~= vx; 418 } 419 420 /** 421 * Rvalue version. 422 */ 423 void append(const Vertex vec) { 424 this.append(vec); 425 } 426 427 /** 428 * Stores multiple Vertices for this Shape. 429 */ 430 void append(const Vertex[] vertices) { 431 this._needUpdate = true; 432 433 this._vertices ~= vertices; 434 } 435 436 /** 437 * Remove the Vertex on the specific index. 438 * If vp is not null, the droped Vertex is stored there. 439 */ 440 void remove(uint index, Vertex* vp = null) { 441 if (index >= this._vertices.length) 442 return; 443 444 this._needUpdate = true; 445 446 if (vp !is null) 447 .memcpy(vp, &this._vertices[index], Vertex.sizeof); 448 449 this._vertices = .remove(this._vertices, index); 450 } 451 452 /** 453 * Returns all Vertex of this Shape. 454 */ 455 const(Vertex[]) getVertices() const pure nothrow { 456 return this._vertices; 457 } 458 459 /** 460 * Returns the Vertex at the given index 461 * or throws an exception, if the index is out of rpoint. 462 */ 463 ref const(Vertex) getVertexAt(uint idx) const { 464 if (idx < this._vertices.length) 465 return this._vertices[idx]; 466 467 throw new Exception("No Vertex at this index."); 468 } 469 470 /** 471 * Returns a pointer of the Vertex at the given index 472 * or null if the index is out of rpoint. 473 */ 474 inout(Vertex)* fetchVertexAt(uint idx) inout { 475 return idx < this._vertices.length ? &this._vertices[idx] : null; 476 } 477 478 /** 479 * Add an array of floats 480 * Note that 3 dimensional coordinate components are expected. 481 */ 482 static Shape make(Type type, const float[] mat) { 483 Shape s = new Shape(type); 484 485 const size_t size = mat.length % 3 == 0 ? mat.length : mat.length - (mat.length % 3); 486 for (size_t i = 0; i < size; i += 3) { 487 s.append(Vertex(mat[i], mat[i + 1], mat[i + 2])); 488 } 489 490 return s; 491 } 492 493 /** 494 * Make a new Shape object with the given type and vertices. 495 */ 496 static Shape make(Type type, const Vertex[] vertices) { 497 Shape qs = new Shape(type); 498 499 foreach (ref const Vertex v; vertices) { 500 qs.append(v); 501 } 502 503 return qs; 504 } 505 506 /** 507 * Make a new Shape object as Circle. 508 */ 509 static Shape makeCircle(ubyte radius, const Vector2f center, ubyte vecNum = 30) in { 510 assert(vecNum >= 10, "Need at least 10 vectors for a circle."); 511 } body { 512 const float Deg2Rad = PIx2 / vecNum; 513 514 Shape s = new Shape(Type.Circle); 515 516 for (ubyte i = 0; i < vecNum; i++) { 517 const float degInRad = i * Deg2Rad; 518 519 float x = center.x + cos(degInRad) * radius; 520 float y = center.y + sin(degInRad) * radius; 521 522 s.append(Vertex(x, y)); 523 } 524 525 return s; 526 } 527 }