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