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;