1 /** 2 * Defines the static Assets class, a static class which manages all textures, meshes, materials, and animations. 3 */ 4 module dash.components.assets; 5 import dash.core.properties, dash.components, dash.utility; 6 import dash.utility.data.serialization; 7 8 import std..string, std.array, std.algorithm; 9 10 import yaml; 11 import derelict.freeimage.freeimage, derelict.assimp3.assimp; 12 13 /** 14 * Assets manages all assets that aren't code, GameObjects, or Prefabs. 15 */ 16 abstract final class Assets 17 { 18 static: 19 private: 20 MaterialAsset[][Resource] materialResources; 21 22 package: 23 MeshAsset[string] meshes; 24 TextureAsset[string] textures; 25 MaterialAsset[string] materials; 26 27 public: 28 /// Basic quad, generally used for billboarding. 29 Mesh unitSquare; 30 31 /** 32 * Get a reference to the asset with the given type and name. 33 */ 34 AssetRefT get( AssetRefT )( string name ) if( is( AssetRefT AssetT : AssetRef!AssetT ) && !is( AssetRefT == AssetRef!AssetT ) ) 35 { 36 static if( is( AssetRefT AssetT : AssetRef!AssetT ) ) 37 return new AssetRefT( getAsset!AssetT( name ) ); 38 else static assert( false ); 39 } 40 41 /** 42 * Get the asset with the given type and name. 43 */ 44 AssetT getAsset( AssetT )( string name ) if( is( AssetT : Asset ) ) 45 { 46 enum get( Type, string array ) = q{ 47 static if( is( AssetT == $Type ) ) 48 { 49 if( auto result = name in $array ) 50 { 51 result.isUsed = true; 52 return *result; 53 } 54 else 55 { 56 errorf( "Unable to find %s in $array.", name ); 57 return null; 58 } 59 } 60 }.replaceMap( [ "$array": array, "$Type": Type.stringof ] ); 61 62 mixin( get!( MeshAsset, q{meshes} ) ); 63 mixin( get!( TextureAsset, q{textures} ) ); 64 mixin( get!( MaterialAsset, q{materials} ) ); 65 } 66 67 /** 68 * Load all assets in the FilePath.ResourceHome folder. 69 */ 70 void initialize() 71 { 72 DerelictFI.load(); 73 74 // Initial assimp start 75 DerelictASSIMP3.load(); 76 77 // Make sure fbxs are supported. 78 assert( aiIsExtensionSupported( ".fbx".toStringz ), "fbx format isn't supported by assimp instance!" ); 79 80 enum aiImportOptions = aiProcess_CalcTangentSpace | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType; 81 82 // Load the unitSquare 83 unitSquare = new Mesh( new MeshAsset( internalResource, aiImportFileFromMemory( 84 unitSquareMesh.toStringz(), unitSquareMesh.length, 85 aiImportOptions, "obj" ).mMeshes[0] ) ); 86 87 foreach( file; scanDirectory( Resources.Meshes ) ) 88 { 89 // Load mesh 90 const aiScene* scene = aiImportFile( file.fullPath.toStringz, aiImportOptions ); 91 assert( scene, "Failed to load scene file '" ~ file.fullPath ~ "' Error: " ~ aiGetErrorString().fromStringz() ); 92 93 // Add mesh 94 if( scene.mNumMeshes > 0 ) 95 { 96 if( file.baseFileName in meshes ) 97 warning( "Mesh ", file.baseFileName, " exsists more than once." ); 98 99 auto newMesh = new MeshAsset( file, scene.mMeshes[ 0 ] ); 100 101 if( scene.mNumAnimations > 0 ) 102 newMesh.animationData = new AnimationData( file, scene.mAnimations, scene.mNumAnimations, scene.mMeshes[ 0 ], scene.mRootNode ); 103 104 meshes[ file.baseFileName ] = newMesh; 105 } 106 else 107 { 108 warning( "Assimp did not contain mesh data, ensure you are loading a valid mesh." ); 109 } 110 111 // Release mesh 112 aiReleaseImport( scene ); 113 } 114 115 foreach( file; scanDirectory( Resources.Textures ) ) 116 { 117 if( file.baseFileName in textures ) 118 warningf( "Texture %s exists more than once.", file.baseFileName ); 119 120 textures[ file.baseFileName ] = new TextureAsset( file ); 121 } 122 123 foreach( res; scanDirectory( Resources.Materials ) ) 124 { 125 auto newMat = deserializeMultiFile!MaterialAsset( res ); 126 127 foreach( mat; newMat ) 128 { 129 if( mat.name in materials ) 130 warningf( "Material %s exists more than once.", mat.name ); 131 132 mat.resource = res; 133 materials[ mat.name ] = mat; 134 materialResources[ res ] ~= mat; 135 } 136 } 137 138 meshes.rehash(); 139 textures.rehash(); 140 materials.rehash(); 141 materialResources.rehash(); 142 } 143 144 /** 145 * Refresh the assets that have changed. 146 */ 147 void refresh() 148 { 149 enum refresh( string aaName ) = q{ 150 foreach_reverse( name; $aaName.keys ) 151 { 152 auto asset = $aaName[ name ]; 153 if( !asset.resource.exists ) 154 { 155 asset.shutdown(); 156 $aaName.remove( name ); 157 } 158 else if( asset.resource.needsRefresh ) 159 { 160 tracef( "Refreshing %s.", name ); 161 asset.refresh(); 162 } 163 } 164 }.replace( "$aaName", aaName ); 165 166 mixin( refresh!q{meshes} ); 167 mixin( refresh!q{textures} ); 168 169 // Iterate over each file, and it's materials 170 //TODO: Implement 171 } 172 173 /** 174 * Unload and destroy all stored assets. 175 */ 176 void shutdown() 177 { 178 enum shutdown( string aaName, string friendlyName ) = q{ 179 foreach_reverse( name; $aaName.keys ) 180 { 181 if( !$aaName[ name ].isUsed ) 182 warningf( "$friendlyName %s not used during this run.", name ); 183 184 $aaName[ name ].shutdown(); 185 $aaName.remove( name ); 186 } 187 }.replaceMap( [ "$aaName": aaName, "$friendlyName": friendlyName ] ); 188 189 mixin( shutdown!( q{meshes}, "Mesh" ) ); 190 mixin( shutdown!( q{textures}, "Texture" ) ); 191 mixin( shutdown!( q{materials}, "Material" ) ); 192 } 193 } 194 195 abstract class Asset 196 { 197 private: 198 bool _isUsed; 199 200 public: 201 /// Whether or not the material is actually used. 202 mixin( Property!( _isUsed, AccessModifier.Package ) ); 203 /// The resource containing this asset. 204 @ignore 205 Resource resource; 206 207 /** 208 * Creates asset with resource. 209 */ 210 this( Resource res ) 211 { 212 resource = res; 213 } 214 215 void initialize() { } 216 void update() { } 217 void refresh() { } 218 void shutdown() { } 219 } 220 221 /** 222 * A reference to an asset. 223 */ 224 abstract class AssetRef( AssetType ) : Component if( is( AssetType : Asset ) ) 225 { 226 public: 227 @ignore 228 AssetType asset; 229 230 @rename( "Asset" ) 231 string assetName; 232 233 this() { } 234 this( AssetType ass ) 235 { 236 asset = ass; 237 initialize(); 238 } 239 240 /// Is the asset null? 241 bool isNull() @property const pure @safe nothrow 242 { 243 return asset is null; 244 } 245 246 /// Gets a reference to it's asset. 247 override void initialize() 248 { 249 if( !asset && assetName ) 250 asset = Assets.getAsset!AssetType( assetName ); 251 } 252 } 253 254 /// Obj for a 1x1 square billboard mesh 255 immutable string unitSquareMesh = q{ 256 v -1.0 1.0 0.0 257 v -1.0 -1.0 0.0 258 v 1.0 1.0 0.0 259 v 1.0 -1.0 0.0 260 261 vt 0.0 0.0 262 vt 0.0 1.0 263 vt 1.0 0.0 264 vt 1.0 1.0 265 266 vn 0.0 0.0 1.0 267 268 f 4/3/1 3/4/1 1/2/1 269 f 2/1/1 4/3/1 1/2/1 270 };