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 creator.core; 11 import creator.core.actionstack; 12 import creator.atlas; 13 14 public import creator.ver; 15 public import creator.atlas; 16 import creator.core.colorbleed; 17 18 /** 19 A project 20 */ 21 class Project { 22 /** 23 The puppet in the project 24 */ 25 Puppet puppet; 26 27 /** 28 Textures for use in the puppet 29 30 Can be rearranged 31 */ 32 Texture[] textures; 33 } 34 35 private { 36 Project activeProject; 37 Node[] selectedNodes; 38 Drawable[] drawables; 39 } 40 41 /** 42 Edit modes 43 */ 44 enum EditMode { 45 ModelEdit, 46 DeformEdit, 47 VertexEdit 48 } 49 50 bool incShowVertices = true; /// Show vertices of selected parts 51 bool incShowBounds = true; /// Show bounds of selected parts 52 bool incShowOrientation = true; /// Show orientation gizmo of selected parts 53 54 /** 55 Current edit mode 56 */ 57 EditMode editMode_; 58 59 void incBeginUpdate() { 60 inBeginScene(); 61 } 62 63 /** 64 Updates the active Inochi2D project 65 */ 66 void incUpdateActiveProject() { 67 68 activeProject.puppet.update(); 69 activeProject.puppet.draw(); 70 71 if (selectedNodes.length > 0) { 72 foreach(selectedNode; selectedNodes) { 73 if (selectedNode is null) continue; 74 if (incShowOrientation) selectedNode.drawOrientation(); 75 if (incShowBounds) selectedNode.drawBounds(); 76 77 if (Drawable selectedDraw = cast(Drawable)selectedNode) { 78 79 if (incShowVertices || incEditMode != EditMode.ModelEdit) { 80 selectedDraw.drawMeshLines(); 81 selectedDraw.drawMeshPoints(); 82 } 83 } 84 85 } 86 } 87 } 88 89 void incEndUpdate() { 90 inEndScene(); 91 } 92 93 94 /** 95 Creates a new project 96 */ 97 void incNewProject() { 98 activeProject = new Project; 99 activeProject.puppet = new Puppet; 100 incSelectNode(null); 101 102 inDbgDrawMeshVertexPoints = true; 103 inDbgDrawMeshOutlines = true; 104 inDbgDrawMeshOrientation = true; 105 106 incTargetPosition = vec2(0); 107 incTargetZoom = 1; 108 109 incActionClearHistory(); 110 incFreeMemory(); 111 } 112 113 /** 114 Imports image files from a selected folder. 115 */ 116 void incImportFolder(string folder) { 117 incNewProject(); 118 119 import std.file : dirEntries, SpanMode; 120 import std.path : stripExtension, baseName; 121 122 // For each file find PNG, TGA and JPEG files and import them 123 Puppet puppet = new Puppet(); 124 size_t i; 125 foreach(file; dirEntries(folder, SpanMode.shallow, false)) { 126 127 // TODO: Check for position.ini 128 129 auto tex = ShallowTexture(file); 130 inTexPremultiply(tex.data); 131 132 Part part = inCreateSimplePart(new Texture(tex), null, file.baseName.stripExtension); 133 part.zSort = -((cast(float)i++)/100); 134 puppet.root.addChild(part); 135 } 136 puppet.rescanNodes(); 137 puppet.populateTextureSlots(); 138 incActiveProject().puppet = puppet; 139 incFreeMemory(); 140 } 141 142 /** 143 Imports a PSD file. 144 */ 145 void incImportPSD(string file) { 146 incNewProject(); 147 import psd : PSD, Layer, LayerType, LayerFlags, parseDocument, BlendingMode; 148 PSD doc = parseDocument(file); 149 vec2i docCenter = vec2i(doc.width/2, doc.height/2); 150 Puppet puppet = new Puppet(); 151 foreach(i, Layer layer; doc.layers) { 152 153 // Skip folders ( for now ) 154 if (layer.type != LayerType.Any) continue; 155 156 layer.extractLayerImage(); 157 inTexPremultiply(layer.data); 158 auto tex = new Texture(layer.data, layer.width, layer.height); 159 Part part = inCreateSimplePart(tex, puppet.root, layer.name); 160 161 auto layerSize = cast(int[2])layer.size(); 162 vec2i layerPosition = vec2i( 163 layer.left, 164 layer.top 165 ); 166 167 part.localTransform.translation = vec3( 168 (layerPosition.x+(layerSize[0]/2))-docCenter.x, 169 (layerPosition.y+(layerSize[1]/2))-docCenter.y, 170 0 171 ); 172 173 part.enabled = (layer.flags & LayerFlags.Visible) == 0; 174 part.opacity = (cast(float)layer.opacity)/255; 175 176 switch(layer.blendModeKey) { 177 case BlendingMode.Multiply: 178 part.blendingMode = BlendMode.Multiply; break; 179 default: 180 part.blendingMode = BlendMode.Normal; break; 181 } 182 183 part.zSort = -(cast(float)i)/100; 184 185 puppet.root.addChild(part); 186 } 187 188 puppet.populateTextureSlots(); 189 incActiveProject().puppet = puppet; 190 incFreeMemory(); 191 } 192 193 /** 194 Imports an INP puppet 195 */ 196 void incImportINP(string file) { 197 incNewProject(); 198 Puppet puppet = inLoadPuppet(file); 199 incActiveProject().puppet = puppet; 200 incFreeMemory(); 201 } 202 203 void incRegenerateMipmaps() { 204 205 // Allow for nice looking filtering 206 foreach(texture; incActiveProject().puppet.textureSlots) { 207 texture.genMipmap(); 208 texture.setFiltering(Filtering.Linear); 209 } 210 } 211 212 /** 213 Re-bleeds textures in a model 214 */ 215 void incRebleedTextures() { 216 incTaskAdd("Rebleed", () { 217 incTaskStatus("Bleeding textures..."); 218 foreach(i, Texture texture; activeProject.puppet.textureSlots) { 219 incTaskProgress(cast(float)i/activeProject.puppet.textureSlots.length); 220 incTaskYield(); 221 incColorBleedPixels(texture); 222 } 223 }); 224 } 225 226 /** 227 Force the garbage collector to collect model memory 228 */ 229 void incFreeMemory() { 230 import core.memory : GC; 231 GC.collect(); 232 } 233 234 /** 235 Gets puppet in active project 236 */ 237 ref Puppet incActivePuppet() { 238 return activeProject.puppet; 239 } 240 241 /** 242 Gets active project 243 */ 244 ref Project incActiveProject() { 245 return activeProject; 246 } 247 248 /** 249 Gets the currently selected node 250 */ 251 ref Node[] incSelectedNodes() { 252 return selectedNodes; 253 } 254 255 /** 256 Gets a list of the current drawables 257 */ 258 ref Drawable[] incDrawables() { 259 return drawables; 260 } 261 262 /** 263 Gets the currently selected root node 264 */ 265 ref Node incSelectedNode() { 266 return selectedNodes.length == 0 ? incActivePuppet.root : selectedNodes[0]; 267 } 268 269 /** 270 Selects a node 271 */ 272 void incSelectNode(Node n = null) { 273 if (n is null) selectedNodes.length = 0; 274 else selectedNodes = [n]; 275 } 276 277 /** 278 Adds node to selection 279 */ 280 void incAddSelectNode(Node n) { 281 selectedNodes ~= n; 282 } 283 284 /** 285 Remove node from selection 286 */ 287 void incRemoveSelectNode(Node n) { 288 foreach(i, nn; selectedNodes) { 289 if (n.uuid == nn.uuid) { 290 import std.algorithm.mutation : remove; 291 selectedNodes = selectedNodes.remove(i); 292 } 293 } 294 } 295 296 private void incSelectAllRecurse(Node n) { 297 incAddSelectNode(n); 298 foreach(child; n.children) { 299 incSelectAllRecurse(child); 300 } 301 } 302 303 /** 304 Selects all nodes 305 */ 306 void incSelectAll() { 307 incSelectNode(); 308 foreach(child; incActivePuppet().root.children) { 309 incSelectAllRecurse(child); 310 } 311 } 312 313 /** 314 Gets whether the node is in the selection 315 */ 316 bool incNodeInSelection(Node n) { 317 foreach(i, nn; selectedNodes) { 318 if (nn is null) continue; 319 320 if (n.uuid == nn.uuid) return true; 321 } 322 323 return false; 324 } 325 326 /** 327 Focus camera at node 328 */ 329 void incFocusCamera(Node node) { 330 if (node !is null) { 331 int width, height; 332 inGetViewport(width, height); 333 334 auto nt = node.transform; 335 336 vec4 bounds = node.getCombinedBounds(); 337 vec2 boundsSize = bounds.zw - bounds.xy; 338 if (auto drawable = cast(Drawable)node) boundsSize = drawable.bounds.zw - drawable.bounds.xy; 339 else { 340 nt.translation = vec3(bounds.x + ((bounds.z-bounds.x)/2), bounds.y + ((bounds.w-bounds.y)/2), 0); 341 } 342 343 344 float largestViewport = max(width, height); 345 float largestBounds = max(boundsSize.x, boundsSize.y); 346 347 float factor = largestViewport/largestBounds; 348 incTargetZoom = clamp(factor*0.85, 0.1, 2); 349 350 incTargetPosition = vec2( 351 -nt.translation.x, 352 -nt.translation.y 353 ); 354 } 355 356 } 357 358 /** 359 Gets the current editing mode 360 */ 361 EditMode incEditMode() { 362 return editMode_; 363 } 364 365 /** 366 Sets the current editing mode 367 */ 368 void incSetEditMode(EditMode editMode) { 369 incSelectNode(null); 370 if (editMode != EditMode.ModelEdit) { 371 drawables = activeProject.puppet.findNodesType!Drawable(activeProject.puppet.root); 372 } 373 editMode_ = editMode; 374 } 375 376 /** 377 Target camera position in scene 378 */ 379 vec2 incTargetPosition = vec2(0); 380 381 /** 382 Target camera zoom in scene 383 */ 384 float incTargetZoom = 1; 385 386 enum incVIEWPORT_ZOOM_MIN = 0.05; 387 enum incVIEWPORT_ZOOM_MAX = 8.0;