1 /* 2 Inochi2D Drawable base class 3 4 Copyright © 2020, Inochi2D Project 5 Distributed under the 2-Clause BSD License, see LICENSE file. 6 7 Authors: Luna Nielsen 8 */ 9 module inochi2d.core.nodes.drawable; 10 public import inochi2d.core.nodes.defstack; 11 import inochi2d.fmt.serialize; 12 import inochi2d.math; 13 import bindbc.opengl; 14 import std.exception; 15 import inochi2d.core.dbg; 16 import inochi2d.core; 17 18 private GLuint drawableVAO; 19 20 package(inochi2d) { 21 void inInitDrawable() { 22 glGenVertexArrays(1, &drawableVAO); 23 } 24 25 26 /** 27 Binds the internal vertex array for rendering 28 */ 29 void incDrawableBindVAO() { 30 31 // Bind our vertex array 32 glBindVertexArray(drawableVAO); 33 } 34 35 bool doGenerateBounds = false; 36 } 37 38 /** 39 Sets whether Inochi2D should keep track of the bounds 40 */ 41 void inSetUpdateBounds(bool state) { 42 doGenerateBounds = state; 43 } 44 45 /** 46 Nodes that are meant to render something in to the Inochi2D scene 47 Other nodes don't have to render anything and serve mostly other 48 purposes. 49 50 The main types of Drawables are Parts and Masks 51 */ 52 53 @TypeId("Drawable") 54 abstract class Drawable : Node { 55 private: 56 void updateIndices() { 57 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); 58 glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices.length*ushort.sizeof, data.indices.ptr, GL_STATIC_DRAW); 59 } 60 61 void updateVertices() { 62 63 // Important check since the user can change this every frame 64 glBindBuffer(GL_ARRAY_BUFFER, vbo); 65 glBufferData(GL_ARRAY_BUFFER, data.vertices.length*vec2.sizeof, data.vertices.ptr, GL_DYNAMIC_DRAW); 66 67 // Zero-fill the deformation delta 68 this.deformation.length = vertices.length; 69 foreach(i; 0..deformation.length) { 70 this.deformation[i] = vec2(0, 0); 71 } 72 this.updateDeform(); 73 } 74 75 void updateDeform() { 76 77 // Important check since the user can change this every frame 78 enforce( 79 deformation.length == vertices.length, 80 "Data length mismatch, if you want to change the mesh you need to change its data with Part.rebuffer." 81 ); 82 83 glBindBuffer(GL_ARRAY_BUFFER, dbo); 84 glBufferData(GL_ARRAY_BUFFER, this.deformation.length*vec2.sizeof, this.deformation.ptr, GL_DYNAMIC_DRAW); 85 86 this.updateBounds(); 87 } 88 89 protected: 90 /** 91 OpenGL Index Buffer Object 92 */ 93 GLuint ibo; 94 95 /** 96 OpenGL Vertex Buffer Object 97 */ 98 GLuint vbo; 99 100 /** 101 OpenGL Vertex Buffer Object for deformation 102 */ 103 GLuint dbo; 104 105 /** 106 The mesh data of this part 107 108 NOTE: DO NOT MODIFY! 109 The data in here is only to be used for reference. 110 */ 111 MeshData data; 112 113 /** 114 Binds Index Buffer for rendering 115 */ 116 final void bindIndex() { 117 118 // Bind element array and draw our mesh 119 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); 120 glDrawElements(GL_TRIANGLES, cast(int)data.indices.length, GL_UNSIGNED_SHORT, null); 121 } 122 123 abstract void renderMask(bool dodge = false); 124 125 /** 126 Allows serializing self data (with pretty serializer) 127 */ 128 override 129 void serializeSelf(ref InochiSerializer serializer) { 130 super.serializeSelf(serializer); 131 serializer.putKey("mesh"); 132 serializer.serializeValue(data); 133 } 134 135 /** 136 Allows serializing self data (with compact serializer) 137 */ 138 override 139 void serializeSelf(ref InochiSerializerCompact serializer) { 140 super.serializeSelf(serializer); 141 serializer.putKey("mesh"); 142 serializer.serializeValue(data); 143 } 144 145 override 146 SerdeException deserializeFromFghj(Fghj data) { 147 import std.stdio : writeln; 148 super.deserializeFromFghj(data); 149 if (auto exc = data["mesh"].deserializeValue(this.data)) return exc; 150 151 this.vertices = this.data.vertices.dup; 152 153 // Update indices and vertices 154 this.updateIndices(); 155 this.updateVertices(); 156 return null; 157 } 158 159 public: 160 161 /** 162 Constructs a new drawable surface 163 */ 164 this(Node parent = null) { 165 super(parent); 166 167 // Generate the buffers 168 glGenBuffers(1, &vbo); 169 glGenBuffers(1, &ibo); 170 glGenBuffers(1, &dbo); 171 172 // Create deformation stack 173 this.deformStack = DeformationStack(this); 174 } 175 176 /** 177 Constructs a new drawable surface 178 */ 179 this(MeshData data, Node parent = null) { 180 this(data, inCreateUUID(), parent); 181 } 182 183 /** 184 Constructs a new drawable surface 185 */ 186 this(MeshData data, uint uuid, Node parent = null) { 187 super(uuid, parent); 188 this.data = data; 189 this.deformStack = DeformationStack(this); 190 191 // Set the deformable points to their initial position 192 this.vertices = data.vertices.dup; 193 194 // Generate the buffers 195 glGenBuffers(1, &vbo); 196 glGenBuffers(1, &ibo); 197 glGenBuffers(1, &dbo); 198 199 // Update indices and vertices 200 this.updateIndices(); 201 this.updateVertices(); 202 } 203 204 ref vec2[] vertices() { 205 return data.vertices; 206 } 207 208 /** 209 Deformation offset to apply 210 */ 211 vec2[] deformation; 212 213 /** 214 The bounds of this drawable 215 */ 216 vec4 bounds; 217 218 /** 219 Deformation stack 220 */ 221 DeformationStack deformStack; 222 223 /** 224 Refreshes the drawable, updating its vertices 225 */ 226 final void refresh() { 227 this.updateVertices(); 228 } 229 230 /** 231 Refreshes the drawable, updating its deformation deltas 232 */ 233 final void refreshDeform() { 234 this.updateDeform(); 235 } 236 237 override 238 void beginUpdate() { 239 deformStack.preUpdate(); 240 super.beginUpdate(); 241 } 242 243 /** 244 Updates the drawable 245 */ 246 override 247 void update() { 248 super.update(); 249 deformStack.update(); 250 this.updateDeform(); 251 } 252 253 /** 254 Draws the drawable 255 */ 256 override 257 void drawOne() { 258 super.drawOne(); 259 } 260 261 /** 262 Draws the drawable without any processing 263 */ 264 void drawOneDirect(bool forMasking) { } 265 266 override 267 string typeId() { return "Drawable"; } 268 269 /** 270 Updates the drawable's bounds 271 */ 272 void updateBounds() { 273 if (!doGenerateBounds) return; 274 275 // Calculate bounds 276 Transform wtransform = transform; 277 bounds = vec4(wtransform.translation.xyxy); 278 foreach(i, vertex; vertices) { 279 vec2 vertOriented = vec2(transform.matrix * vec4(vertex+deformation[i], 0, 1)); 280 if (vertOriented.x < bounds.x) bounds.x = vertOriented.x; 281 if (vertOriented.y < bounds.y) bounds.y = vertOriented.y; 282 if (vertOriented.x > bounds.z) bounds.z = vertOriented.x; 283 if (vertOriented.y > bounds.w) bounds.w = vertOriented.y; 284 } 285 } 286 287 /** 288 Draws bounds 289 */ 290 override 291 void drawBounds() { 292 if (!doGenerateBounds) return; 293 if (vertices.length == 0) return; 294 295 float width = bounds.z-bounds.x; 296 float height = bounds.w-bounds.y; 297 inDbgSetBuffer([ 298 vec3(bounds.x, bounds.y, 0), 299 vec3(bounds.x + width, bounds.y, 0), 300 301 vec3(bounds.x + width, bounds.y, 0), 302 vec3(bounds.x + width, bounds.y+height, 0), 303 304 vec3(bounds.x + width, bounds.y+height, 0), 305 vec3(bounds.x, bounds.y+height, 0), 306 307 vec3(bounds.x, bounds.y+height, 0), 308 vec3(bounds.x, bounds.y, 0), 309 ]); 310 inDbgLineWidth(3); 311 inDbgDrawLines(vec4(.5, .5, .5, 1)); 312 inDbgLineWidth(1); 313 } 314 315 /** 316 Draws line of mesh 317 */ 318 void drawMeshLines() { 319 if (vertices.length == 0) return; 320 321 auto trans = transform.matrix(); 322 ushort[] indices = data.indices; 323 324 vec3[] points = new vec3[indices.length*2]; 325 foreach(i; 0..indices.length/3) { 326 size_t ix = i*3; 327 size_t iy = ix*2; 328 auto indice = indices[ix]; 329 330 points[iy+0] = vec3(vertices[indice]-data.origin+deformation[indice], 0); 331 points[iy+1] = vec3(vertices[indices[ix+1]]-data.origin+deformation[indices[ix+1]], 0); 332 333 points[iy+2] = vec3(vertices[indices[ix+1]]-data.origin+deformation[indices[ix+1]], 0); 334 points[iy+3] = vec3(vertices[indices[ix+2]]-data.origin+deformation[indices[ix+2]], 0); 335 336 points[iy+4] = vec3(vertices[indices[ix+2]]-data.origin+deformation[indices[ix+2]], 0); 337 points[iy+5] = vec3(vertices[indice]-data.origin+deformation[indice], 0); 338 } 339 340 inDbgSetBuffer(points); 341 inDbgDrawLines(vec4(.5, .5, .5, 1), trans); 342 } 343 344 /** 345 Draws the points of the mesh 346 */ 347 void drawMeshPoints() { 348 if (vertices.length == 0) return; 349 350 auto trans = transform.matrix(); 351 vec3[] points = new vec3[vertices.length]; 352 foreach(i, point; vertices) { 353 points[i] = vec3(point-data.origin+deformation[i], 0); 354 } 355 356 inDbgSetBuffer(points); 357 inDbgPointsSize(8); 358 inDbgDrawPoints(vec4(0, 0, 0, 1), trans); 359 inDbgPointsSize(4); 360 inDbgDrawPoints(vec4(1, 1, 1, 1), trans); 361 } 362 363 /** 364 Returns the mesh data for this Part. 365 */ 366 final ref MeshData getMesh() { 367 return this.data; 368 } 369 370 /** 371 Changes this mesh's data 372 */ 373 void rebuffer(ref MeshData data) { 374 this.data = data; 375 this.updateIndices(); 376 this.updateVertices(); 377 } 378 379 /** 380 Resets the vertices of this drawable 381 */ 382 final void reset() { 383 vertices[] = data.vertices; 384 } 385 } 386 387 /** 388 Begins a mask 389 390 This causes the next draw calls until inBeginMaskContent/inBeginDodgeContent or inEndMask 391 to be written to the current mask. 392 393 This also clears whatever old mask there was. 394 */ 395 void inBeginMask(bool hasMasks) { 396 397 // Enable and clear the stencil buffer so we can write our mask to it 398 glEnable(GL_STENCIL_TEST); 399 glClearStencil(hasMasks ? 0 : 1); 400 glClear(GL_STENCIL_BUFFER_BIT); 401 } 402 403 /** 404 End masking 405 406 Once masking is ended content will no longer be masked by the defined mask. 407 */ 408 void inEndMask() { 409 410 // We're done stencil testing, disable it again so that we don't accidentally mask more stuff out 411 glStencilMask(0xFF); 412 glStencilFunc(GL_ALWAYS, 1, 0xFF); 413 glDisable(GL_STENCIL_TEST); 414 } 415 416 /** 417 Starts masking content 418 419 NOTE: This have to be run within a inBeginMask and inEndMask block! 420 */ 421 void inBeginMaskContent() { 422 glStencilFunc(GL_EQUAL, 1, 0xFF); 423 glStencilMask(0x00); 424 }