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.graphics.materials.generic;
59 import dagon.logics.entity;
60 
61 /*
62  * A simple asset package format based on Box container (https://github.com/gecko0307/box).
63  * It is an archive that stores entities, meshes, materials and textures.
64  */
65  
66 class PackageAssetOwner: Owner
67 {
68     this(Owner o)
69     {
70         super(o);
71     }
72 }
73 
74 class PackageAsset: Asset
75 {
76     Dict!(OBJAsset, string) meshes;
77     Dict!(EntityAsset, string) entities;
78     Dict!(TextureAsset, string) textures;
79     Dict!(MaterialAsset, string) materials;
80     
81     string filename;
82     string index;
83     BoxFileSystem boxfs;
84     AssetManager assetManager;
85     Scene scene;
86     Entity rootEntity;
87     PackageAssetOwner assetOwner;
88 
89     this(Scene scene, Owner o)
90     {
91         super(o);
92         this.scene = scene;
93         
94         rootEntity = New!Entity(scene.eventManager, this);
95     }
96 
97     ~this()
98     {
99         release();
100     }
101 
102     override bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr)
103     {
104         this.filename = filename;
105         meshes = New!(Dict!(OBJAsset, string))();
106         entities = New!(Dict!(EntityAsset, string))();
107         textures = New!(Dict!(TextureAsset, string))();
108         materials = New!(Dict!(MaterialAsset, string))();
109         boxfs = New!BoxFileSystem(fs, filename);
110         
111         if (fileExists("INDEX"))
112         {
113             auto fstrm = boxfs.openForInput("INDEX");
114             index = readText(fstrm);
115             Delete(fstrm);
116         }
117         
118         assetManager = mngr;
119         
120         assetOwner = New!PackageAssetOwner(null);
121         
122         return true;
123     }
124 
125     override bool loadThreadUnsafePart()
126     {        
127         return true;
128     }
129     
130     bool loadAsset(Asset asset, string filename)
131     {
132         if (!fileExists(filename))
133         {
134             writefln("Error: cannot find file \"%s\" in package", filename);
135             return false;
136         }
137         
138         auto fstrm = boxfs.openForInput(filename);
139         bool res = asset.loadThreadSafePart(filename, fstrm, boxfs, assetManager);
140         asset.threadSafePartLoaded = res;
141         Delete(fstrm);
142         
143         if (!res)
144         {
145             writefln("Error: failed to load asset \"%s\" from package", filename);
146             return false;
147         }
148         else
149         {
150             res = asset.loadThreadUnsafePart();
151             asset.threadUnsafePartLoaded = res;
152             if (!res)
153             {
154                 writefln("Error: failed to load asset \"%s\" from package", filename);
155                 return false;
156             }
157             else
158             {
159                 return true;
160             }
161         }
162     }
163     
164     Mesh mesh(string filename)
165     {
166         if (!(filename in meshes))
167         {
168             OBJAsset objAsset = New!OBJAsset(assetOwner);
169             if (loadAsset(objAsset, filename))
170             {
171                 meshes[filename] = objAsset;
172                 return objAsset.mesh;
173             }
174             else
175             {
176                 return null;
177             }
178         }
179         else
180         {
181             return meshes[filename].mesh;
182         }
183     }
184     
185     Entity entity(string filename)
186     {
187         if (!(filename in entities))
188         {
189             EntityAsset entityAsset = New!EntityAsset(assetOwner);
190 
191             if (loadAsset(entityAsset, filename))
192             {
193                 entities[filename] = entityAsset;
194                 
195                 Entity parent = rootEntity;
196                 
197                 if ("parent" in entityAsset.props)
198                 {
199                     parent = entity(entityAsset.props.parent.toString);
200                 }
201                 
202                 entityAsset.entity = New!Entity(parent, assetOwner);
203                 entityAsset.entity.visible = true;
204                 entityAsset.entity.castShadow = true;
205                 entityAsset.entity.useMotionBlur = true;
206                 entityAsset.entity.layer = 1;
207                 entityAsset.entity.solid = true;
208                 entityAsset.entity.material = scene.defaultMaterial3D;
209                 
210                 if ("position" in entityAsset.props)
211                 {
212                     entityAsset.entity.position = entityAsset.props.position.toVector3f;
213                 }
214     
215                 if ("rotation" in entityAsset.props)
216                 {
217                     entityAsset.entity.rotation = Quaternionf(entityAsset.props.rotation.toVector4f).normalized;
218                 }
219     
220                 if ("scale" in entityAsset.props)
221                 {
222                     entityAsset.entity.scaling = entityAsset.props.scale.toVector3f;
223                 }
224    
225                 entityAsset.entity.updateTransformation();
226                 
227                 if ("visible" in entityAsset.props)
228                 {
229                     entityAsset.entity.visible = entityAsset.props.visible.toBool;
230                 }
231                 
232                 if ("castShadow" in entityAsset.props)
233                 {
234                     entityAsset.entity.castShadow = entityAsset.props.castShadow.toBool;
235                 }
236                 
237                 if ("useMotionBlur" in entityAsset.props)
238                 {
239                     entityAsset.entity.useMotionBlur = entityAsset.props.useMotionBlur.toBool;
240                 }
241                 
242                 if ("solid" in entityAsset.props)
243                 {
244                     entityAsset.entity.solid = entityAsset.props.solid.toBool;
245                 }
246                 
247                 if ("layer" in entityAsset.props)
248                 {
249                     entityAsset.entity.layer = entityAsset.props.layer.toInt;
250                 }
251                 
252                 if ("mesh" in entityAsset.props)
253                 {
254                     entityAsset.entity.drawable = mesh(entityAsset.props.mesh.toString);
255                 }
256     
257                 if ("material" in entityAsset.props)
258                 {
259                     entityAsset.entity.material = material(entityAsset.props.material.toString);
260                 }
261                 
262                 if (entityAsset.entity.parent)
263                     scene.sortEntities(entityAsset.entity.parent.children);
264     
265                 return entityAsset.entity;
266             }
267             else
268             {
269                 return null;
270             }
271         }
272         else
273         {
274             return entities[filename].entity;
275         }
276     }
277     
278     Texture texture(string filename)
279     {
280         if (!(filename in textures))
281         {
282             TextureAsset texAsset = New!TextureAsset(assetManager.imageFactory, assetManager.hdrImageFactory, assetOwner);
283             if (loadAsset(texAsset, filename))
284             {
285                 textures[filename] = texAsset;
286                 return texAsset.texture;
287             }
288             else
289             {
290                 return null;
291             }
292         }
293         else
294         {
295             return textures[filename].texture;
296         }
297     }
298     
299     Material material(string filename)
300     {
301         if (!(filename in materials))
302         {
303             MaterialAsset matAsset = New!MaterialAsset(assetOwner);
304             if (loadAsset(matAsset, filename))
305             {
306                 materials[filename] = matAsset;
307                 matAsset.material = createMaterial();
308                 
309                 // diffuse
310                 if ("diffuse" in matAsset.props)
311                 {
312                     if (matAsset.props.diffuse.type == DPropType.String)
313                     {
314                         matAsset.material.diffuse = texture(matAsset.props.diffuse.toString);
315                     }
316                     else
317                     {
318                         Vector3f diffCol = matAsset.props.diffuse.toVector3f;
319                         matAsset.material.diffuse = Color4f(diffCol.r, diffCol.g, diffCol.b, 1.0f);
320                     }
321                 }
322                 
323                 // emission
324                 if ("emission" in matAsset.props)
325                 {
326                     if (matAsset.props.emission.type == DPropType.String)
327                     {
328                         matAsset.material.emission = texture(matAsset.props.emission.toString);
329                     }
330                     else
331                     {
332                         Vector3f emissionCol = matAsset.props.emission.toVector3f;
333                         matAsset.material.emission = Color4f(emissionCol.r, emissionCol.g, emissionCol.b, 1.0f);
334                     }
335                 }
336                 
337                 // energy
338                 if ("energy" in matAsset.props)
339                 {
340                     matAsset.material.energy = matAsset.props.energy.toFloat;
341                 }
342                 
343                 // normal
344                 if ("normal" in matAsset.props)
345                 {
346                     if (matAsset.props.normal.type == DPropType.String)
347                     {
348                         matAsset.material.normal = texture(matAsset.props.normal.toString);
349                     }
350                 }
351  
352                 // height
353                 if ("height" in matAsset.props)
354                 {
355                     if (matAsset.props.height.type == DPropType.String)
356                     {
357                         matAsset.material.height = texture(matAsset.props.height.toString);
358                     }
359                 }
360                 
361                 // roughness
362                 if ("roughness" in matAsset.props)
363                 {
364                     if (matAsset.props.roughness.type == DPropType.String)
365                     {
366                         matAsset.material.roughness = texture(matAsset.props.roughness.toString);
367                     }
368                     else
369                     {
370                         matAsset.material.roughness = matAsset.props.roughness.toFloat;
371                     }
372                 }
373                 
374                 // metallic
375                 if ("metallic" in matAsset.props)
376                 {
377                     if (matAsset.props.metallic.type == DPropType.String)
378                     {
379                         matAsset.material.metallic = texture(matAsset.props.metallic.toString);
380                     }
381                     else
382                     {
383                         matAsset.material.metallic = matAsset.props.metallic.toFloat;
384                     }
385                 }
386                 
387                 // parallax
388                 if ("parallax" in matAsset.props)
389                 {
390                     matAsset.material.parallax = matAsset.props.parallax.toInt;
391                 }
392                 
393                 // parallaxScale
394                 if ("parallaxScale" in matAsset.props)
395                 {
396                     matAsset.material.parallaxScale = matAsset.props.parallaxScale.toFloat;
397                 }
398                 
399                 // parallaxBias
400                 if ("parallaxBias" in matAsset.props)
401                 {
402                     matAsset.material.parallaxBias = matAsset.props.parallaxBias.toFloat;
403                 }
404                 
405                 // shadeless
406                 if ("shadeless" in matAsset.props)
407                 {
408                     matAsset.material.shadeless = matAsset.props.shadeless.toBool;
409                 }
410                 
411                 // culling
412                 if ("culling" in matAsset.props)
413                 {
414                     matAsset.material.culling = matAsset.props.culling.toBool;
415                 }
416                 
417                 // colorWrite
418                 if ("colorWrite" in matAsset.props)
419                 {
420                     matAsset.material.colorWrite = matAsset.props.colorWrite.toBool;
421                 }
422                 
423                 // depthWrite
424                 if ("depthWrite" in matAsset.props)
425                 {
426                     matAsset.material.depthWrite = matAsset.props.depthWrite.toBool;
427                 }
428                 
429                 // useShadows
430                 if ("useShadows" in matAsset.props)
431                 {
432                     matAsset.material.shadowsEnabled = matAsset.props.useShadows.toBool;
433                 }
434                 
435                 // useFog
436                 if ("useFog" in matAsset.props)
437                 {
438                     matAsset.material.fogEnabled = matAsset.props.useFog.toBool;
439                 }
440                 
441                 // shadowFilter
442                 if ("shadowFilter" in matAsset.props)
443                 {
444                     matAsset.material.shadowFilter = matAsset.props.shadowFilter.toInt;
445                 }
446                 
447                 // blendingMode
448                 if ("blendingMode" in matAsset.props)
449                 {
450                     matAsset.material.blending = matAsset.props.blendingMode.toInt;
451                 }
452                 
453                 // transparency
454                 if ("transparency" in matAsset.props)
455                 {
456                     matAsset.material.transparency = matAsset.props.transparency.toFloat;
457                 }
458                 
459                 return matAsset.material;
460             }
461             else
462             {
463                 return null;
464             }
465         }
466         else
467         {
468             return materials[filename].material;
469         }
470     }
471     
472     Entity entity()
473     {
474         if (index.length)
475         foreach(path; lineSplitter(index))
476         {
477             Entity e = entity(path);
478         }
479     
480         return rootEntity;
481     }
482     
483     GenericMaterial createMaterial()
484     {
485         GenericMaterial m = New!GenericMaterial(scene.defaultMaterialBackend, assetOwner);
486         return m;
487     }
488     
489     bool fileExists(string filename)
490     {
491         FileStat stat;
492         return boxfs.stat(filename, stat);
493     }
494 
495     override void release()
496     {
497         Delete(boxfs);
498         
499         Delete(meshes);
500         Delete(entities);
501         Delete(textures);
502         Delete(materials);
503         
504         Delete(assetOwner);
505 
506         rootEntity.release();
507 
508         if (index.length)
509             Delete(index);
510     }
511 }
512