1 module inochi2d.core.puppet; 2 import inochi2d.fmt.serialize; 3 import inochi2d.core; 4 import inochi2d.math; 5 import std.algorithm.sorting; 6 import std.algorithm.mutation : SwapStrategy; 7 import std.exception; 8 import std.format; 9 import std.file; 10 import std.path : extension; 11 import std.json; 12 13 /** 14 Magic value meaning that the model has no thumbnail 15 */ 16 enum NO_THUMBNAIL = uint.max; 17 18 enum PuppetAllowedUsers { 19 /** 20 Only the author(s) are allowed to use the puppet 21 */ 22 OnlyAuthor = "onlyAuthor", 23 24 /** 25 Only licensee(s) are allowed to use the puppet 26 */ 27 OnlyLicensee = "onlyLicensee", 28 29 /** 30 Everyone may use the model 31 */ 32 Everyone = "everyone" 33 } 34 35 enum PuppetAllowedRedistribution { 36 /** 37 Redistribution is prohibited 38 */ 39 Prohibited = "prohibited", 40 41 /** 42 Redistribution is allowed, but only under 43 the same license as the original. 44 */ 45 ViralLicense = "viralLicense", 46 47 /** 48 Redistribution is allowed, and the puppet 49 may be redistributed under a different 50 license than the original. 51 52 This goes in conjunction with modification rights. 53 */ 54 CopyleftLicense = "copyleftLicense" 55 } 56 57 enum PuppetAllowedModification { 58 /** 59 Modification is prohibited 60 */ 61 Prohibited = "prohibited", 62 63 /** 64 Modification is only allowed for personal use 65 */ 66 AllowPersonal = "allowPersonal", 67 68 /** 69 Modification is allowed with redistribution, 70 see allowedRedistribution for redistribution terms. 71 */ 72 AllowRedistribute = "allowRedistribute", 73 } 74 75 class PuppetUsageRights { 76 /** 77 Who is allowed to use the puppet? 78 */ 79 @Optional 80 PuppetAllowedUsers allowedUsers = PuppetAllowedUsers.OnlyAuthor; 81 82 /** 83 Whether violence content is allowed 84 */ 85 @Optional 86 bool allowViolence = false; 87 88 /** 89 Whether sexual content is allowed 90 */ 91 @Optional 92 bool allowSexual = false; 93 94 /** 95 Whether commerical use is allowed 96 */ 97 @Optional 98 bool allowCommercial = false; 99 100 /** 101 Whether a model may be redistributed 102 */ 103 @Optional 104 PuppetAllowedRedistribution allowRedistribution = PuppetAllowedRedistribution.Prohibited; 105 106 /** 107 Whether a model may be modified 108 */ 109 @Optional 110 PuppetAllowedModification allowModification = PuppetAllowedModification.Prohibited; 111 112 /** 113 Whether the author(s) must be attributed for use. 114 */ 115 @Optional 116 bool requireAttribution = false; 117 } 118 119 /** 120 Puppet meta information 121 */ 122 class PuppetMeta { 123 124 /** 125 Name of the puppet 126 */ 127 string name; 128 /** 129 Version of the Inochi2D spec that was used for creating this model 130 */ 131 @Name("version") 132 string version_ = "1.0-alpha"; 133 134 /** 135 Rigger(s) of the puppet 136 */ 137 @Optional 138 string rigger; 139 140 /** 141 Artist(s) of the puppet 142 */ 143 @Optional 144 string artist; 145 146 /** 147 Usage Rights of the puppet 148 */ 149 @Optional 150 PuppetUsageRights rights; 151 152 /** 153 Copyright string 154 */ 155 @Optional 156 string copyright; 157 158 /** 159 URL of license 160 */ 161 @Optional 162 string licenseURL; 163 164 /** 165 Contact information of the first author 166 */ 167 @Optional 168 string contact; 169 170 /** 171 Link to the origin of this puppet 172 */ 173 @Optional 174 string reference; 175 176 /** 177 Texture ID of this puppet's thumbnail 178 */ 179 @Optional 180 uint thumbnailId = NO_THUMBNAIL; 181 182 /** 183 Whether the puppet should preserve pixel borders. 184 This feature is mainly useful for puppets which use pixel art. 185 */ 186 @Optional 187 bool preservePixels = false; 188 } 189 190 /** 191 Puppet physics settings 192 */ 193 class PuppetPhysics { 194 @Optional 195 float pixelsPerMeter = 1000; 196 197 @Optional 198 float gravity = 9.8; 199 } 200 201 /** 202 A puppet 203 */ 204 class Puppet { 205 private: 206 /** 207 An internal puppet root node 208 */ 209 @Ignore 210 Node puppetRootNode; 211 212 /** 213 A list of parts that are not masked by other parts 214 215 for Z sorting 216 */ 217 @Ignore 218 Node[] rootParts; 219 220 /** 221 A list of drivers that need to run to update the puppet 222 */ 223 Driver[] drivers; 224 225 /** 226 A list of parameters that are driven by drivers 227 */ 228 Driver[Parameter] drivenParameters; 229 230 /** 231 A dictionary of named animations 232 */ 233 Animation[string] animations; 234 235 void scanPartsRecurse(ref Node node, bool driversOnly = false) { 236 237 // Don't need to scan null nodes 238 if (node is null) return; 239 240 // Collect Drivers 241 if (Driver part = cast(Driver)node) { 242 drivers ~= part; 243 foreach(Parameter param; part.getAffectedParameters()) 244 drivenParameters[param] = part; 245 } else if (!driversOnly) { 246 // Collect drawable nodes only if we aren't inside a Composite node 247 248 if (Composite composite = cast(Composite)node) { 249 // Composite nodes handle and keep their own root node list, as such we should just draw them directly 250 composite.scanParts(); 251 rootParts ~= composite; 252 253 // For this subtree, only look for Drivers 254 driversOnly = true; 255 } else if (Part part = cast(Part)node) { 256 // Collect Part nodes 257 rootParts ~= part; 258 } 259 // Non-part nodes just need to be recursed through, 260 // they don't draw anything. 261 } 262 263 // Recurse through children nodes 264 foreach(child; node.children) { 265 scanPartsRecurse(child, driversOnly); 266 } 267 } 268 269 final void scanParts(bool reparent = false)(ref Node node) { 270 271 // We want rootParts to be cleared so that we 272 // don't draw the same part multiple times 273 // and if the node tree changed we want to reflect those changes 274 // not the old node tree. 275 rootParts = []; 276 277 // Same for drivers 278 drivers = []; 279 drivenParameters.clear(); 280 281 this.scanPartsRecurse(node); 282 283 // To make sure the GC can collect any nodes that aren't referenced 284 // anymore, we clear its children first, then assign its new child 285 // to our "new" root node. In some cases the root node will be 286 // quite different. 287 static if (reparent) { 288 if (puppetRootNode !is null) puppetRootNode.clearChildren(); 289 node.parent = puppetRootNode; 290 } 291 } 292 293 void selfSort() { 294 import std.math : cmp; 295 sort!((a, b) => cmp( 296 a.zSort, 297 b.zSort) > 0, SwapStrategy.stable)(rootParts); 298 } 299 300 Node findNode(Node n, string name) { 301 302 // Name matches! 303 if (n.name == name) return n; 304 305 // Recurse through children 306 foreach(child; n.children) { 307 if (Node c = findNode(child, name)) return c; 308 } 309 310 // Not found 311 return null; 312 } 313 314 Node findNode(Node n, uint uuid) { 315 316 // Name matches! 317 if (n.uuid == uuid) return n; 318 319 // Recurse through children 320 foreach(child; n.children) { 321 if (Node c = findNode(child, uuid)) return c; 322 } 323 324 // Not found 325 return null; 326 } 327 328 public: 329 /** 330 Meta information about this puppet 331 */ 332 @Name("meta") 333 PuppetMeta meta; 334 335 /** 336 Global physics settings for this puppet 337 */ 338 @Name("physics") 339 PuppetPhysics physics; 340 341 /** 342 The root node of the puppet 343 */ 344 @Name("nodes", "Root Node") 345 Node root; 346 347 /** 348 Parameters 349 */ 350 @Name("param", "Parameters") 351 @Optional 352 Parameter[] parameters; 353 354 /** 355 Parameters 356 */ 357 @Name("automation", "Automation") 358 @Optional 359 Automation[] automation; 360 361 /** 362 INP Texture slots for this puppet 363 */ 364 @Ignore 365 Texture[] textureSlots; 366 367 /** 368 Extended vendor data 369 */ 370 @Ignore 371 ubyte[][string] extData; 372 373 /** 374 Whether parameters should be rendered 375 */ 376 @Ignore 377 bool renderParameters = true; 378 379 /** 380 Whether drivers should run 381 */ 382 @Ignore 383 bool enableDrivers = true; 384 385 /** 386 Puppet render transform 387 388 This transform does not affect physics 389 */ 390 Transform transform; 391 392 /** 393 Creates a new puppet from nothing () 394 */ 395 this() { 396 this.puppetRootNode = new Node(this); 397 this.meta = new PuppetMeta(); 398 this.physics = new PuppetPhysics(); 399 root = new Node(this.puppetRootNode); 400 root.name = "Root"; 401 transform = Transform(vec3(0, 0, 0)); 402 } 403 404 /** 405 Creates a new puppet from a node tree 406 */ 407 this(Node root) { 408 this.meta = new PuppetMeta(); 409 this.physics = new PuppetPhysics(); 410 this.root = root; 411 this.puppetRootNode = new Node(this); 412 this.root.name = "Root"; 413 this.scanParts!true(this.root); 414 transform = Transform(vec3(0, 0, 0)); 415 this.selfSort(); 416 } 417 418 /** 419 Updates the nodes 420 */ 421 final void update() { 422 transform.update(); 423 424 // Update Automators 425 foreach(auto_; automation) { 426 auto_.update(); 427 } 428 429 root.beginUpdate(); 430 431 if (renderParameters) { 432 433 // Update parameters 434 foreach(parameter; parameters) { 435 436 if (!enableDrivers || parameter !in drivenParameters) 437 parameter.update(); 438 } 439 } 440 441 // Ensure the transform tree is updated 442 root.transformChanged(); 443 444 if (renderParameters && enableDrivers) { 445 // Update parameter/node driver nodes (e.g. physics) 446 foreach(driver; drivers) { 447 driver.updateDriver(); 448 } 449 } 450 451 // Update nodes 452 root.update(); 453 } 454 455 /** 456 Reset drivers/physics nodes 457 */ 458 final void resetDrivers() { 459 foreach(driver; drivers) { 460 driver.reset(); 461 } 462 463 // Update so that the timestep gets reset. 464 import inochi2d : inUpdate; 465 inUpdate(); 466 } 467 468 /** 469 Returns the index of a parameter by name 470 */ 471 ptrdiff_t findParameterIndex(string name) { 472 foreach(i, parameter; parameters) { 473 if (parameter.name == name) { 474 return i; 475 } 476 } 477 return -1; 478 } 479 480 /** 481 Returns a parameter by UUID 482 */ 483 Parameter findParameter(uint uuid) { 484 foreach(i, parameter; parameters) { 485 if (parameter.uuid == uuid) { 486 return parameter; 487 } 488 } 489 return null; 490 } 491 492 /** 493 Gets if a node is bound to ANY parameter. 494 */ 495 bool getIsNodeBound(Node n) { 496 foreach(i, parameter; parameters) { 497 if (parameter.hasAnyBinding(n)) return true; 498 } 499 return false; 500 } 501 502 /** 503 Draws the puppet 504 */ 505 final void draw() { 506 this.selfSort(); 507 508 foreach(rootPart; rootParts) { 509 if (!rootPart.renderEnabled) continue; 510 rootPart.drawOne(); 511 } 512 } 513 514 /** 515 Removes a parameter from this puppet 516 */ 517 void removeParameter(Parameter param) { 518 import std.algorithm.searching : countUntil; 519 import std.algorithm.mutation : remove; 520 ptrdiff_t idx = parameters.countUntil(param); 521 if (idx >= 0) { 522 parameters = parameters.remove(idx); 523 } 524 } 525 526 /** 527 Rescans the puppet's nodes 528 529 Run this every time you change the layout of the puppet's node tree 530 */ 531 final void rescanNodes() { 532 this.scanParts!false(root); 533 } 534 535 /** 536 Updates the texture state for all texture slots. 537 */ 538 final void updateTextureState() { 539 540 // Update filtering mode for texture slots 541 foreach(texutre; textureSlots) { 542 texutre.setFiltering(meta.preservePixels ? Filtering.Point : Filtering.Linear); 543 } 544 } 545 546 /** 547 Finds Node by its name 548 */ 549 T find(T = Node)(string name) if (is(T : Node)) { 550 return cast(T)findNode(root, name); 551 } 552 553 /** 554 Finds Node by its unique id 555 */ 556 T find(T = Node)(uint uuid) if (is(T : Node)) { 557 return cast(T)findNode(root, uuid); 558 } 559 560 /** 561 Returns all the parts in the puppet 562 */ 563 Part[] getAllParts() { 564 return findNodesType!Part(root); 565 } 566 567 /** 568 Finds nodes based on their type 569 */ 570 final T[] findNodesType(T)(Node n) if (is(T : Node)) { 571 T[] nodes; 572 573 if (T item = cast(T)n) { 574 nodes ~= item; 575 } 576 577 // Recurse through children 578 foreach(child; n.children) { 579 nodes ~= findNodesType!T(child); 580 } 581 582 return nodes; 583 } 584 585 /** 586 Adds a texture to a new slot if it doesn't already exist within this puppet 587 */ 588 final uint addTextureToSlot(Texture texture) { 589 import std.algorithm.searching : canFind; 590 591 // Add texture if we can't find it. 592 if (!textureSlots.canFind(texture)) textureSlots ~= texture; 593 return cast(uint)textureSlots.length-1; 594 } 595 596 /** 597 Populate texture slots with all visible textures in the model 598 */ 599 final void populateTextureSlots() { 600 if (textureSlots.length > 0) textureSlots.length = 0; 601 602 foreach(part; getAllParts) { 603 foreach(texture; part.textures) { 604 if (texture) this.addTextureToSlot(texture); 605 } 606 } 607 } 608 609 /** 610 Finds a texture by its runtime UUID 611 */ 612 final Texture findTextureByRuntimeUUID(uint uuid) { 613 foreach(ref slot; textureSlots) { 614 if (slot.getRuntimeUUID()) 615 return slot; 616 } 617 return null; 618 } 619 620 /** 621 Sets thumbnail of this puppet 622 */ 623 final void setThumbnail(Texture texture) { 624 if (this.meta.thumbnailId == NO_THUMBNAIL) { 625 this.meta.thumbnailId = this.addTextureToSlot(texture); 626 } else { 627 textureSlots[this.meta.thumbnailId] = texture; 628 } 629 } 630 631 /** 632 Gets the texture slot index for a texture 633 634 returns -1 if none was found 635 */ 636 final ptrdiff_t getTextureSlotIndexFor(Texture texture) { 637 import std.algorithm.searching : countUntil; 638 return textureSlots.countUntil(texture); 639 } 640 641 /** 642 Clears this puppet's thumbnail 643 644 By default it does not delete the texture assigned, pass in true to delete texture 645 */ 646 final void clearThumbnail(bool deleteTexture = false) { 647 import std.algorithm.mutation : remove; 648 if (deleteTexture) textureSlots = remove(textureSlots, this.meta.thumbnailId); 649 this.meta.thumbnailId = NO_THUMBNAIL; 650 } 651 652 /** 653 This cursed toString implementation outputs the puppet's 654 nodetree as a pretty printed tree. 655 656 Please use a graphical viewer instead of this if you can, 657 eg. Inochi Creator. 658 */ 659 override 660 string toString() { 661 import std.format : format; 662 import std.range : repeat, takeExactly; 663 import std.array : array; 664 bool[] lineSet; 665 666 string toStringBranch(Node n, int indent, bool showLines = true) { 667 668 lineSet ~= n.children.length > 0; 669 string getLineSet() { 670 if (indent == 0) return ""; 671 string s = ""; 672 foreach(i; 1..lineSet.length) { 673 s ~= lineSet[i-1] ? "│ " : " "; 674 } 675 return s; 676 } 677 678 string iden = getLineSet(); 679 680 string s = "%s[%s] %s <%s>\n".format(n.children.length > 0 ? "╭─" : "", n.typeId, n.name, n.uuid); 681 foreach(i, child; n.children) { 682 string term = "├→"; 683 if (i == n.children.length-1) { 684 term = "╰→"; 685 lineSet[indent] = false; 686 } 687 s ~= "%s%s%s".format(iden, term, toStringBranch(child, indent+1)); 688 } 689 690 lineSet.length--; 691 692 return s; 693 } 694 695 return toStringBranch(root, 0); 696 } 697 698 699 void serializeSelf(ref InochiSerializer serializer) { 700 serializer.putKey("meta"); 701 serializer.serializeValue(meta); 702 serializer.putKey("physics"); 703 serializer.serializeValue(physics); 704 serializer.putKey("nodes"); 705 serializer.serializeValue(root); 706 serializer.putKey("param"); 707 serializer.serializeValue(parameters); 708 serializer.putKey("automation"); 709 serializer.serializeValue(automation); 710 serializer.putKey("animations"); 711 serializer.serializeValue(animations); 712 } 713 714 /** 715 Serializes a puppet 716 */ 717 void serialize(ref InochiSerializer serializer) { 718 auto state = serializer.objectBegin; 719 serializeSelf(serializer); 720 serializer.objectEnd(state); 721 } 722 723 /** 724 Deserializes a puppet 725 */ 726 SerdeException deserializeFromFghj(Fghj data) { 727 if (auto exc = data["meta"].deserializeValue(this.meta)) return exc; 728 if (!data["physics"].isEmpty) 729 if (auto exc = data["physics"].deserializeValue(this.physics)) return exc; 730 if (auto exc = data["nodes"].deserializeValue(this.root)) return exc; 731 732 // Allow parameter loading to be overridden (for Inochi Creator) 733 foreach(key; data["param"].byElement) { 734 this.parameters ~= inParameterCreate(key); 735 } 736 737 // Deserialize automation 738 foreach(key; data["automation"].byElement) { 739 string type; 740 if (auto exc = key["type"].deserializeValue(type)) return exc; 741 742 if (inHasAutomationType(type)) { 743 auto auto_ = inInstantiateAutomation(type, this); 744 auto_.deserializeFromFghj(key); 745 this.automation ~= auto_; 746 } 747 } 748 if (!data["animations"].isEmpty) data["animations"].deserializeValue(animations); 749 this.finalizeDeserialization(data); 750 751 return null; 752 } 753 754 755 void reconstruct() { 756 this.root.reconstruct(); 757 foreach(parameter; parameters.dup) { 758 parameter.reconstruct(this); 759 } 760 foreach(automation_; automation.dup) { 761 automation_.reconstruct(this); 762 } 763 foreach(ref animation; animations.dup) { 764 animation.reconstruct(this); 765 } 766 } 767 768 void finalize() { 769 this.root.setPuppet(this); 770 this.root.name = "Root"; 771 this.puppetRootNode = new Node(this); 772 773 // Finally update link etc. 774 this.root.finalize(); 775 foreach(parameter; parameters) { 776 parameter.finalize(this); 777 } 778 foreach(automation_; automation) { 779 automation_.finalize(this); 780 } 781 foreach(ref animation; animations) { 782 animation.finalize(this); 783 } 784 this.scanParts!true(this.root); 785 this.selfSort(); 786 } 787 /** 788 Finalizer 789 */ 790 void finalizeDeserialization(Fghj data) { 791 // reconstruct object path so that object is located at final position 792 reconstruct(); 793 finalize(); 794 } 795 796 /** 797 Gets the internal root parts array 798 799 Do note that some root parts may be Composites instead. 800 */ 801 final 802 ref Node[] getRootParts() { 803 return rootParts; 804 } 805 806 /** 807 Gets a list of drivers 808 */ 809 final 810 ref Driver[] getDrivers() { 811 return drivers; 812 } 813 814 /** 815 Gets a mapping from parameters to their drivers 816 */ 817 final 818 ref Driver[Parameter] getParameterDrivers() { 819 return drivenParameters; 820 } 821 822 /** 823 Gets the animation dictionary 824 */ 825 final 826 ref Animation[string] getAnimations() { 827 return animations; 828 } 829 830 void applyDeformToChildren() { 831 auto nodes = findNodesType!MeshGroup(root); 832 foreach (node; nodes) { 833 node.applyDeformToChildren(parameters); 834 } 835 } 836 837 /** 838 Gets the combined bounds of the puppet 839 */ 840 vec4 getCombinedBounds(bool reupdate=false)() { 841 return root.getCombinedBounds!(reupdate, true); 842 } 843 }