1 /++ 2 Add-on module for loading 2D puppets and displaying them in the game. 3 <$(HTTP https://github.com/Inochi2D/inochi2d/wiki, INP Format information) 4 5 Currently supported: 6 - Loading .inp format including JSON data and textures. 7 - Displays nodes of type Part (the rest are ignored) 8 - Reading the transformation of the node, identifier, type, Mesh data, transparency. 9 - Setting the 'part' texture. 10 - Saving to an .inp file. 11 - Sorting zoffset. 12 13 Not implemented: 14 - Mask mod. 15 - Path Deform. 16 - JointBindingData. 17 18 There are some modifications that are not described in the reference: 19 - It is not necessary to specify the index data. 20 21 For rendering, you need a context. The format is loaded in the following way: 22 --- 23 Puppet puppet = new Puppet(); 24 puppet.load("data.inp"); // or puppet.loadFromMem(...); 25 --- 26 27 The function `render.draw` is used for displaying, since this is an IDrawable object: 28 --- 29 render.draw(puppet, Vecf(32, 32)); 30 --- 31 32 Authors implementations for Tida: $(HTTP https://github.com/TodNaz, TodNaz) 33 Authors INP format: $(HTTP https://github.com/LunaTheFoxgirl, LunaTheFoxgirl) 34 License: $(HTTP https://opensource.org/licenses/MIT, MIT) 35 +/ 36 module tida.puppet; 37 38 import std.json; 39 40 import tida.gl; 41 import tida.drawable; 42 import tida.vertgen; 43 import tida.matrix; 44 import tida.render; 45 import tida.image; 46 import tida.texture; 47 import tida.shader; 48 import tida.vector; 49 import tida.angle; 50 import tida.shape; 51 import tida.color; 52 53 private bool isKeyExists(JSONValue json, string key) @trusted { 54 try { 55 auto item = json[key]; 56 return true; 57 }catch(Exception e) { 58 return false; 59 } 60 } 61 62 alias UUID = int; 63 64 enum MaskMode : string{ 65 Mask = "Mask", DodgeMask = "DodgeMask" 66 } 67 68 /// Node type 69 enum PuppetNodeType : string { 70 Node = "Node", 71 Drawable = "Drawable", 72 PathDeform = "PathDeform", 73 Part = "Part", 74 Mask = "Mask" 75 } 76 77 /// Transformation structure for the matrix. 78 struct Transform 79 { 80 public 81 { 82 float[3] trans; /// Translate vector 83 bool[3] transLock; /// Translate lock 84 float[3] rot; /// Rotate vector 85 bool[3] rotLock; /// Rotate lock 86 Vecf scaling; /// Scale vector 87 bool[2] scaleLock; // Scale lock 88 } 89 90 void parseJSON(JSONValue json) @trusted 91 { 92 trans[0] = json["trans"][0].get!float; 93 trans[1] = json["trans"][1].get!float; 94 trans[2] = json["trans"][2].get!float; 95 96 if(json.isKeyExists("trans_lock")) 97 { 98 transLock[0] = json["trans_lock"][0].get!bool; 99 transLock[1] = json["trans_lock"][1].get!bool; 100 transLock[2] = json["trans_lock"][2].get!bool; 101 } 102 103 rot[0] = json["rot"][0].get!float; 104 rot[1] = json["rot"][1].get!float; 105 rot[2] = json["rot"][2].get!float; 106 107 if(json.isKeyExists("rot_lock")) 108 { 109 rotLock[0] = json["rot_lock"][0].get!bool; 110 rotLock[1] = json["rot_lock"][1].get!bool; 111 rotLock[2] = json["rot_lock"][2].get!bool; 112 } 113 114 scaling.x = json["scale"][0].get!float; 115 scaling.y = json["scale"][1].get!float; 116 117 if(json.isKeyExists("scale_lock")) 118 { 119 scaleLock[0] = json["scale_lock"][0].get!bool; 120 scaleLock[1] = json["scale_lock"][1].get!bool; 121 } 122 } 123 124 JSONValue toJSON() @trusted 125 { 126 JSONValue json; 127 128 json["trans"] = JSONValue(trans); 129 if(json.isKeyExists("trans_lock")) json["trans_lock"] = JSONValue(transLock); 130 json["rot"] = JSONValue(rot); 131 if(json.isKeyExists("rot_lock")) json["rot_lock"] = JSONValue(rotLock); 132 json["scale"] = JSONValue(scaling.array); 133 if(json.isKeyExists("scale_lock")) json["scale_lock"] = JSONValue(scaleLock); 134 135 return json; 136 } 137 138 /// Returns the model of the matrix by the parameters of the structure. 139 float[4][4] modelMatrix() @safe @property nothrow pure 140 { 141 float[4][4] mat = identity(); 142 143 mat = translate(mat, this.transLock[0] ? 0.0 : trans[0], 144 this.transLock[1] ? 0.0 : trans[1], 145 this.transLock[2] ? 0.0 : trans[2]); 146 mat = eulerRotate(mat, this.rotLock[0] ? 0.0 : -rot[0], 147 this.rotLock[1] ? 0.0 : -rot[1], 148 this.rotLock[2] ? 0.0 : -rot[2]); 149 mat = scale(mat, this.scaleLock[0] ? 1.0 : scaling.x, 150 this.scaleLock[1] ? 1.0 : scaling.y); 151 152 return mat; 153 } 154 } 155 156 /// Information about the puppet. 157 struct PuppetMeta 158 { 159 public 160 { 161 string name; /// 162 string ver = "v1.0.0-alpha"; /// 163 string[] authors; /// 164 string copyright; /// 165 string contact; /// Mail / address / website. 166 string reference = "https://github.com/Inochi2D/inochi2d/"; /// 167 uint thumbnailID; /// Puppet thumbnail. 168 } 169 170 void parseJSON(JSONValue json) @trusted 171 { 172 name = json["name"].str; 173 ver = json["version"].str; 174 if(json.isKeyExists("thumbnail_id")) thumbnailID = cast(uint) json["thumbnail_id"].integer; 175 } 176 177 JSONValue toJSON() @trusted 178 { 179 JSONValue json; 180 181 json["name"] = JSONValue(name); 182 json["version"] = JSONValue(ver); 183 json["thumbnail_id"] = JSONValue(thumbnailID); 184 185 return json; 186 } 187 } 188 189 class PuppetNode : IDrawable 190 { 191 import std.random; 192 193 public 194 { 195 PuppetNodeType type; /// 196 UUID uuid; /// identificator 197 string name = "Unnamed"; /// 198 bool enabled = true; /// Do I need to display the node. 199 float zsort = 0.0; /// zsort value 200 Transform transform; /// 201 PuppetNode[] children; //// 202 203 float[4][4] transformMatrix; 204 PuppetNode parent; 205 } 206 207 protected 208 { 209 Puppet puppet; 210 } 211 212 float zSort() @safe @property { 213 return parent !is null ? parent.zSort + zsort : zsort; 214 } 215 216 this(PuppetNode parent = null) @safe { 217 type = PuppetNodeType.Node; 218 uuid = uniform!UUID; 219 this.parent = parent; 220 } 221 222 this(Puppet puppet, PuppetNode parent = null) @safe { 223 this.puppet = puppet; 224 type = PuppetNodeType.Node; 225 uuid = uniform!UUID; 226 this.parent = parent; 227 } 228 229 void parseJSON(JSONValue json, Puppet puppet) @trusted 230 { 231 this.puppet = puppet; 232 233 uuid = cast(UUID) json["uuid"].integer; 234 if(json.isKeyExists("name")) name = json["name"].str; 235 enabled = json["enabled"].get!bool; 236 zsort = json["zsort"].get!float; 237 transform.parseJSON(json["transform"]); 238 239 if(parent is null) 240 transformMatrix = transform.modelMatrix(); 241 else 242 transformMatrix = mulmat(transform.modelMatrix(), parent.transformMatrix); 243 244 if(json.isKeyExists("children")) 245 foreach(e; json["children"].array) { 246 PuppetNode node = jsonToNode(e, puppet, this); 247 children ~= node; 248 } 249 } 250 251 JSONValue toJSON() @trusted 252 { 253 JSONValue json; 254 255 json["type"] = JSONValue(cast(string) type); 256 json["uuid"] = JSONValue(uuid); 257 if(name != []) json["name"] = JSONValue(name); 258 json["enabled"] = JSONValue(enabled); 259 json["transform"] = transform.toJSON(); 260 json["zsort"] = JSONValue(zsort); 261 262 json["children"] = [null]; 263 264 foreach(e; children) { 265 json["children"].array ~= e.toJSON(); 266 } 267 268 json["children"].array = json["children"].array[1 .. $]; 269 270 return json; 271 } 272 273 override void draw(IRenderer render, Vecf position) @safe 274 { 275 if(!enabled) return; 276 } 277 } 278 279 PuppetNode jsonToNode(JSONValue json, Puppet puppet, PuppetNode parent = null) @trusted 280 { 281 PuppetNodeType type = cast(PuppetNodeType) json["type"].str; 282 283 switch(type) 284 { 285 case PuppetNodeType.Node: 286 PuppetNode node = new PuppetNode(parent); 287 node.type = type; 288 node.parseJSON(json, puppet); 289 290 return node; 291 292 case PuppetNodeType.Drawable: 293 goto default; 294 295 case PuppetNodeType.PathDeform: 296 PuppetPathDeform node = new PuppetPathDeform(parent); 297 node.type = type; 298 node.parseJSON(json, puppet); 299 300 return node; 301 302 case PuppetNodeType.Part: 303 PuppetPart node = new PuppetPart(parent); 304 node.type = type; 305 node.parseJSON(json, puppet); 306 307 return node; 308 309 case PuppetNodeType.Mask: 310 PuppetMask node = new PuppetMask(parent); 311 node.type = type; 312 node.parseJSON(json, puppet); 313 314 return node; 315 316 default: 317 return null; 318 } 319 } 320 321 /// Geometry information. 322 struct MeshData 323 { 324 public 325 { 326 float[] vertsOld; 327 float[] verts; /// 328 float[] uvs; /// 329 uint[] indices; // is not a ushort 330 } 331 332 private 333 { 334 bool textureCoordinatePut = false; 335 } 336 337 void parseJSON(JSONValue json) @trusted 338 { 339 foreach(e; json["verts"].array) 340 verts ~= e.get!float; 341 342 if(json.isKeyExists("uvs")) 343 foreach(e; json["uvs"].array) 344 uvs ~= e.get!float; 345 346 if(json.isKeyExists("indices")) 347 foreach(e; json["indices"].array) 348 indices ~= cast(uint) e.integer; 349 350 this.textured(); 351 } 352 353 void textured() @safe { 354 vertsOld = verts.dup; 355 356 Vecf[] vertxs; 357 for(int i = 0; i < verts.length; i += 2) { 358 vertxs ~= Vecf(verts[i], verts[i + 1]); 359 } 360 361 auto vertDump = vertxs; 362 vertxs.length = 0; 363 364 if(uvs.length == 0) 365 { 366 const clip = rectVertexs(vertDump); 367 368 foreach(e; vertDump) { 369 vertxs ~= [e, 370 Vecf((e.x - clip.x) / clip.width, 371 (e.y - clip.y) / clip.height)]; 372 } 373 374 verts = vertxs.generateArray; 375 }else 376 { 377 Vecf[] uvvs; 378 for(int i = 0; i < uvs.length; i += 2) { 379 uvvs ~= Vecf(uvs[i], uvs[i + 1]); 380 } 381 382 foreach(i; 0 .. vertDump.length) { 383 vertxs ~= [vertDump[i], uvvs[i]]; 384 } 385 386 verts = vertxs.generateArray; 387 } 388 389 textureCoordinatePut = true; 390 } 391 392 JSONValue toJSON() @trusted 393 { 394 JSONValue json; 395 396 json["verts"] = textureCoordinatePut ? JSONValue(vertsOld) : JSONValue(verts); 397 json["uvs"] = JSONValue(uvs); 398 json["indices"] = JSONValue(indices); 399 400 return json; 401 } 402 } 403 404 abstract class PuppetDrawable : PuppetNode 405 { 406 public 407 { 408 MeshData mesh; /// 409 410 VertexInfo!float vertInfo; /// 411 } 412 413 override void parseJSON(JSONValue json, Puppet puppet) @trusted 414 { 415 super.parseJSON(json, puppet); 416 417 mesh.parseJSON(json["mesh"]); 418 generateVertexInfo(); 419 } 420 421 override JSONValue toJSON() @trusted 422 { 423 auto json = super.toJSON(); 424 425 json["mesh"] = mesh.toJSON(); 426 427 return json; 428 } 429 430 void generateVertexInfo() @safe 431 { 432 vertInfo = new VertexInfo!float(); 433 434 if(mesh.indices != []) { 435 vertInfo.bindFromBufferAndElem(mesh.verts, mesh.indices); 436 } else { 437 vertInfo.bindFromBuffer(mesh.verts); 438 } 439 } 440 } 441 442 /// Texture display node 443 class PuppetPart : PuppetDrawable 444 { 445 import std.random; 446 447 public 448 { 449 uint[] textures; /// 450 float opacity; /// 451 MaskMode maskMode; /// 452 float maskThreshold; /// 453 UUID[] maskedBy; /// 454 } 455 456 this(PuppetNode parent = null) @safe { 457 type = PuppetNodeType.Part; 458 uuid = uniform!uint; 459 this.parent = parent; 460 } 461 462 this(Puppet puppet, PuppetNode parent = null) @safe 463 { 464 type = PuppetNodeType.Part; 465 this.puppet = puppet; 466 uuid = uniform!uint; 467 this.parent = parent; 468 } 469 470 override void parseJSON(JSONValue json, Puppet puppet) @trusted 471 { 472 super.parseJSON(json, puppet); 473 474 foreach(e; json["textures"].array) 475 textures ~= cast(uint) e.integer; 476 477 opacity = json["opacity"].get!float; 478 maskMode = cast(MaskMode) json["mask_mode"].str; 479 maskThreshold = json["mask_threshold"].get!float; 480 481 if(json.isKeyExists("masked_by")) 482 foreach(e; json["masked_by"].array) 483 maskedBy ~= cast(UUID) e.integer; 484 } 485 486 override JSONValue toJSON() @trusted 487 { 488 auto json = super.toJSON(); 489 490 json["textures"] = JSONValue(textures); 491 json["opacity"] = JSONValue(opacity); 492 json["mask_mode"] = JSONValue(maskMode); 493 json["mask_threshold"] = JSONValue(maskThreshold); 494 json["masked_by"] = JSONValue(maskedBy); 495 496 return json; 497 } 498 499 void drawTexture(IRenderer render, Texture texture, Vecf position) @trusted 500 { 501 Shader!Program shader = texture.initShader(render); 502 VertexInfo!float vinfo = vertInfo; 503 504 vinfo.bindVertexArray(); 505 glBindBuffer(GL_ARRAY_BUFFER, vinfo.idBufferArray); 506 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vinfo.idElementArray); 507 508 glEnableVertexAttribArray(shader.getAttribLocation("position")); 509 glVertexAttribPointer(shader.getAttribLocation("position"), 2, GL_FLOAT, false, 4 * float.sizeof, null); 510 511 glEnableVertexAttribArray(shader.getAttribLocation("aTexCoord")); 512 glVertexAttribPointer(shader.getAttribLocation("aTexCoord"), 2, GL_FLOAT, false, 4 * float.sizeof, 513 cast(void*) (2 * float.sizeof)); 514 515 float[4][4] proj = (cast(GLRender) render).projection(); 516 float[4][4] model = transformMatrix; 517 518 model = translate(model, position.x, position.y, 0.0f); 519 520 shader.using(); 521 522 glActiveTexture(GL_TEXTURE0); 523 texture.bind(); 524 525 if(shader.getUniformLocation("projection") != -1) 526 shader.setUniform("projection", proj); 527 528 if(shader.getUniformLocation("model") != -1) 529 shader.setUniform("model", model); 530 531 if(shader.getUniformLocation("color") != -1) 532 shader.setUniform("color", rgba(255, 255, 255, cast(ubyte) (ubyte.max * opacity))); 533 534 if(vinfo.elementLength != 0) 535 glDrawElements(GL_TRIANGLES, cast(uint) vinfo.elementLength, GL_UNSIGNED_INT, null); 536 else 537 glDrawArrays(GL_TRIANGLES, 0, cast(uint) vinfo.length / 4); 538 539 glBindBuffer(GL_ARRAY_BUFFER, 0); 540 glBindVertexArray(0); 541 glBindTexture(GL_TEXTURE_2D, 0); 542 543 render.resetShader(); 544 } 545 546 override void draw(IRenderer render, Vecf position) @safe { 547 foreach(e; textures) { 548 drawTexture(render, puppet.dataTexture[e].texture, position); 549 } 550 } 551 } 552 553 /// 554 class PuppetMask : PuppetDrawable 555 { 556 this(PuppetNode parent = null) @safe 557 { 558 this.parent = parent; 559 } 560 561 this(Puppet puppet, PuppetNode parent) @safe 562 { 563 this.parent = parent; 564 this.puppet = puppet; 565 } 566 } 567 568 /// 569 struct PuppetJointBindingData 570 { 571 public 572 { 573 UUID boundTo; 574 size_t[][] bindData; 575 } 576 } 577 578 /// 579 class PuppetPathDeform : PuppetNode 580 { 581 public 582 { 583 Vecf[] joints; 584 PuppetJointBindingData[] bindings; 585 } 586 587 this(PuppetNode parent = null) @safe 588 { 589 this.parent = parent; 590 } 591 592 this(Puppet puppet, PuppetNode parent) @safe 593 { 594 this.parent = parent; 595 this.puppet = puppet; 596 } 597 } 598 599 private T byteTo(T)(ubyte[] bytes) @trusted 600 { 601 T data = T.init; 602 data = bytes[0] << 24 | 603 bytes[1] << 16 | 604 bytes[2] << 8 | 605 bytes[3]; 606 607 return data; 608 } 609 610 611 private ubyte[4] toByte(T)(T value) @trusted 612 { 613 ubyte[4] ab; 614 for (int i = 0; i < 4; i++) 615 ab[3 - i] = cast(ubyte) (value >> (i * 8)); 616 617 return ab; 618 } 619 620 /// 621 class Puppet : IDrawable 622 { 623 import std.file : read; 624 import imagefmt; 625 626 public 627 { 628 PuppetMeta meta; /// Puppet information 629 PuppetNode node; /// 630 Image[] dataTexture; /// 631 } 632 633 protected 634 { 635 PuppetNode[] nodesDraw; 636 } 637 638 /++ 639 Loads a puppet from a mem. 640 641 Params: 642 data = Raw data. 643 644 Throws: 'Exception' if the file header is not found / damaged, 645 as well as if there is no image data. If you only need to 646 load JSON data, then just use the 'parseJSON' function. 647 +/ 648 void loadFromMem(ubyte[] data) @trusted 649 { 650 import std.stdio; 651 652 struct TextureBlob { 653 ubyte encode; 654 ubyte[] data; 655 } 656 657 TextureBlob[] blobs; 658 659 if(data[0 .. 8] != ['T','R','N','S','R','T','S','\0']) 660 throw new Exception("It's a not a INP file!"); 661 662 immutable len = byteTo!int(data[8 .. 8 + 4]); 663 string jsonString = cast(string) data[8 + 4 .. 8 + 4 + len]; 664 665 if(data[len + 8 + 4 .. len + 8 + 4 + 8] != ['T','E','X','_','S','E','C','T']) 666 throw new Exception("Not find \"TEX_SECT\"!"); 667 668 int offset = len + 16 + 4; 669 immutable texcount = byteTo!int(data[offset .. offset += 4]); 670 671 foreach(_; 0 .. texcount) { 672 immutable texLeng = byteTo!int(data[offset .. offset += 4]); 673 immutable texType = data[offset++]; 674 blobs ~= TextureBlob(texType, data[offset .. offset += texLeng]); 675 } 676 677 Image[] images; 678 foreach(e; blobs) { 679 Image image = new Image(); 680 681 IFImage temp = read_image(e.data, 4); 682 scope(exit) temp.free(); 683 assert(temp.e == 0, IF_ERROR[temp.e]); 684 685 image.allocatePlace(temp.w,temp.h); 686 image.bytes!(PixelFormat.RGBA)(temp.buf8); 687 688 image.toTexture(); 689 690 dataTexture ~= image; 691 } 692 693 this.parseJSON(jsonString.parseJSON); 694 } 695 696 /++ 697 Loads a puppet from a file. 698 699 Params: 700 file = path to the file. 701 702 Throws: 'Exception' if the file header is not found / damaged, 703 as well as if there is no image data. If you only need to 704 load JSON data, then just use the 'parseJSON' function. 705 +/ 706 void load(string file) @trusted { 707 loadFromMem(cast(ubyte[]) read(file)); 708 } 709 710 /// 711 ubyte[] toRaw() @trusted 712 { 713 ubyte[] data = cast(ubyte[]) "TRNSRTS\0"; 714 715 string jsonString = this.toJSON().toString(); 716 data ~= toByte!int(cast(int) jsonString.length); 717 data ~= cast(ubyte[]) jsonString; 718 719 data ~= cast(ubyte[]) "TEX_SECT"; 720 data ~= toByte!int(cast(int) dataTexture.length); 721 722 foreach(e; dataTexture) { 723 int err; 724 ubyte[] textData = write_image_mem(4, e.width, e.height, e.bytes!(PixelFormat.RGBA), 2, err); 725 data ~= toByte!int(cast(int) textData.length); 726 data ~= 0; 727 data ~= textData; 728 } 729 730 return data; 731 } 732 733 void scan(PuppetNode node) @safe { 734 if(node is null) return; 735 if(!node.enabled) return; 736 737 if(node.type == PuppetNodeType.Part) this.nodesDraw ~= node; 738 foreach(e; node.children) 739 scan(e); 740 } 741 742 void sortNodeDraw() @safe { 743 import std.algorithm : sort; 744 745 sort!((a, b) => a.zSort > b.zSort)(nodesDraw); 746 } 747 748 void update() @safe 749 { 750 scan(node); 751 sortNodeDraw(); 752 } 753 754 /// 755 void parseJSON(JSONValue json) @trusted { 756 meta.parseJSON(json["meta"]); 757 node = jsonToNode(json["nodes"], this); 758 759 update(); 760 } 761 762 string nodesDrawsToString() @trusted { 763 string str = "["; 764 foreach(PuppetNode e; nodesDraw) { 765 str ~= e.toJSON().toString() ~ ","; 766 } 767 return str[0 .. $ - 1] ~ "]"; 768 } 769 770 /// 771 JSONValue toJSON() @trusted { 772 JSONValue json; 773 json["meta"] = meta.toJSON(); 774 json["nodes"] = node.toJSON(); 775 776 return json; 777 } 778 779 override void draw(IRenderer render, Vecf position) @safe 780 { 781 foreach(e; nodesDraw) e.draw(render, position); 782 } 783 } 784 785 /// Find a node by name. 786 PuppetNode nodeByName(PuppetNode node, string name) @trusted { 787 if(node.name == name) return node; 788 789 foreach(e; node.children) if(nodeByName(e, name) !is null) return e; 790 791 return null; 792 } 793 794 /// Find a node by identificator. 795 PuppetNode nodeByUUID(PuppetNode node, UUID uuid) @trusted { 796 if(node.uuid == uuid) return node; 797 798 foreach(e; node.children) if(nodeByUUID(e, uuid) !is null) return e; 799 800 return null; 801 }