1 module des.assimp.loader;
2 
3 import derelict.assimp3.assimp;
4 import derelict.assimp3.types;
5 
6 import des.util.helpers;
7 import des.util.arch;
8 import des.util.data.type;
9 import des.util.stdext..string;
10 
11 import des.assimp.mesh;
12 
13 ///
14 class SMLoaderException : Exception
15 {
16     ///
17     this( string msg, string file=__FILE__, size_t line=__LINE__ ) pure nothrow @safe
18     { super( msg, file, line ); }
19 }
20 
21 ///
22 class SMLoader : DesObject, SMMeshGenerator
23 {
24     mixin DES;
25 protected:
26 
27     string scene_file_name;
28     const(aiScene)* scene;
29 
30     string sourceName() const @property
31     { return "scene '" ~ scene_file_name ~ "'"; }
32 
33 public:
34 
35     /// process scene before loading, use Assimp3 documentation for more information
36     enum PostProcess
37     {
38         /// Calculates the tangents and bitangents for the imported meshes.
39         CalcTangentSpace = aiProcess_CalcTangentSpace,
40 
41         /// Identifies and joins identical vertex data sets within all imported meshes.
42         JoinIdenticalVertices = aiProcess_JoinIdenticalVertices,
43 
44         /// Converts all the imported data to a left-handed coordinate space.
45         MakeLeftHanded = aiProcess_MakeLeftHanded,
46 
47         /// Triangulates all faces of all meshes.
48         Triangulate = aiProcess_Triangulate,
49 
50         /++ Removes some parts of the data structure (animations, materials,
51             light sources, cameras, textures, vertex components).  +/
52         //RemoveComponent = aiProcess_RemoveComponent,
53 
54         /// Generates normals for all faces of all meshes.
55         GenNormals = aiProcess_GenNormals,
56 
57         /// Generates smooth normals for all vertices in the mesh.
58         GenSmoothNormals = aiProcess_GenSmoothNormals,
59 
60         /// Splits large meshes into smaller sub-meshes.
61         SplitLargeMeshes = aiProcess_SplitLargeMeshes,
62 
63         /++ <hr>Removes the node graph and pre-transforms all vertices with
64         the local transformation matrices of their nodes. +/
65         PreTransformVertices = aiProcess_PreTransformVertices,
66 
67         /// Limits the number of bones simultaneously affecting a single vertex to a maximum value.
68         LimitBoneWeights = aiProcess_LimitBoneWeights,
69 
70         /// Validates the imported scene data structure.
71         ValidateDataStructure = aiProcess_ValidateDataStructure,
72 
73         /// Reorders triangles for better vertex cache locality.
74         ImproveCacheLocality = aiProcess_ImproveCacheLocality,
75 
76         /// Searches for redundant/unreferenced materials and removes them.
77         RemoveRedundantMaterials = aiProcess_RemoveRedundantMaterials,
78 
79         /++ This step tries to determine which meshes have normal vectors
80             that are facing inwards and inverts them. +/
81         FixInFacingNormals = aiProcess_FixInFacingNormals,
82 
83         /++ This step splits meshes with more than one primitive type in
84             homogeneous sub-meshes. +/
85         SortByPType = aiProcess_SortByPType,
86 
87         /++ This step searches all meshes for degenerate primitives and
88             converts them to proper lines or points. +/
89         FindDegenerates = aiProcess_FindDegenerates,
90 
91         /++ This step searches all meshes for invalid data, such as zeroed
92             normal vectors or invalid UV coords and removes/fixes them. This is
93             intended to get rid of some common exporter errors. +/
94         FindInvalidData = aiProcess_FindInvalidData,
95 
96         /++ This step converts non-UV mappings (such as spherical or
97             cylindrical mapping) to proper texture coordinate channels. +/
98         GenUVCoords = aiProcess_GenUVCoords,
99 
100         /++ This step applies per-texture UV transformations and bakes
101             them into stand-alone vtexture coordinate channels. +/
102         TransformUVCoords = aiProcess_TransformUVCoords,
103 
104         /++ This step searches for duplicate meshes and replaces them
105             with references to the first mesh. +/
106         FindInstances = aiProcess_FindInstances,
107 
108         /// A postprocessing step to reduce the number of meshes.
109         OptimizeMeshes = aiProcess_OptimizeMeshes,
110 
111         /// A postprocessing step to optimize the scene hierarchy.
112         OptimizeGraph = aiProcess_OptimizeGraph,
113 
114         /++ This step flips all UV coordinates along the y-axis and adjusts
115             material settings and bitangents accordingly. +/
116         FlipUVs = aiProcess_FlipUVs,
117 
118         /// This step adjusts the output face winding order to be CW.
119         FlipWindingOrder = aiProcess_FlipWindingOrder,
120 
121         /++ This step splits meshes with many bones into sub-meshes so that each
122             su-bmesh has fewer or as many bones as a given limit. +/
123         SplitByBoneCount = aiProcess_SplitByBoneCount,
124 
125         /// This step removes bones losslessly or according to some threshold.
126         Debone = aiProcess_Debone,
127 
128         // aiProcess_GenEntityMeshes = 0x100000,
129         // aiProcess_OptimizeAnimations = 0x200000
130         // aiProcess_FixTexturePaths = 0x200000
131     };
132 
133     ///
134     this()
135     {
136         if( !DerelictASSIMP3.isLoaded )
137             DerelictASSIMP3.load();
138     }
139 
140     ///
141     PostProcess[] default_post_process = [ PostProcess.OptimizeMeshes,
142                                            PostProcess.CalcTangentSpace,
143                                            PostProcess.JoinIdenticalVertices,
144                                            PostProcess.Triangulate ];
145 
146     ///
147     void loadScene( string fname, PostProcess[] pp... )
148     {
149         scene_file_name = fname;
150         scene = aiImportFile( fname.toStringz,
151                 packBitMask( default_post_process ~ pp ) );
152     }
153 
154     ///
155     SMMesh getMesh( string name )
156     {
157         foreach( i; 0 .. scene.mNumMeshes )
158             if( toDStringFix( scene.mMeshes[i].mName.data ) == name )
159                 return convMesh( scene.mMeshes[i] );
160         throw new SMLoaderException( "no mesh '" ~ name ~
161                                      "' in " ~ sourceName );
162     }
163 
164     ///
165     SMMesh getMesh( size_t no )
166     {
167         if( no < scene.mNumMeshes )
168             return convMesh( scene.mMeshes[no] );
169         throw new SMLoaderException( "no mesh #" ~ to!string(no) ~
170                                      " in " ~ sourceName );
171     }
172 
173     ///
174     SMMesh[] getAllMeshes()
175     {
176         SMMesh[] ret;
177         foreach( i; 0 .. scene.mNumMeshes )
178             ret ~= convMesh( scene.mMeshes[i] );
179         return ret;
180     }
181 
182 protected:
183 
184     SMMesh convMesh( in aiMesh* m )
185     in{ assert( m !is null ); } body
186     {
187         return SMMesh
188         (
189             SMMesh.Type.TRIANGLES,
190             toDStringFix( m.mName.data ),
191             getIndices( m ),
192             getVertices( m ),
193             getTexCoords( m ),
194             getNormals( m ),
195             getTangents( m ),
196             getBitangents( m ),
197             getColors( m )
198         );
199     }
200 
201     uint[] getIndices( in aiMesh* m )
202     {
203         uint[] ret;
204 
205         foreach( i; 0 .. m.mNumFaces )
206         {
207             auto f = m.mFaces[i];
208             enforce( f.mNumIndices == 3, new SMLoaderException( "one or more faces is not triangle" ) );
209             ret ~= getTypedArray!uint( 3, f.mIndices ).arr;
210         }
211 
212         return ret;
213     }
214 
215     vec3[] getVertices( in aiMesh* m )
216     { return getVertVectors( m, m.mVertices ); }
217 
218     vec3[] getNormals( in aiMesh* m )
219     { return getVertVectors( m, m.mNormals ); }
220 
221     vec3[] getTangents( in aiMesh* m )
222     { return getVertVectors( m, m.mTangents ); }
223 
224     vec3[] getBitangents( in aiMesh* m )
225     { return getVertVectors( m, m.mBitangents ); }
226 
227     vec3[] getVertVectors( in aiMesh* m, in aiVector3D* buf )
228     {
229         if( buf is null ) return null;
230         return getTypedArray!vec3( m.mNumVertices, buf ).arr.dup;
231     }
232 
233     SMTexCoord[] getTexCoords( in aiMesh* m )
234     {
235         SMTexCoord[] ret;
236         foreach( t; 0 .. AI_MAX_NUMBER_OF_TEXTURECOORDS )
237         {
238             auto tc = m.mTextureCoords[t];
239             if( tc is null ) continue;
240             auto nvert = m.mNumVertices;
241             auto comp = m.mNumUVComponents[t];
242             auto buf = new float[]( comp * nvert );
243             foreach( i; 0 .. nvert )
244                 foreach( j; 0 .. comp )
245                     buf[i*comp+j] = *(cast(float*)( tc + i ) + j);
246             ret ~= SMTexCoord( comp, buf );
247         }
248         return ret;
249     }
250 
251     vec4[][] getColors( in aiMesh* m )
252     {
253         vec4[][] ret;
254 
255         foreach( i; 0 .. AI_MAX_NUMBER_OF_COLOR_SETS )
256         {
257             if( m.mColors[i] is null ) continue;
258             ret ~= getTypedArray!vec4( m.mNumVertices, m.mColors[i] ).arr.dup;
259         }
260 
261         return ret;
262     }
263 }