1 module evael.graphics.models.Wavefront;
2 
3 import std.stdio;
4 import std.array : split;
5 import std.algorithm;
6 import std.string : indexOf;
7 import std.conv : to;
8 
9 import dnogc.DynamicArray;
10 
11 import evael.graphics.Drawable;
12 import evael.graphics.GraphicsDevice;
13 import evael.graphics.shaders.BasicLightShader;
14 import evael.graphics.Vertex;
15 import evael.graphics.Texture;
16 import evael.graphics.models.Model;
17 import evael.graphics.models.BoundingBox;
18 
19 import evael.system.Asset;
20 import evael.system.AssetLoader;
21 
22 import evael.utils.Math;
23 import evael.utils.Color;
24 
25 class Wavefront	: Model
26 {
27 	enum MaterialType : ubyte
28 	{
29 		Texture,
30 		Color
31 	}
32 
33 	struct Material
34 	{
35 		public MaterialType type;
36 		public Color color;
37 		public Texture texture;
38 	}
39 
40 	/// Current bb
41 	private BoundingBox m_currentBoundingBox;
42 
43 	private uint m_vertexBuffer, m_indexBuffer;
44 
45 	private Texture m_texture;
46 
47 	/// Triangles number
48 	private int m_trianglesNumber;
49 
50 	private PolygonDefinition m_navPolygon;
51 
52 	public this(GraphicsDevice graphicsDevice)
53 	{
54 		super(graphicsDevice);
55 	}
56 
57 	public override void dispose()
58 	{
59 		this.m_navPolygon.dispose();
60 	}
61 
62 	public override void drawInstances(in bool bindTexture = true)
63 	{
64 		if (this.m_instancesCount != 0)
65 		{
66 			if (bindTexture && this.m_texture !is null)
67 			{
68 				this.m_graphicsDevice.bindTexture(this.m_texture);
69 			}
70 
71 			this.m_graphicsDevice.bindVAO(this.m_vao);
72 
73 			gl.DrawArraysInstanced(GL_TRIANGLES, 0, this.m_trianglesNumber * 3, this.m_instancesCount);
74 
75 			this.m_graphicsDevice.bindVAO(0);
76 		}
77 	}
78 
79 	public override void draw(in float deltaTime, mat4 view, mat4 projection)
80 	{
81 		this.m_graphicsDevice.enableShader(this.m_shader);
82 
83 		if (this.m_texture !is null)
84 		{
85 			this.m_graphicsDevice.bindTexture(this.m_texture);
86 		}
87 
88 		mat4 translation = translationMatrix(this.m_position);
89 		mat4 rotation = this.m_rotation.toMatrix4x4();
90 		mat4 model = translation * rotation;
91 	
92 		this.m_graphicsDevice.setMatrix(this.m_shader.modelMatrix, model.arrayof.ptr);
93 		this.m_graphicsDevice.setMatrix(this.m_shader.viewMatrix, view.arrayof.ptr);
94 		this.m_graphicsDevice.setMatrix(this.m_shader.projectionMatrix, projection.arrayof.ptr);
95 
96 		this.m_graphicsDevice.setEnvironment();
97 
98 		this.m_graphicsDevice.bindVAO(this.m_vao);
99 		this.m_graphicsDevice.drawPrimitives!(PrimitiveType.Triangle)(this.m_trianglesNumber);
100 		this.m_graphicsDevice.bindVAO(0);
101 
102 		this.m_graphicsDevice.clearTexture();
103 
104 		this.m_graphicsDevice.disableShader();
105 	}
106 
107 	static Wavefront load(in string fileName)
108 	{
109 		import evael.utils.Config;
110 
111 		File file = File(Config.Paths.models!string ~ fileName);
112 		scope(exit) file.close;
113 
114 		vec3[] vertices;
115 		vec3[] normals;
116 		vec2[] uvs;
117 
118 		uint[] indices;
119 		uint[] uvsIndices;
120 		uint[] normalsIndices;
121 
122 		Material defaultMaterial = Material(MaterialType.Color, Color.White, null);
123 		Material currentMaterial = defaultMaterial;
124 
125 		auto materials = loadMaterials(Config.Paths.models!string ~ fileName[0 .. $ - 3] ~ "mtl");
126 
127 		Texture texture;
128 
129 		while (!file.eof())
130 		{
131 			string lineHeader = file.readln();
132 
133 			if (lineHeader.length == 0)
134 				break;
135 
136 			immutable string[] lineData = lineHeader.split();
137 
138 			if (lineHeader.startsWith("v "))
139 			{
140 				vertices ~= vec3(lineData[1].to!float(), lineData[2].to!float(), lineData[3].to!float());
141 			}
142 			else if (lineHeader.startsWith("vt"))
143 			{
144 				uvs ~= vec2(lineData[1].to!float(), lineData[2].to!float());
145 			}
146 			else if (lineHeader.startsWith("vn"))
147 			{
148 				normals ~= vec3(lineData[1].to!float(), lineData[2].to!float(), lineData[3].to!float());
149 			}
150 			else if (lineHeader.startsWith("f"))
151 			{
152 				immutable string[] faceOne = lineData[1].split('/');
153 				immutable string[] faceTwo = lineData[2].split('/');
154 				immutable string[] faceThree = lineData[3].split('/');
155 
156 				// Indice start at 1 in obj format
157 				indices ~= [faceOne[0].to!uint()  - 1, faceTwo[0].to!uint() - 1, faceThree[0].to!uint() - 1];
158 
159 				if (uvs.length)
160 				{
161 					uvsIndices ~= [faceOne[1].to!uint()  - 1, faceTwo[1].to!uint() - 1, faceThree[1].to!uint() - 1];
162 				}
163 
164 				if (normals.length)
165 				{
166 					normalsIndices ~= [faceOne[2].to!uint()  - 1, faceTwo[2].to!uint() - 1, faceThree[2].to!uint() - 1];
167 				}
168 			}
169 			else if (lineHeader.startsWith("usemtl"))
170 			{
171 				currentMaterial = materials.require(lineData[1], defaultMaterial);
172 				texture = currentMaterial.texture;
173 			}
174 		}
175 
176 		auto outVertices = new VertexPositionColorNormalTexture[indices.length];
177 
178 		foreach (i, indice; indices)
179 		{
180 			vec2 uv = vec2(1);
181 			vec3 normal = vec3(1, 0, 0);
182 
183 			if (uvsIndices.length)
184 			{
185 				uv = uvs[uvsIndices[i]];
186 			}
187 
188 			if (normalsIndices.length)
189 			{
190 				normal = normals[normalsIndices[i]];
191 			}
192 
193 			outVertices[i] = VertexPositionColorNormalTexture(vertices[indices[i]], currentMaterial.color, normal, uv);
194 		}
195 
196 		auto graphicsDevice = GraphicsDevice.getInstance();
197 
198 		auto obj = new Wavefront(graphicsDevice);
199 		obj.m_texture = texture;
200 		obj.m_trianglesNumber = outVertices.length / 3;
201 		obj.m_vertexBuffer = graphicsDevice.createVertexBuffer(VertexPositionColorNormalTexture.sizeof * outVertices.length, outVertices.ptr);
202 		//obj.m_indexBuffer = graphicsDevice.createIndexBuffer(uint.sizeof * indices.length, indices.ptr);
203 		obj.m_shader = AssetLoader.getInstance().load!(BasicLightShader)("textured_primitive_light");
204 
205 		graphicsDevice.setVertexBuffer!VertexPositionColorNormalTexture(obj.m_vertexBuffer);
206 		graphicsDevice.bindVAO(0);
207 
208 		/* BoundingBox */
209 		auto minX = minCount!("a.x < b.x")(vertices)[0];
210 		auto minY = minCount!("a.y < b.y")(vertices)[0];
211 		auto minZ = minCount!("a.z < b.z")(vertices)[0];
212 
213 		auto maxX = minCount!("a.x > b.x")(vertices)[0];
214 		auto maxY = minCount!("a.y > b.y")(vertices)[0];
215 		auto maxZ = minCount!("a.z > b.z")(vertices)[0];
216 
217 		auto boundingBox = BoundingBox(vec3(minX.x, minY.y, minZ.z), vec3(maxX.x, maxY.y, maxZ.z));
218 		obj.m_currentBoundingBox = boundingBox;
219 		obj.m_navPolygon = loadNavPolygon(Config.Paths.models!string ~ fileName[0.. $ - 3] ~ "nav", boundingBox);
220 
221 		return obj;
222 	}
223 
224 	/**
225 	 * Loads nav polygon
226 	 */
227 	static PolygonDefinition loadNavPolygon(in string navName, in ref BoundingBox boundingBox)
228 	{
229 		import std.file : exists;
230 		import core.stdc.stdio;
231 		import std.string : toStringz;
232 
233 		if (!exists(navName))
234 		{
235 			// We try to build navpolygon from boundingbox values
236 			auto polygon = PolygonDefinition(4);
237 			polygon ~= boundingBox.min; 												// Bottom front-left
238 			polygon ~= vec3(boundingBox.min.x, boundingBox.min.y, boundingBox.max.z);  // Bottom back-left
239 			polygon ~= vec3(boundingBox.max.x, boundingBox.min.y, boundingBox.max.z);  // Bottom back-right
240 			polygon ~= vec3(boundingBox.max.x, boundingBox.min.y, boundingBox.min.z);	// Bottom front-right
241 
242 			return polygon;
243 		}
244 
245 		/**
246 		* VERTICES_COUNT : INT
247 		* FACE : INT , INT , INT , INT , ...
248 		* VERTICES : FLOAT, FLOAT, FLOAT
249 		* VERTICES : FLOAT, FLOAT, FLOAT
250 		* ...
251 		*/
252 		auto file = fopen(navName.toStringz(), "rb");
253 		scope(exit) { fclose(file); }
254 
255 		int verticesCount;
256 		fread(&verticesCount, int.sizeof, 1, file);
257 
258 		auto indices = DynamicArray!int(verticesCount / 3);
259 		indices.length = verticesCount / 3;
260 		fread(indices.ptr, int.sizeof, verticesCount / 3, file);
261 
262 		auto vertices = DynamicArray!vec3(indices.length);
263 		vertices.length = indices.length;
264 		fread(vertices.ptr, vec3.sizeof, indices.length, file);
265 
266 		auto polygon = PolygonDefinition(indices.length);
267 
268 		// We need to defines vertices in good order
269 		foreach(i, indice; indices)
270 		{
271 			polygon ~= vertices[indice - 1];
272 		}
273 
274 		return polygon;
275 	}
276 
277 	/**
278 	 * Loads wavefront material.
279 	 * Params:
280 	 *		materialName : material file name
281 	 */
282 	static Material[string] loadMaterials(in string materialName)
283 	{
284 		auto file = File(materialName);
285 		scope(exit) file.close;
286 
287 		Material[string] materials;
288 
289 		string currentMaterialName = "";
290 
291 		while (!file.eof())
292 		{
293 			immutable string lineHeader = file.readln();
294 
295 			immutable string[] data = lineHeader.split();
296 
297 			if (lineHeader.startsWith("newmtl"))
298 			{
299 				currentMaterialName = data[1];
300 
301 				materials[currentMaterialName] = Material(MaterialType.Color);
302 			}
303 			else if (lineHeader.startsWith("map_Kd"))
304 			{
305 				import std.path : baseName;
306 
307 				// Texture
308 				string textureName = data[$ - 1];
309 
310 				materials[currentMaterialName].type = MaterialType.Texture;
311 				materials[currentMaterialName].texture = AssetLoader.getInstance().load!(Texture)(textureName.baseName(), false);
312 			}
313 			else if (lineHeader.startsWith("Kd"))
314 			{
315 				// Color
316 				materials[currentMaterialName].color = Color(cast(ubyte)(data[1].to!float * 255),
317 															 cast(ubyte)(data[2].to!float * 255),
318 															 cast(ubyte)(data[3].to!float * 255));
319 			}
320 		}
321 
322 		return materials;
323 	}
324 
325 	@nogc @safe
326 	@property pure nothrow
327 	{
328 
329 		public BoundingBox currentBoundingBox() const
330 		{
331 			return this.m_currentBoundingBox;
332 		}
333 
334 		public void currentBoundingBox(in ref BoundingBox value)
335 		{
336 			this.m_currentBoundingBox = value;
337 		}
338 
339 		public PolygonDefinition navPolygon()
340 		{
341 			return this.m_navPolygon;
342 		}
343 
344 		public int trianglesNumber() const
345 		{
346 			return this.m_trianglesNumber;
347 		}
348 	}
349 }
350