1 /* 2 Copyright © 2020, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module creator; 8 import inochi2d; 9 import inochi2d.core.dbg; 10 import inochi2d.core.nodes.common; 11 import creator.viewport; 12 import creator.viewport.model; 13 import creator.viewport.model.deform; 14 import creator.core; 15 import creator.core.actionstack; 16 import creator.windows; 17 import creator.atlas; 18 19 public import creator.ver; 20 public import creator.atlas; 21 import creator.core.colorbleed; 22 23 import std.file; 24 25 /** 26 A project 27 */ 28 class Project { 29 /** 30 The puppet in the project 31 */ 32 Puppet puppet; 33 34 /** 35 Textures for use in the puppet 36 37 Can be rearranged 38 */ 39 Texture[] textures; 40 } 41 42 private { 43 Project activeProject; 44 Node[] selectedNodes; 45 Drawable[] drawables; 46 Parameter armedParam; 47 string currProjectPath; 48 string[] prevProjects; 49 } 50 51 /** 52 Edit modes 53 */ 54 enum EditMode { 55 /** 56 Model editing mode 57 */ 58 ModelEdit, 59 60 /** 61 Vertex Editing Mode 62 */ 63 VertexEdit, 64 65 /** 66 Animation Editing Mode 67 */ 68 AnimEdit, 69 70 /** 71 Model testing mode 72 */ 73 ModelTest 74 } 75 76 bool incShowVertices = true; /// Show vertices of selected parts 77 bool incShowBounds = true; /// Show bounds of selected parts 78 bool incShowOrientation = true; /// Show orientation gizmo of selected parts 79 80 /** 81 Current edit mode 82 */ 83 EditMode editMode_; 84 85 86 /** 87 Returns the current project path 88 */ 89 string incProjectPath() { 90 return currProjectPath; 91 } 92 93 /** 94 Return a list of prior projects 95 */ 96 string[] incGetPrevProjects() { 97 return incSettingsGet!(string[])("prev_projects"); 98 } 99 100 void incAddPrevProject(string path) { 101 import std.algorithm.searching : countUntil; 102 import std.algorithm.mutation : remove; 103 string[] projects = incSettingsGet!(string[])("prev_projects"); 104 105 ptrdiff_t idx = projects.countUntil(path); 106 if (idx >= 0) { 107 projects = projects.remove(idx); 108 } 109 110 // Put project to the start of the "previous" list and 111 // limit to 10 elements 112 projects = path ~ projects; 113 if(projects.length > 10) projects.length = 10; 114 115 // Then save. 116 incSettingsSet("prev_projects", projects); 117 incSettingsSave(); 118 } 119 120 /** 121 Creates a new project 122 */ 123 void incNewProject() { 124 currProjectPath = ""; 125 editMode_ = EditMode.ModelEdit; 126 import creator.viewport : incViewportReset; 127 128 incPopWindowListAll(); 129 130 activeProject = new Project; 131 activeProject.puppet = new Puppet; 132 incSelectNode(null); 133 134 inDbgDrawMeshVertexPoints = true; 135 inDbgDrawMeshOutlines = true; 136 inDbgDrawMeshOrientation = true; 137 138 incViewportReset(); 139 140 incActionClearHistory(); 141 incFreeMemory(); 142 143 incViewportPresentMode(editMode_); 144 } 145 146 void incOpenProject(string path) { 147 Puppet puppet; 148 149 // Load the puppet from file 150 try { 151 puppet = inLoadPuppet(path); 152 } catch (std.file.FileException e) { 153 return; 154 } 155 156 // Clear out stuff by creating a new project 157 incNewProject(); 158 159 // Set the path 160 currProjectPath = path; 161 incAddPrevProject(path); 162 163 incActiveProject().puppet = puppet; 164 incFocusCamera(incActivePuppet().root); 165 incFreeMemory(); 166 } 167 168 void incSaveProject(string path) { 169 import std.path : setExtension; 170 string finalPath = path.setExtension(".inx"); 171 currProjectPath = path; 172 incAddPrevProject(finalPath); 173 174 // Remember to populate texture slots otherwise things will break real bad! 175 incActivePuppet().populateTextureSlots(); 176 177 // Write the puppet to file 178 inWriteINPPuppet(incActivePuppet(), finalPath); 179 } 180 181 /** 182 Imports image files from a selected folder. 183 */ 184 void incImportFolder(string folder) { 185 incNewProject(); 186 187 import std.file : dirEntries, SpanMode; 188 import std.path : stripExtension, baseName; 189 190 // For each file find PNG, TGA and JPEG files and import them 191 Puppet puppet = new Puppet(); 192 size_t i; 193 foreach(file; dirEntries(folder, SpanMode.shallow, false)) { 194 195 // TODO: Check for position.ini 196 197 auto tex = ShallowTexture(file); 198 inTexPremultiply(tex.data); 199 200 Part part = inCreateSimplePart(new Texture(tex), null, file.baseName.stripExtension); 201 part.zSort = -((cast(float)i++)/100); 202 puppet.root.addChild(part); 203 } 204 puppet.rescanNodes(); 205 puppet.populateTextureSlots(); 206 incActiveProject().puppet = puppet; 207 incFocusCamera(incActivePuppet().root); 208 incFreeMemory(); 209 } 210 211 /** 212 Imports a PSD file. 213 */ 214 void incImportPSD(string file) { 215 incNewProject(); 216 import psd : PSD, Layer, LayerType, LayerFlags, parseDocument, BlendingMode; 217 PSD doc = parseDocument(file); 218 vec2i docCenter = vec2i(doc.width/2, doc.height/2); 219 Puppet puppet = new Puppet(); 220 221 Layer[] layerGroupStack; 222 bool isLastStackItemHidden() { 223 return layerGroupStack.length > 0 ? (layerGroupStack[$-1].flags & LayerFlags.Visible) != 0 : false; 224 } 225 226 foreach_reverse(i, Layer layer; doc.layers) { 227 import std.stdio : writeln; 228 debug writeln(layer.name, " ", layer.blendModeKey); 229 230 // Skip folders ( for now ) 231 if (layer.type != LayerType.Any) { 232 if (layer.name != "</Layer set>") { 233 layerGroupStack ~= layer; 234 } else layerGroupStack.length--; 235 236 continue; 237 } 238 239 layer.extractLayerImage(); 240 inTexPremultiply(layer.data); 241 auto tex = new Texture(layer.data, layer.width, layer.height); 242 Part part = inCreateSimplePart(tex, puppet.root, layer.name); 243 244 auto layerSize = cast(int[2])layer.size(); 245 vec2i layerPosition = vec2i( 246 layer.left, 247 layer.top 248 ); 249 250 part.localTransform.translation = vec3( 251 (layerPosition.x+(layerSize[0]/2))-docCenter.x, 252 (layerPosition.y+(layerSize[1]/2))-docCenter.y, 253 0 254 ); 255 256 257 part.enabled = (layer.flags & LayerFlags.Visible) == 0; 258 part.opacity = (cast(float)layer.opacity)/255; 259 part.zSort = -(cast(float)i)/100; 260 switch(layer.blendModeKey) { 261 case BlendingMode.Multiply: 262 part.blendingMode = BlendMode.Multiply; break; 263 case BlendingMode.LinearDodge: 264 part.blendingMode = BlendMode.LinearDodge; break; 265 case BlendingMode.ColorDodge: 266 part.blendingMode = BlendMode.ColorDodge; break; 267 case BlendingMode.Screen: 268 part.blendingMode = BlendMode.Screen; break; 269 default: 270 part.blendingMode = BlendMode.Normal; break; 271 } 272 debug writeln(part.name, ": ", part.blendingMode); 273 274 // Handle layer stack stuff 275 if (layerGroupStack.length > 0) { 276 if (isLastStackItemHidden()) part.enabled = false; 277 if (layerGroupStack[$-1].blendModeKey != BlendingMode.PassThrough) { 278 switch(layerGroupStack[$-1].blendModeKey) { 279 case BlendingMode.Multiply: 280 part.blendingMode = BlendMode.Multiply; break; 281 case BlendingMode.LinearDodge: 282 part.blendingMode = BlendMode.LinearDodge; break; 283 case BlendingMode.ColorDodge: 284 part.blendingMode = BlendMode.ColorDodge; break; 285 case BlendingMode.Screen: 286 part.blendingMode = BlendMode.Screen; break; 287 default: 288 part.blendingMode = BlendMode.Normal; break; 289 } 290 } 291 } 292 293 puppet.root.addChild(part); 294 } 295 296 puppet.populateTextureSlots(); 297 incActiveProject().puppet = puppet; 298 incFocusCamera(incActivePuppet().root); 299 incFreeMemory(); 300 } 301 302 /** 303 Imports an Inochi2D puppet 304 */ 305 void incImportINP(string file) { 306 incNewProject(); 307 Puppet puppet = inLoadPuppet(file); 308 incActiveProject().puppet = puppet; 309 incFocusCamera(incActivePuppet().root); 310 incFreeMemory(); 311 } 312 313 /** 314 Exports an Inochi2D Puppet 315 */ 316 void incExportINP(string file) { 317 import std.path : setExtension; 318 319 // Remember to populate texture slots otherwise things will break real bad! 320 incActivePuppet().populateTextureSlots(); 321 322 // TODO: Generate optimized puppet from this puppet. 323 324 // Write the puppet to file 325 inWriteINPPuppet(incActivePuppet(), file.setExtension(".inp")); 326 327 } 328 329 void incRegenerateMipmaps() { 330 331 // Allow for nice looking filtering 332 foreach(texture; incActiveProject().puppet.textureSlots) { 333 texture.genMipmap(); 334 texture.setFiltering(Filtering.Linear); 335 } 336 } 337 338 /** 339 Re-bleeds textures in a model 340 */ 341 void incRebleedTextures() { 342 incTaskAdd("Rebleed", () { 343 incTaskStatus("Bleeding textures..."); 344 foreach(i, Texture texture; activeProject.puppet.textureSlots) { 345 incTaskProgress(cast(float)i/activeProject.puppet.textureSlots.length); 346 incTaskYield(); 347 incColorBleedPixels(texture); 348 } 349 }); 350 } 351 352 /** 353 Force the garbage collector to collect model memory 354 */ 355 void incFreeMemory() { 356 import core.memory : GC; 357 GC.collect(); 358 } 359 360 /** 361 Gets puppet in active project 362 */ 363 ref Puppet incActivePuppet() { 364 return activeProject.puppet; 365 } 366 367 /** 368 Gets active project 369 */ 370 ref Project incActiveProject() { 371 return activeProject; 372 } 373 374 /** 375 Gets the currently armed parameter 376 */ 377 Parameter incArmedParameter() { 378 return editMode_ == EditMode.ModelEdit ? armedParam : null; 379 } 380 381 /** 382 Gets the currently selected node 383 */ 384 ref Node[] incSelectedNodes() { 385 return selectedNodes; 386 } 387 388 /** 389 Gets a list of the current drawables 390 */ 391 ref Drawable[] incDrawables() { 392 return drawables; 393 } 394 395 /** 396 Gets the currently selected root node 397 */ 398 ref Node incSelectedNode() { 399 return selectedNodes.length == 0 ? incActivePuppet.root : selectedNodes[0]; 400 } 401 402 /** 403 Arms a parameter 404 */ 405 void incArmParameter(ref Parameter param) { 406 armedParam = param; 407 incViewportNodeDeformNotifyParamValueChanged(); 408 activeProject.puppet.renderParameters = false; 409 } 410 411 /** 412 Disarms parameter recording 413 */ 414 void incDisarmParameter() { 415 armedParam = null; 416 incViewportNodeDeformNotifyParamValueChanged(); 417 activeProject.puppet.renderParameters = true; 418 } 419 420 /** 421 Selects a node 422 */ 423 void incSelectNode(Node n = null) { 424 if (n is null) selectedNodes.length = 0; 425 else selectedNodes = [n]; 426 incViewportModelNodeSelectionChanged(); 427 } 428 429 /** 430 Adds node to selection 431 */ 432 void incAddSelectNode(Node n) { 433 if (incArmedParameter()) return; 434 selectedNodes ~= n; 435 } 436 437 /** 438 Remove node from selection 439 */ 440 void incRemoveSelectNode(Node n) { 441 foreach(i, nn; selectedNodes) { 442 if (n.uuid == nn.uuid) { 443 import std.algorithm.mutation : remove; 444 selectedNodes = selectedNodes.remove(i); 445 } 446 } 447 } 448 449 private void incSelectAllRecurse(Node n) { 450 incAddSelectNode(n); 451 foreach(child; n.children) { 452 incSelectAllRecurse(child); 453 } 454 } 455 456 /** 457 Selects all nodes 458 */ 459 void incSelectAll() { 460 if (incArmedParameter()) return; 461 incSelectNode(); 462 foreach(child; incActivePuppet().root.children) { 463 incSelectAllRecurse(child); 464 } 465 } 466 467 /** 468 Gets whether the node is in the selection 469 */ 470 bool incNodeInSelection(Node n) { 471 foreach(i, nn; selectedNodes) { 472 if (nn is null) continue; 473 474 if (n.uuid == nn.uuid) return true; 475 } 476 477 return false; 478 } 479 480 /** 481 Focus camera at node 482 */ 483 void incFocusCamera(Node node) { 484 import creator.viewport : incViewportTargetZoom, incViewportTargetPosition; 485 if (node is null) return; 486 487 auto nt = node.transform; 488 incFocusCamera(node, vec2(-nt.translation.x, -nt.translation.y)); 489 } 490 491 /** 492 Focus camera at node 493 */ 494 void incFocusCamera(Node node, vec2 position) { 495 import creator.viewport : incViewportTargetZoom, incViewportTargetPosition; 496 if (node is null) return; 497 498 int width, height; 499 inGetViewport(width, height); 500 501 auto nt = node.transform; 502 503 vec4 bounds = node.getCombinedBounds(); 504 vec2 boundsSize = bounds.zw - bounds.xy; 505 if (auto drawable = cast(Drawable)node) boundsSize = drawable.bounds.zw - drawable.bounds.xy; 506 else { 507 nt.translation = vec3(bounds.x + ((bounds.z-bounds.x)/2), bounds.y + ((bounds.w-bounds.y)/2), 0); 508 } 509 510 511 float largestViewport = max(width, height); 512 float largestBounds = max(boundsSize.x, boundsSize.y); 513 514 float factor = largestViewport/largestBounds; 515 incViewportTargetZoom = clamp(factor*0.85, 0.1, 2); 516 517 incViewportTargetPosition = vec2( 518 position.x, 519 position.y 520 ); 521 } 522 523 /** 524 Gets the current editing mode 525 */ 526 EditMode incEditMode() { 527 return editMode_; 528 } 529 530 /** 531 Sets the current editing mode 532 */ 533 void incSetEditMode(EditMode editMode, bool unselect = true) { 534 incViewportWithdrawMode(editMode_); 535 536 if (armedParam) { 537 armedParam.value = armedParam.getClosestKeypointValue(armedParam.value); 538 } 539 if (unselect) incSelectNode(null); 540 if (editMode != EditMode.ModelEdit) { 541 drawables = activeProject.puppet.findNodesType!Drawable(activeProject.puppet.root); 542 } 543 editMode_ = editMode; 544 545 incViewportPresentMode(editMode_); 546 }