1 module armos.graphics.model;
2 
3 import armos.graphics;
4 import armos.math;
5 import armos.types;
6 
7 /++
8 3Dモデルを読み込み,描画するclassです.
9 +/
10 class Model {
11     public{
12         Mesh[] meshes;
13         Material[] materials;
14         Entity[] entities;
15 
16         /++
17             モデルを読み込みます.
18 
19             読み込み時にmeshはmaterial毎に分割されます.
20         +/
21         Model load(in string pathInDataDir){
22             auto loadedModel = (new AssimpModelLoader).load(pathInDataDir);
23             meshes    = loadedModel.meshes;
24             materials = loadedModel.materials;
25             entities  = loadedModel.entities;
26             return this;
27         }
28 
29         /++
30             読み込まれたmeshの数を返します.
31         +/
32         size_t numMeshes()const{
33             return meshes.length;
34         }
35 
36         /++
37             読み込まれたmaterialの数を返します.
38         +/
39         size_t numMaterials()const{
40             return materials.length;
41         }
42 
43         /++
44             modelを描画します.
45             Params:
46             renderMode = 面,線,点のどれを描画するか指定します.
47         +/
48         Model draw(in PolyRenderMode renderMode){
49             foreach (entity; entities) {
50                 entity.draw(renderMode);
51             }
52             return this;
53         };
54 
55         /++
56             modelをワイヤフレームで描画します.
57         +/
58         Model drawWireFrame(){
59             draw(PolyRenderMode.WireFrame);
60             return this;
61         };
62 
63         /++
64             modelの頂点を点で描画します.
65         +/
66         Model drawVertices(){
67             draw(PolyRenderMode.Points);
68             return this;
69         };
70 
71         /++
72             meshの面を塗りつぶして描画します.
73         +/
74         Model drawFill(){
75             draw(PolyRenderMode.Fill);
76             return this;
77         };
78 
79     }//public
80 
81     private{
82     }//private
83 }//class Model
84 
85 import derelict.assimp3.assimp;
86 import std.stdio;
87 import std.algorithm : map;
88 import std.array : array, Appender;
89 
90 /++
91     Assimpによりmodelの読み込みを行います.
92 +/
93 class AssimpModelLoader {
94     public{
95         ~this(){
96             clear;
97         }
98 
99         /// modelを読み込みそれを返します.
100         Model load(in string pathInDataDir){
101             import std.string;
102             import armos.utils;
103             _modelfilepath = absolutePath(pathInDataDir);
104 
105             import derelict.assimp3.assimp;
106             import std.stdio;
107             DerelictASSIMP3.load();
108 
109             loadScene(_modelfilepath);
110             
111             return createdModel(_scene);
112         }
113 
114         /// 読み込んだモデルを削除します.
115         void clear(){
116             if(_isLoaded){
117                 aiReleaseImport(_scene);
118                 _isLoaded = false;
119             }
120         }
121 
122 
123     }//public 
124     
125     private{
126 
127         ///
128         void loadScene(in string fileName){
129             auto f = File(fileName, "r");
130 
131             char[] str;
132             while (f.readln(str)){
133                 // write(str);
134             }
135 
136             aiPropertyStore* store = aiCreatePropertyStore();
137             immutable uint flags = aiProcess_CalcTangentSpace
138                 | aiProcess_Triangulate
139                 | aiProcess_JoinIdenticalVertices
140                 | aiProcess_SortByPType;
141 
142             import std.string;
143             _scene = cast(aiScene*)aiImportFile(fileName.toStringz, flags);
144             // _scene = cast(aiScene*)aiImportFileExWithProperties(str.ptr , flags, null, store);
145 
146             if(_scene){
147                 _isLoaded = true;
148             }
149         }
150 
151         ///
152         static string fromAiString(const(aiString) s) @safe pure {
153             return s.data[0 .. s.length].idup;
154         }
155 
156         ///
157         static armos.types.Color fromAiColor(ref const(aiColor4D) c){
158             return armos.types.Color(c.r*armos.types.Color.limit, c.g*armos.types.Color.limit, c.b*armos.types.Color.limit, c.a*armos.types.Color.limit);
159         }
160 
161         ///
162         static Vector3f fromAiVector3f(ref const( aiVector3D ) v){
163             return Vector3f(v.x, v.y, v.z);
164         }
165 
166         static Vector2f fromAiVector2f(ref const aiVector2D v){
167             return Vector2f(v.x, v.y);
168         }
169         
170         Model createdModel(const(aiScene)* scene)const{
171             import std.stdio;
172             auto t = _scene.mRootNode.mTransformation;
173             Matrix4f rootTransformation = Matrix4f([
174                 [t.a1, t.a2, t.a3, t.a4], 
175                 [t.b1, t.b2, t.b3, t.b4], 
176                 [t.c1, t.c2, t.c3, t.c4], 
177                 [t.d1, t.d2, t.d3, t.d4], 
178             ]);
179 
180             import std.range;
181             auto model = new Model;
182             model.materials = scene.mMaterials[0 .. scene.mNumMaterials]
183                                    .map!(m => createdMaterial(m))
184                                    .array;
185 
186             model.meshes    = scene.mMeshes[0 .. scene.mNumMeshes]
187                                    .map!(m => createdMesh(m, rootTransformation))
188                                    .array;
189             
190             model.entities  = scene.mMeshes[0 .. scene.mNumMeshes]
191                                    .length
192                                    .iota
193                                    .map!( 
194                                            i => (new Entity).mesh(model.meshes[i])
195                                                                             .material(model.materials[scene.mMeshes[i].mMaterialIndex])
196                                         )
197                                    .array;
198             return model;
199         }
200 
201         Material createdMaterial(const(aiMaterial)* material) const {
202             aiString aiName;
203             aiGetMaterialString(material, AI_MATKEY_NAME, 0, 0, &aiName);
204             auto name = fromAiString(aiName);
205 
206 
207             Material mat;
208             
209             //texture
210             aiString aiPath;
211             if(
212                     aiGetMaterialTexture(
213                         material,
214                         aiTextureType_DIFFUSE, 0, &aiPath,
215                         null, null, null, null, null 
216                         ) ==  aiReturn_SUCCESS
217               ){
218                 auto image = new Image;
219 
220                 import std.path;
221                 immutable string textureFileName = fromAiString( aiPath );
222                 image.load(buildPath( dirName(_modelfilepath), textureFileName ));
223                 
224                 mat = new DefaultMaterial;
225                 mat.texture("tex0", image.texture);
226             }else{
227                 mat = new NoTextureMaterial;
228             }
229             
230             aiColor4D color;
231 
232             //diffuse
233             aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, 0, 0, &color);
234             mat.uniform("diffuse", fromAiColor(color));
235 
236             //speculer
237             aiGetMaterialColor(material, AI_MATKEY_COLOR_SPECULAR, 0, 0, &color);
238             mat.uniform("speculer", fromAiColor(color));
239 
240             //ambient
241             aiGetMaterialColor(material, AI_MATKEY_COLOR_AMBIENT, 0, 0, &color);
242             mat.uniform("ambient", fromAiColor(color));
243 
244             return mat;
245         }
246 
247         Mesh createdMesh(const(aiMesh)* mesh, Matrix4f matrix) const {
248         // Mesh createMesh(const(aiMesh)* mesh, Material[] materials) const {
249             auto name = fromAiString(mesh.mName);
250 
251             auto vertices = 
252                 mesh.mVertices[0 .. mesh.mNumVertices]
253                 .map!(v => fromAiVector3f(v))
254                 .array;
255             foreach (ref v3; vertices) {
256                 auto v4 = Vector4f(v3.x, v3.y, v3.z, 0);
257                 v3 = (matrix*v4).xyz;
258             }
259 
260             const(Vector3f)[] normals;
261             if(mesh.mNormals !is null) {
262                 normals = mesh.mNormals[0 .. mesh.mNumVertices]
263                     .map!(n => fromAiVector3f(n))
264                     .array;
265             }
266 
267             auto convertedMesh= new Mesh;
268 
269             if(mesh.mTextureCoords[0] !is null){
270                 convertedMesh.texCoords = new Vector4f[mesh.mNumVertices];
271                 for (int i = 0; i < mesh.mNumVertices; i++) {
272                     convertedMesh.texCoords[i] = Vector4f(mesh.mTextureCoords[0][i].x, mesh.mTextureCoords[0][i].y, 0f, 1f);
273                 }
274             }
275             
276             convertedMesh.vertices = vertices.map!((Vector3f vec){
277                     return Vector4f(vec[0], vec[1], vec[2], 1f);
278                     }).array;
279 
280             auto totalIndices = 0;
281             foreach(f; mesh.mFaces[0 .. mesh.mNumFaces]) {
282                 totalIndices += f.mNumIndices;
283             }
284             
285             convertedMesh.indices = new int[totalIndices];
286             auto indexCounter = 0;
287             foreach(f; mesh.mFaces[0 .. mesh.mNumFaces]) {
288                 immutable int numVertices =  f.mNumIndices;
289                 foreach(i; f.mIndices[0 .. numVertices]){
290                     convertedMesh.indices[indexCounter] = i;
291                     indexCounter++;
292                 }
293             }
294             // immutable mi = mesh.mMaterialIndex;
295             // auto material = (mi < materials.length) ? materials[mi] : null;
296             // convertedMesh.material = material;
297 
298             return convertedMesh;
299         }
300 
301         aiScene* _scene;
302         bool _isLoaded = false; 
303         string _modelfilepath;
304     }//private
305 }//class AssimpModelLoader
306 
307 ///
308 class NoTextureMaterial : Material{
309     mixin MaterialImpl;
310     
311     ///
312     this(){
313         _shader = new armos.graphics.Shader;
314         _shader.loadSources(noTextureVertexShaderSource, "", noTextureFragmentShaderSource);
315     }
316 }//class NoTextureMaterial
317 
318 private immutable string noTextureVertexShaderSource = q{
319 #version 330
320 
321 uniform mat4 modelViewMatrix;
322 uniform mat4 projectionMatrix;
323 uniform mat4 modelViewProjectionMatrix;
324 
325 in vec4 vertex;
326 in vec3 normal;
327 in vec3 tangent;
328 in vec4 color;
329 
330 out vec4 f_color;
331 
332 void main(void) {
333     gl_Position = modelViewProjectionMatrix * vertex;
334     f_color = color;
335 }
336 };
337 
338 private immutable string noTextureFragmentShaderSource = q{
339 #version 330
340     
341 in vec4 f_color;
342 
343 out vec4 fragColor;
344 
345 uniform vec4 diffuse;
346 
347 void main(void) {
348     fragColor = diffuse;
349 }
350 };