1 /*
2 Copyright (c) 2018 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.resource.packageasset;
29 
30 import std.stdio;
31 import std.string;
32 import std.format;
33 import std.path;
34 
35 import dlib.core.memory;
36 import dlib.core.stream;
37 import dlib.filesystem.filesystem;
38 import dlib.filesystem.stdfs;
39 import dlib.container.array;
40 import dlib.container.dict;
41 import dlib.math.vector;
42 import dlib.math.quaternion;
43 import dlib.image.color;
44 
45 import dagon.core.ownership;
46 import dagon.core.interfaces;
47 import dagon.resource.asset;
48 import dagon.resource.boxfs;
49 import dagon.resource.obj;
50 import dagon.resource.textureasset;
51 import dagon.resource.entityasset;
52 import dagon.resource.materialasset;
53 import dagon.resource.scene;
54 import dagon.resource.props;
55 import dagon.graphics.mesh;
56 import dagon.graphics.texture;
57 import dagon.graphics.material;
58 import dagon.logics.entity;
59 
60 /*
61  * A simple asset package format based on Box container (https://github.com/gecko0307/box).
62  * It is an archive that stores entities, meshes, materials and textures.
63  */
64 
65 class PackageAssetOwner: Owner
66 {
67     this(Owner o)
68     {
69         super(o);
70     }
71 }
72 
73 class PackageAsset: Asset
74 {
75     Dict!(OBJAsset, string) meshes;
76     Dict!(EntityAsset, string) entities;
77     Dict!(TextureAsset, string) textures;
78     Dict!(MaterialAsset, string) materials;
79 
80     string filename;
81     string index;
82     BoxFileSystem boxfs;
83     AssetManager assetManager;
84     Scene scene;
85     Entity rootEntity;
86     PackageAssetOwner assetOwner;
87 
88     this(Scene scene, Owner o)
89     {
90         super(o);
91         this.scene = scene;
92 
93         rootEntity = New!Entity(scene.eventManager, this);
94     }
95 
96     ~this()
97     {
98         release();
99     }
100 
101     override bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr)
102     {
103         this.filename = filename;
104         meshes = New!(Dict!(OBJAsset, string))();
105         entities = New!(Dict!(EntityAsset, string))();
106         textures = New!(Dict!(TextureAsset, string))();
107         materials = New!(Dict!(MaterialAsset, string))();
108         boxfs = New!BoxFileSystem(fs, filename);
109 
110         if (fileExists("INDEX"))
111         {
112             auto fstrm = boxfs.openForInput("INDEX");
113             index = readText(fstrm);
114             Delete(fstrm);
115         }
116 
117         assetManager = mngr;
118 
119         assetOwner = New!PackageAssetOwner(null);
120 
121         return true;
122     }
123 
124     override bool loadThreadUnsafePart()
125     {
126         return true;
127     }
128 
129     bool loadAsset(Asset asset, string filename)
130     {
131         if (!fileExists(filename))
132         {
133             writefln("Error: cannot find file \"%s\" in package", filename);
134             return false;
135         }
136 
137         auto fstrm = boxfs.openForInput(filename);
138         bool res = asset.loadThreadSafePart(filename, fstrm, boxfs, assetManager);
139         asset.threadSafePartLoaded = res;
140         Delete(fstrm);
141 
142         if (!res)
143         {
144             writefln("Error: failed to load asset \"%s\" from package", filename);
145             return false;
146         }
147         else
148         {
149             res = asset.loadThreadUnsafePart();
150             asset.threadUnsafePartLoaded = res;
151             if (!res)
152             {
153                 writefln("Error: failed to load asset \"%s\" from package", filename);
154                 return false;
155             }
156             else
157             {
158                 return true;
159             }
160         }
161     }
162 
163     Mesh mesh(string filename)
164     {
165         if (!(filename in meshes))
166         {
167             OBJAsset objAsset = New!OBJAsset(assetOwner);
168             if (loadAsset(objAsset, filename))
169             {
170                 meshes[filename] = objAsset;
171                 return objAsset.mesh;
172             }
173             else
174             {
175                 return null;
176             }
177         }
178         else
179         {
180             return meshes[filename].mesh;
181         }
182     }
183 
184     Entity entity(string filename)
185     {
186         if (!(filename in entities))
187         {
188             EntityAsset entityAsset = New!EntityAsset(assetOwner);
189 
190             if (loadAsset(entityAsset, filename))
191             {
192                 entities[filename] = entityAsset;
193 
194                 Entity parent = rootEntity;
195 
196                 if ("parent" in entityAsset.props)
197                 {
198                     parent = entity(entityAsset.props.parent.toString);
199                 }
200 
201                 entityAsset.entity = New!Entity(parent, assetOwner);
202                 entityAsset.entity.visible = true;
203                 entityAsset.entity.castShadow = true;
204                 entityAsset.entity.useMotionBlur = true;
205                 entityAsset.entity.layer = 1;
206                 entityAsset.entity.solid = true;
207                 entityAsset.entity.material = scene.defaultMaterial3D;
208 
209                 if ("position" in entityAsset.props)
210                 {
211                     entityAsset.entity.position = entityAsset.props.position.toVector3f;
212                 }
213 
214                 if ("rotation" in entityAsset.props)
215                 {
216                     entityAsset.entity.rotation = Quaternionf(entityAsset.props.rotation.toVector4f).normalized;
217                 }
218 
219                 if ("scale" in entityAsset.props)
220                 {
221                     entityAsset.entity.scaling = entityAsset.props.scale.toVector3f;
222                 }
223 
224                 entityAsset.entity.updateTransformation();
225 
226                 if ("visible" in entityAsset.props)
227                 {
228                     entityAsset.entity.visible = entityAsset.props.visible.toBool;
229                 }
230 
231                 if ("castShadow" in entityAsset.props)
232                 {
233                     entityAsset.entity.castShadow = entityAsset.props.castShadow.toBool;
234                 }
235 
236                 if ("useMotionBlur" in entityAsset.props)
237                 {
238                     entityAsset.entity.useMotionBlur = entityAsset.props.useMotionBlur.toBool;
239                 }
240 
241                 if ("solid" in entityAsset.props)
242                 {
243                     entityAsset.entity.solid = entityAsset.props.solid.toBool;
244                 }
245 
246                 if ("layer" in entityAsset.props)
247                 {
248                     entityAsset.entity.layer = entityAsset.props.layer.toInt;
249                 }
250 
251                 if ("mesh" in entityAsset.props)
252                 {
253                     entityAsset.entity.drawable = mesh(entityAsset.props.mesh.toString);
254                 }
255 
256                 if ("material" in entityAsset.props)
257                 {
258                     entityAsset.entity.material = material(entityAsset.props.material.toString);
259                 }
260 
261                 if (entityAsset.entity.parent)
262                     scene.sortEntities(entityAsset.entity.parent.children);
263 
264                 return entityAsset.entity;
265             }
266             else
267             {
268                 return null;
269             }
270         }
271         else
272         {
273             return entities[filename].entity;
274         }
275     }
276 
277     Texture texture(string filename)
278     {
279         if (!(filename in textures))
280         {
281             TextureAsset texAsset = New!TextureAsset(assetManager.imageFactory, assetManager.hdrImageFactory, assetOwner);
282             if (loadAsset(texAsset, filename))
283             {
284                 textures[filename] = texAsset;
285                 return texAsset.texture;
286             }
287             else
288             {
289                 return null;
290             }
291         }
292         else
293         {
294             return textures[filename].texture;
295         }
296     }
297 
298     Material material(string filename)
299     {
300         if (!(filename in materials))
301         {
302             MaterialAsset matAsset = New!MaterialAsset(assetOwner);
303             if (loadAsset(matAsset, filename))
304             {
305                 materials[filename] = matAsset;
306                 matAsset.material = createMaterial();
307 
308                 // diffuse
309                 if ("diffuse" in matAsset.props)
310                 {
311                     if (matAsset.props.diffuse.type == DPropType.String)
312                     {
313                         matAsset.material.diffuse = texture(matAsset.props.diffuse.toString);
314                     }
315                     else
316                     {
317                         Vector3f diffCol = matAsset.props.diffuse.toVector3f;
318                         matAsset.material.diffuse = Color4f(diffCol.r, diffCol.g, diffCol.b, 1.0f);
319                     }
320                 }
321 
322                 // emission
323                 if ("emission" in matAsset.props)
324                 {
325                     if (matAsset.props.emission.type == DPropType.String)
326                     {
327                         matAsset.material.emission = texture(matAsset.props.emission.toString);
328                     }
329                     else
330                     {
331                         Vector3f emissionCol = matAsset.props.emission.toVector3f;
332                         matAsset.material.emission = Color4f(emissionCol.r, emissionCol.g, emissionCol.b, 1.0f);
333                     }
334                 }
335 
336                 // energy
337                 if ("energy" in matAsset.props)
338                 {
339                     matAsset.material.energy = matAsset.props.energy.toFloat;
340                 }
341 
342                 // normal
343                 if ("normal" in matAsset.props)
344                 {
345                     if (matAsset.props.normal.type == DPropType.String)
346                     {
347                         matAsset.material.normal = texture(matAsset.props.normal.toString);
348                     }
349                 }
350 
351                 // height
352                 if ("height" in matAsset.props)
353                 {
354                     if (matAsset.props.height.type == DPropType.String)
355                     {
356                         matAsset.material.height = texture(matAsset.props.height.toString);
357                     }
358                 }
359 
360                 // roughness
361                 if ("roughness" in matAsset.props)
362                 {
363                     if (matAsset.props.roughness.type == DPropType.String)
364                     {
365                         matAsset.material.roughness = texture(matAsset.props.roughness.toString);
366                     }
367                     else
368                     {
369                         matAsset.material.roughness = matAsset.props.roughness.toFloat;
370                     }
371                 }
372 
373                 // metallic
374                 if ("metallic" in matAsset.props)
375                 {
376                     if (matAsset.props.metallic.type == DPropType.String)
377                     {
378                         matAsset.material.metallic = texture(matAsset.props.metallic.toString);
379                     }
380                     else
381                     {
382                         matAsset.material.metallic = matAsset.props.metallic.toFloat;
383                     }
384                 }
385 
386                 // parallax
387                 if ("parallax" in matAsset.props)
388                 {
389                     matAsset.material.parallax = matAsset.props.parallax.toInt;
390                 }
391 
392                 // parallaxScale
393                 if ("parallaxScale" in matAsset.props)
394                 {
395                     matAsset.material.parallaxScale = matAsset.props.parallaxScale.toFloat;
396                 }
397 
398                 // parallaxBias
399                 if ("parallaxBias" in matAsset.props)
400                 {
401                     matAsset.material.parallaxBias = matAsset.props.parallaxBias.toFloat;
402                 }
403 
404                 // shadeless
405                 if ("shadeless" in matAsset.props)
406                 {
407                     matAsset.material.shadeless = matAsset.props.shadeless.toBool;
408                 }
409 
410                 // culling
411                 if ("culling" in matAsset.props)
412                 {
413                     matAsset.material.culling = matAsset.props.culling.toBool;
414                 }
415 
416                 // colorWrite
417                 if ("colorWrite" in matAsset.props)
418                 {
419                     matAsset.material.colorWrite = matAsset.props.colorWrite.toBool;
420                 }
421 
422                 // depthWrite
423                 if ("depthWrite" in matAsset.props)
424                 {
425                     matAsset.material.depthWrite = matAsset.props.depthWrite.toBool;
426                 }
427 
428                 // useShadows
429                 if ("useShadows" in matAsset.props)
430                 {
431                     matAsset.material.shadowsEnabled = matAsset.props.useShadows.toBool;
432                 }
433 
434                 // useFog
435                 if ("useFog" in matAsset.props)
436                 {
437                     matAsset.material.fogEnabled = matAsset.props.useFog.toBool;
438                 }
439 
440                 // shadowFilter
441                 if ("shadowFilter" in matAsset.props)
442                 {
443                     matAsset.material.shadowFilter = matAsset.props.shadowFilter.toInt;
444                 }
445 
446                 // blendingMode
447                 if ("blendingMode" in matAsset.props)
448                 {
449                     matAsset.material.blending = matAsset.props.blendingMode.toInt;
450                 }
451 
452                 // transparency
453                 if ("transparency" in matAsset.props)
454                 {
455                     matAsset.material.transparency = matAsset.props.transparency.toFloat;
456                 }
457 
458                 return matAsset.material;
459             }
460             else
461             {
462                 return null;
463             }
464         }
465         else
466         {
467             return materials[filename].material;
468         }
469     }
470 
471     Entity entity()
472     {
473         if (index.length)
474         foreach(path; lineSplitter(index))
475         {
476             Entity e = entity(path);
477         }
478 
479         return rootEntity;
480     }
481 
482     Material createMaterial()
483     {
484         auto m = New!Material(scene.standardShader, assetOwner);
485         return m;
486     }
487 
488     bool fileExists(string filename)
489     {
490         FileStat stat;
491         return boxfs.stat(filename, stat);
492     }
493 
494     override void release()
495     {
496         Delete(boxfs);
497 
498         Delete(meshes);
499         Delete(entities);
500         Delete(textures);
501         Delete(materials);
502 
503         Delete(assetOwner);
504 
505         rootEntity.release();
506 
507         if (index.length)
508             Delete(index);
509     }
510 }
511