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 void scanPartsRecurse(ref Node node, bool driversOnly = false) { 231 232 // Don't need to scan null nodes 233 if (node is null) return; 234 235 // Collect Drivers 236 if (Driver part = cast(Driver)node) { 237 drivers ~= part; 238 foreach(Parameter param; part.getAffectedParameters()) 239 drivenParameters[param] = part; 240 } else if (!driversOnly) { 241 // Collect drawable nodes only if we aren't inside a Composite node 242 243 if (Composite composite = cast(Composite)node) { 244 // Composite nodes handle and keep their own root node list, as such we should just draw them directly 245 composite.scanParts(); 246 rootParts ~= composite; 247 248 // For this subtree, only look for Drivers 249 driversOnly = true; 250 } else if (Part part = cast(Part)node) { 251 // Collect Part nodes 252 rootParts ~= part; 253 } 254 // Non-part nodes just need to be recursed through, 255 // they don't draw anything. 256 } 257 258 // Recurse through children nodes 259 foreach(child; node.children) { 260 scanPartsRecurse(child, driversOnly); 261 } 262 } 263 264 final void scanParts(bool reparent = false)(ref Node node) { 265 266 // We want rootParts to be cleared so that we 267 // don't draw the same part multiple times 268 // and if the node tree changed we want to reflect those changes 269 // not the old node tree. 270 rootParts = []; 271 272 // Same for drivers 273 drivers = []; 274 drivenParameters.clear(); 275 276 this.scanPartsRecurse(node); 277 278 // To make sure the GC can collect any nodes that aren't referenced 279 // anymore, we clear its children first, then assign its new child 280 // to our "new" root node. In some cases the root node will be 281 // quite different. 282 static if (reparent) { 283 if (puppetRootNode !is null) puppetRootNode.clearChildren(); 284 node.parent = puppetRootNode; 285 } 286 } 287 288 void selfSort() { 289 import std.math : cmp; 290 sort!((a, b) => cmp( 291 a.zSort, 292 b.zSort) > 0, SwapStrategy.stable)(rootParts); 293 } 294 295 Node findNode(Node n, string name) { 296 297 // Name matches! 298 if (n.name == name) return n; 299 300 // Recurse through children 301 foreach(child; n.children) { 302 if (Node c = findNode(child, name)) return c; 303 } 304 305 // Not found 306 return null; 307 } 308 309 Node findNode(Node n, uint uuid) { 310 311 // Name matches! 312 if (n.uuid == uuid) return n; 313 314 // Recurse through children 315 foreach(child; n.children) { 316 if (Node c = findNode(child, uuid)) return c; 317 } 318 319 // Not found 320 return null; 321 } 322 323 public: 324 /** 325 Meta information about this puppet 326 */ 327 @Name("meta") 328 PuppetMeta meta; 329 330 /** 331 Global physics settings for this puppet 332 */ 333 @Name("physics") 334 PuppetPhysics physics; 335 336 /** 337 The root node of the puppet 338 */ 339 @Name("nodes", "Root Node") 340 Node root; 341 342 /** 343 Parameters 344 */ 345 @Name("param", "Parameters") 346 @Optional 347 Parameter[] parameters; 348 349 /** 350 Parameters 351 */ 352 @Name("automation", "Automation") 353 @Optional 354 Automation[] automation; 355 356 /** 357 INP Texture slots for this puppet 358 */ 359 @Ignore 360 Texture[] textureSlots; 361 362 /** 363 Extended vendor data 364 */ 365 @Ignore 366 ubyte[][string] extData; 367 368 /** 369 Whether parameters should be rendered 370 */ 371 @Ignore 372 bool renderParameters = true; 373 374 /** 375 Whether drivers should run 376 */ 377 @Ignore 378 bool enableDrivers = true; 379 380 /** 381 Creates a new puppet from nothing () 382 */ 383 this() { 384 this.puppetRootNode = new Node(this); 385 this.meta = new PuppetMeta(); 386 this.physics = new PuppetPhysics(); 387 root = new Node(this.puppetRootNode); 388 root.name = "Root"; 389 } 390 391 /** 392 Creates a new puppet from a node tree 393 */ 394 this(Node root) { 395 this.meta = new PuppetMeta(); 396 this.physics = new PuppetPhysics(); 397 this.root = root; 398 this.puppetRootNode = new Node(this); 399 this.root.name = "Root"; 400 this.scanParts!true(this.root); 401 this.selfSort(); 402 } 403 404 /** 405 Updates the nodes 406 */ 407 final void update() { 408 409 // Rest additive offsets 410 foreach(parameter; parameters) { 411 parameter.preUpdate(); 412 } 413 414 // Update Automators 415 foreach(auto_; this.automation) { 416 auto_.update(); 417 } 418 419 root.beginUpdate(); 420 421 if (renderParameters) { 422 423 // Update parameters 424 foreach(parameter; parameters) { 425 if (!enableDrivers || parameter !in drivenParameters) 426 parameter.update(); 427 } 428 } 429 430 // Ensure the transform tree is updated 431 root.transformChanged(); 432 433 if (renderParameters && enableDrivers) { 434 // Update parameter/node driver nodes (e.g. physics) 435 foreach(driver; drivers) { 436 driver.updateDriver(); 437 } 438 } 439 440 // Update nodes 441 root.update(); 442 } 443 444 /** 445 Reset drivers/physics nodes 446 */ 447 final void resetDrivers() { 448 foreach(driver; drivers) { 449 driver.reset(); 450 } 451 } 452 453 /** 454 Returns the index of a parameter by name 455 */ 456 ptrdiff_t findParameterIndex(string name) { 457 foreach(i, parameter; parameters) { 458 if (parameter.name == name) { 459 return i; 460 } 461 } 462 return -1; 463 } 464 465 /** 466 Returns a parameter by UUID 467 */ 468 Parameter findParameter(uint uuid) { 469 foreach(i, parameter; parameters) { 470 if (parameter.uuid == uuid) { 471 return parameter; 472 } 473 } 474 return null; 475 } 476 477 /** 478 Gets if a node is bound to ANY parameter. 479 */ 480 bool getIsNodeBound(Node n) { 481 foreach(i, parameter; parameters) { 482 if (parameter.hasAnyBinding(n)) return true; 483 } 484 return false; 485 } 486 487 /** 488 Draws the puppet 489 */ 490 final void draw() { 491 this.selfSort(); 492 493 foreach(rootPart; rootParts) { 494 if (!rootPart.renderEnabled) continue; 495 rootPart.drawOne(); 496 } 497 } 498 499 /** 500 Removes a parameter from this puppet 501 */ 502 final void removeParameter(Parameter param) { 503 import std.algorithm.searching : countUntil; 504 import std.algorithm.mutation : remove; 505 ptrdiff_t idx = parameters.countUntil(param); 506 if (idx >= 0) { 507 parameters = parameters.remove(idx); 508 } 509 } 510 511 /** 512 Gets this puppet's root transform 513 */ 514 final Transform transform() { 515 return puppetRootNode.transform; 516 } 517 518 /** 519 Rescans the puppet's nodes 520 521 Run this every time you change the layout of the puppet's node tree 522 */ 523 final void rescanNodes() { 524 this.scanParts!false(root); 525 } 526 527 /** 528 Updates the texture state for all texture slots. 529 */ 530 final void updateTextureState() { 531 532 // Update filtering mode for texture slots 533 foreach(texutre; textureSlots) { 534 texutre.setFiltering(meta.preservePixels ? Filtering.Point : Filtering.Linear); 535 } 536 } 537 538 /** 539 Finds Node by its name 540 */ 541 T find(T = Node)(string name) if (is(T : Node)) { 542 return cast(T)findNode(root, name); 543 } 544 545 /** 546 Finds Node by its unique id 547 */ 548 T find(T = Node)(uint uuid) if (is(T : Node)) { 549 return cast(T)findNode(root, uuid); 550 } 551 552 /** 553 Returns all the parts in the puppet 554 */ 555 Part[] getAllParts() { 556 return findNodesType!Part(root); 557 } 558 559 /** 560 Finds nodes based on their type 561 */ 562 final T[] findNodesType(T)(Node n) if (is(T : Node)) { 563 T[] nodes; 564 565 if (T item = cast(T)n) { 566 nodes ~= item; 567 } 568 569 // Recurse through children 570 foreach(child; n.children) { 571 nodes ~= findNodesType!T(child); 572 } 573 574 return nodes; 575 } 576 577 /** 578 Adds a texture to a new slot if it doesn't already exist within this puppet 579 */ 580 final uint addTextureToSlot(Texture texture) { 581 import std.algorithm.searching : canFind; 582 583 // Add texture if we can't find it. 584 if (!textureSlots.canFind(texture)) textureSlots ~= texture; 585 return cast(uint)textureSlots.length-1; 586 } 587 588 /** 589 Populate texture slots with all visible textures in the model 590 */ 591 final void populateTextureSlots() { 592 if (textureSlots.length > 0) textureSlots.length = 0; 593 594 foreach(part; getAllParts) { 595 foreach(texture; part.textures) { 596 this.addTextureToSlot(texture); 597 } 598 } 599 } 600 601 /** 602 Sets thumbnail of this puppet 603 */ 604 final void setThumbnail(Texture texture) { 605 if (this.meta.thumbnailId == NO_THUMBNAIL) { 606 this.meta.thumbnailId = this.addTextureToSlot(texture); 607 } else { 608 textureSlots[this.meta.thumbnailId] = texture; 609 } 610 } 611 612 /** 613 Gets the texture slot index for a texture 614 615 returns -1 if none was found 616 */ 617 final ptrdiff_t getTextureSlotIndexFor(Texture texture) { 618 import std.algorithm.searching : countUntil; 619 return textureSlots.countUntil(texture); 620 } 621 622 /** 623 Clears this puppet's thumbnail 624 625 By default it does not delete the texture assigned, pass in true to delete texture 626 */ 627 final void clearThumbnail(bool deleteTexture = false) { 628 import std.algorithm.mutation : remove; 629 if (deleteTexture) textureSlots = remove(textureSlots, this.meta.thumbnailId); 630 this.meta.thumbnailId = NO_THUMBNAIL; 631 } 632 633 /** 634 This cursed toString implementation outputs the puppet's 635 nodetree as a pretty printed tree. 636 637 Please use a graphical viewer instead of this if you can, 638 eg. Inochi Creator. 639 */ 640 override 641 string toString() { 642 import std.format : format; 643 import std.range : repeat, takeExactly; 644 import std.array : array; 645 bool[] lineSet; 646 647 string toStringBranch(Node n, int indent, bool showLines = true) { 648 649 lineSet ~= n.children.length > 0; 650 string getLineSet() { 651 if (indent == 0) return ""; 652 string s = ""; 653 foreach(i; 1..lineSet.length) { 654 s ~= lineSet[i-1] ? "│ " : " "; 655 } 656 return s; 657 } 658 659 string iden = getLineSet(); 660 661 string s = "%s[%s] %s <%s>\n".format(n.children.length > 0 ? "╭─" : "", n.typeId, n.name, n.uuid); 662 foreach(i, child; n.children) { 663 string term = "├→"; 664 if (i == n.children.length-1) { 665 term = "╰→"; 666 lineSet[indent] = false; 667 } 668 s ~= "%s%s%s".format(iden, term, toStringBranch(child, indent+1)); 669 } 670 671 lineSet.length--; 672 673 return s; 674 } 675 676 return toStringBranch(root, 0); 677 } 678 679 /** 680 Serializes a puppet 681 */ 682 void serialize(S)(ref S serializer) { 683 auto state = serializer.objectBegin; 684 serializer.putKey("meta"); 685 serializer.serializeValue(meta); 686 serializer.putKey("physics"); 687 serializer.serializeValue(physics); 688 serializer.putKey("nodes"); 689 serializer.serializeValue(root); 690 serializer.putKey("param"); 691 serializer.serializeValue(parameters); 692 serializer.putKey("automation"); 693 serializer.serializeValue(automation); 694 serializer.objectEnd(state); 695 } 696 697 /** 698 Deserializes a puppet 699 */ 700 SerdeException deserializeFromFghj(Fghj data) { 701 if (auto exc = data["meta"].deserializeValue(this.meta)) return exc; 702 if (!data["physics"].isEmpty) 703 if (auto exc = data["physics"].deserializeValue(this.physics)) return exc; 704 if (auto exc = data["nodes"].deserializeValue(this.root)) return exc; 705 if (auto exc = data["param"].deserializeValue(this.parameters)) return exc; 706 707 // Deserialize automation 708 foreach(key; data["automation"].byElement) { 709 string type; 710 if (auto exc = key["type"].deserializeValue(type)) return exc; 711 712 if (inHasAutomationType(type)) { 713 auto auto_ = inInstantiateAutomation(type, this); 714 auto_.deserializeFromFghj(key); 715 this.automation ~= auto_; 716 } 717 } 718 this.finalizeDeserialization(data); 719 720 return null; 721 } 722 723 /** 724 Finalizer 725 */ 726 void finalizeDeserialization(Fghj data) { 727 this.root.setPuppet(this); 728 this.root.name = "Root"; 729 this.puppetRootNode = new Node(this); 730 this.root.finalize(); 731 foreach(parameter; parameters) { 732 parameter.finalize(this); 733 } 734 foreach(automation_; automation) { 735 automation_.finalize(this); 736 } 737 this.scanParts!true(this.root); 738 this.selfSort(); 739 } 740 741 /** 742 Gets the internal root parts array 743 744 Do note that some root parts may be Composites instead. 745 */ 746 final Node[] getRootParts() { 747 return rootParts; 748 } 749 }