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