source: OniSplit/Dae/IO/ObjReader.cs@ 1155

Last change on this file since 1155 was 1114, checked in by iritscen, 5 years ago

Adding OniSplit source code (v0.9.99.0). Many thanks to Neo for all his work over the years.

File size: 17.7 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.IO;
5
6namespace Oni.Dae.IO
7{
8 internal class ObjReader
9 {
10 private struct ObjVertex : IEquatable<ObjVertex>
11 {
12 public int PointIndex;
13 public int TexCoordIndex;
14 public int NormalIndex;
15
16 public ObjVertex(int pointIndex, int uvIndex, int normalIndex)
17 {
18 PointIndex = pointIndex;
19 TexCoordIndex = uvIndex;
20 NormalIndex = normalIndex;
21 }
22
23 public static bool operator ==(ObjVertex v1, ObjVertex v2) =>
24 v1.PointIndex == v2.PointIndex
25 && v1.TexCoordIndex == v2.TexCoordIndex
26 && v1.NormalIndex == v2.NormalIndex;
27
28 public static bool operator !=(ObjVertex v1, ObjVertex v2) =>
29 v1.PointIndex != v2.PointIndex
30 || v1.TexCoordIndex != v2.TexCoordIndex
31 || v1.NormalIndex != v2.NormalIndex;
32
33 public bool Equals(ObjVertex v) => this == v;
34 public override bool Equals(object obj) => obj is ObjVertex && Equals((ObjVertex)obj);
35 public override int GetHashCode() => PointIndex ^ TexCoordIndex ^ NormalIndex;
36 }
37
38 private class ObjFace
39 {
40 public string ObjectName;
41 public string[] GroupsNames;
42 public ObjVertex[] Vertices;
43 }
44
45 private class ObjMaterial
46 {
47 private readonly string name;
48 private string textureFilePath;
49 private Material material;
50
51 public ObjMaterial(string name)
52 {
53 this.name = name;
54 }
55
56 public string Name => name;
57
58 public string TextureFilePath
59 {
60 get { return textureFilePath; }
61 set { textureFilePath = value; }
62 }
63
64 public Material Material
65 {
66 get
67 {
68 if (material == null && TextureFilePath != null)
69 CreateMaterial();
70
71 return material;
72 }
73 }
74
75 private void CreateMaterial()
76 {
77 var image = new Image
78 {
79 FilePath = TextureFilePath,
80 Name = name + "_img"
81 };
82
83 var effectSurface = new EffectSurface(image);
84 var effectSampler = new EffectSampler(effectSurface);
85 var effectTexture = new EffectTexture
86 {
87 Sampler = effectSampler,
88 Channel = EffectTextureChannel.Diffuse,
89 TexCoordSemantic = "diffuse_TEXCOORD"
90 };
91
92 material = new Material
93 {
94 Id = name,
95 Name = name,
96 Effect = new Effect
97 {
98 Id = name + "_fx",
99 DiffuseValue = effectTexture,
100 Parameters = {
101 new EffectParameter("surface", effectSurface),
102 new EffectParameter("sampler", effectSampler)
103 }
104 }
105 };
106 }
107 }
108
109 private class ObjPrimitives
110 {
111 public ObjMaterial Material;
112 public readonly List<ObjFace> Faces = new List<ObjFace>(4);
113 }
114
115 #region Private data
116 private static readonly string[] emptyStrings = new string[0];
117 private static readonly char[] whiteSpaceChars = new char[] { ' ', '\t' };
118 private static readonly char[] vertexSeparator = new char[] { '/' };
119
120 private Scene mainScene;
121
122 private readonly List<Vector3> points = new List<Vector3>();
123 private readonly List<Vector2> texCoords = new List<Vector2>();
124 private readonly List<Vector3> normals = new List<Vector3>();
125
126 private int pointCount;
127 private int normalCount;
128 private int texCoordCount;
129
130 private readonly Dictionary<Vector3, int> pointIndex = new Dictionary<Vector3, int>();
131 private readonly Dictionary<Vector3, int> normalIndex = new Dictionary<Vector3, int>();
132 private readonly Dictionary<Vector2, int> texCoordIndex = new Dictionary<Vector2, int>();
133 private readonly List<int> pointRemap = new List<int>();
134 private readonly List<int> normalRemap = new List<int>();
135 private readonly List<int> texCoordRemap = new List<int>();
136
137 private readonly Dictionary<string, ObjMaterial> materials = new Dictionary<string, ObjMaterial>(StringComparer.Ordinal);
138
139 private string currentObjectName;
140 private string[] currentGroupNames;
141 private readonly List<ObjPrimitives> primitives = new List<ObjPrimitives>();
142 private ObjPrimitives currentPrimitives;
143 #endregion
144
145 public static Scene ReadFile(string filePath)
146 {
147 var reader = new ObjReader();
148 reader.ReadObjFile(filePath);
149 reader.ImportObjects();
150 return reader.mainScene;
151 }
152
153 private void ReadObjFile(string filePath)
154 {
155 mainScene = new Scene();
156
157 foreach (string line in ReadLines(filePath))
158 {
159 var tokens = line.Split(whiteSpaceChars, StringSplitOptions.RemoveEmptyEntries);
160
161 switch (tokens[0])
162 {
163 case "o":
164 ReadObject(tokens);
165 break;
166
167 case "g":
168 ReadGroup(tokens);
169 break;
170
171 case "v":
172 ReadPoint(tokens);
173 break;
174
175 case "vn":
176 ReadNormal(tokens);
177 break;
178
179 case "vt":
180 ReadTexCoord(tokens);
181 break;
182
183 case "f":
184 case "fo":
185 ReadFace(tokens);
186 break;
187
188 case "mtllib":
189 ReadMtlLib(filePath, tokens);
190 break;
191
192 case "usemtl":
193 ReadUseMtl(tokens);
194 break;
195 }
196 }
197 }
198
199 private void ReadPoint(string[] tokens)
200 {
201 var point = new Vector3(
202 float.Parse(tokens[1], CultureInfo.InvariantCulture),
203 float.Parse(tokens[2], CultureInfo.InvariantCulture),
204 float.Parse(tokens[3], CultureInfo.InvariantCulture));
205
206 AddPoint(point);
207
208 pointCount++;
209 }
210
211 private void AddPoint(Vector3 point)
212 {
213 int newIndex;
214
215 if (pointIndex.TryGetValue(point, out newIndex))
216 {
217 pointRemap.Add(newIndex);
218 }
219 else
220 {
221 pointRemap.Add(points.Count);
222 pointIndex.Add(point, points.Count);
223 points.Add(point);
224 }
225 }
226
227 private void ReadNormal(string[] tokens)
228 {
229 var normal = new Vector3(
230 float.Parse(tokens[1], CultureInfo.InvariantCulture),
231 float.Parse(tokens[2], CultureInfo.InvariantCulture),
232 float.Parse(tokens[3], CultureInfo.InvariantCulture));
233
234 AddNormal(normal);
235
236 normalCount++;
237 }
238
239 private void AddNormal(Vector3 normal)
240 {
241 int newIndex;
242
243 if (normalIndex.TryGetValue(normal, out newIndex))
244 {
245 normalRemap.Add(newIndex);
246 }
247 else
248 {
249 normalRemap.Add(normals.Count);
250 normalIndex.Add(normal, normals.Count);
251 normals.Add(normal);
252 }
253 }
254
255 private void ReadTexCoord(string[] tokens)
256 {
257 var texCoord = new Vector2(
258 float.Parse(tokens[1], CultureInfo.InvariantCulture),
259 1.0f - float.Parse(tokens[2], CultureInfo.InvariantCulture));
260
261 AddTexCoord(texCoord);
262
263 texCoordCount++;
264 }
265
266 private void AddTexCoord(Vector2 texCoord)
267 {
268 int newIndex;
269
270 if (texCoordIndex.TryGetValue(texCoord, out newIndex))
271 {
272 texCoordRemap.Add(newIndex);
273 }
274 else
275 {
276 texCoordRemap.Add(texCoords.Count);
277 texCoordIndex.Add(texCoord, texCoords.Count);
278 texCoords.Add(texCoord);
279 }
280 }
281
282 private void ReadFace(string[] tokens)
283 {
284 var faceVertices = ReadVertices(tokens);
285
286 if (currentPrimitives == null)
287 ReadUseMtl(emptyStrings);
288
289 currentPrimitives.Faces.Add(new ObjFace
290 {
291 ObjectName = currentObjectName,
292 GroupsNames = currentGroupNames,
293 Vertices = faceVertices
294 });
295 }
296
297 private ObjVertex[] ReadVertices(string[] tokens)
298 {
299 var vertices = new ObjVertex[tokens.Length - 1];
300
301 for (int i = 0; i < vertices.Length; i++)
302 {
303 //
304 // Read a point/texture/normal index pair
305 //
306
307 var indices = tokens[i + 1].Split(vertexSeparator);
308
309 if (indices.Length == 0 || indices.Length > 3)
310 throw new InvalidDataException();
311
312 //
313 // Extract indices from file: 0 means "not specified"
314 //
315
316 int pointIndex = int.Parse(indices[0], CultureInfo.InvariantCulture);
317 int texCoordIndex = (indices.Length > 1 && indices[1].Length > 0) ? int.Parse(indices[1], CultureInfo.InvariantCulture) : 0;
318 int normalIndex = (indices.Length > 2 && indices[2].Length > 0) ? int.Parse(indices[2], CultureInfo.InvariantCulture) : 0;
319
320 //
321 // Adjust for negative indices
322 //
323
324 if (pointIndex < 0)
325 pointIndex = pointCount + pointIndex + 1;
326
327 if (texCoordIndex < 0)
328 texCoordIndex = texCoordCount + texCoordIndex + 1;
329
330 if (normalIndex < 0)
331 normalIndex = normalCount + normalIndex + 1;
332
333 //
334 // Convert indices to internal representation: range 0..n and -1 means "not specified".
335 //
336
337 pointIndex = pointIndex - 1;
338 texCoordIndex = texCoordIndex - 1;
339 normalIndex = normalIndex - 1;
340
341 //
342 // Remap indices
343 //
344
345 pointIndex = pointRemap[pointIndex];
346
347 if (texCoordIndex < 0 || texCoordRemap.Count <= texCoordIndex)
348 texCoordIndex = -1;
349 else
350 texCoordIndex = texCoordRemap[texCoordIndex];
351
352 if (normalIndex < 0 || normalRemap.Count <= normalIndex)
353 normalIndex = -1;
354 else
355 normalIndex = normalRemap[normalIndex];
356
357 vertices[i] = new ObjVertex
358 {
359 PointIndex = pointIndex,
360 TexCoordIndex = texCoordIndex,
361 NormalIndex = normalIndex
362 };
363 }
364
365 return vertices;
366 }
367
368 private void ReadObject(string[] tokens)
369 {
370 currentObjectName = tokens[1];
371 }
372
373 private void ReadGroup(string[] tokens)
374 {
375 currentGroupNames = tokens;
376 }
377
378 private void ReadUseMtl(string[] tokens)
379 {
380 currentPrimitives = new ObjPrimitives();
381
382 if (tokens.Length > 0)
383 materials.TryGetValue(tokens[1], out currentPrimitives.Material);
384
385 primitives.Add(currentPrimitives);
386 }
387
388 private void ReadMtlLib(string objFilePath, string[] tokens)
389 {
390 string materialLibraryFilePath = tokens[1];
391
392 if (Path.GetExtension(materialLibraryFilePath).Length == 0)
393 materialLibraryFilePath += ".mtl";
394
395 var dirPath = Path.GetDirectoryName(objFilePath);
396 var mtlFilePath = Path.Combine(dirPath, materialLibraryFilePath);
397
398 if (!File.Exists(mtlFilePath))
399 {
400 Console.Error.WriteLine("Material file {0} does not exist", mtlFilePath);
401 return;
402 }
403
404 ReadMtlFile(mtlFilePath);
405 }
406
407 private void ReadMtlFile(string filePath)
408 {
409 var dirPath = Path.GetDirectoryName(filePath);
410
411 ObjMaterial currentMaterial = null;
412
413 foreach (string line in ReadLines(filePath))
414 {
415 var tokens = line.Split(whiteSpaceChars, StringSplitOptions.RemoveEmptyEntries);
416
417 switch (tokens[0])
418 {
419 case "newmtl":
420 currentMaterial = new ObjMaterial(tokens[1]);
421 materials[currentMaterial.Name] = currentMaterial;
422 break;
423
424 case "map_Kd":
425 string textureFilePath = Path.GetFullPath(Path.Combine(dirPath, tokens[1]));
426
427 if (File.Exists(textureFilePath))
428 currentMaterial.TextureFilePath = textureFilePath;
429
430 break;
431 }
432 }
433 }
434
435 private void ImportObjects()
436 {
437 var inputs = new List<IndexedInput>();
438 IndexedInput positionInput, texCoordInput, normalInput;
439
440 positionInput = new IndexedInput(Semantic.Position, new Source(points));
441 inputs.Add(positionInput);
442
443 if (texCoords.Count > 0)
444 {
445 texCoordInput = new IndexedInput(Semantic.TexCoord, new Source(texCoords));
446 inputs.Add(texCoordInput);
447 }
448 else
449 {
450 texCoordInput = null;
451 }
452
453 if (normals.Count > 0)
454 {
455 normalInput = new IndexedInput(Semantic.Normal, new Source(normals));
456 inputs.Add(normalInput);
457 }
458 else
459 {
460 normalInput = null;
461 }
462
463 var geometry = new Geometry
464 {
465 Vertices = { positionInput }
466 };
467
468 var geometryInstance = new GeometryInstance
469 {
470 Target = geometry
471 };
472
473 foreach (var primitive in primitives.Where(p => p.Faces.Count > 0))
474 {
475 var meshPrimtives = new MeshPrimitives(MeshPrimitiveType.Polygons, inputs);
476
477 foreach (var face in primitive.Faces)
478 {
479 meshPrimtives.VertexCounts.Add(face.Vertices.Length);
480
481 foreach (var vertex in face.Vertices)
482 {
483 positionInput.Indices.Add(vertex.PointIndex);
484
485 if (texCoordInput != null)
486 texCoordInput.Indices.Add(vertex.TexCoordIndex);
487
488 if (normalInput != null)
489 normalInput.Indices.Add(vertex.NormalIndex);
490 }
491 }
492
493 geometry.Primitives.Add(meshPrimtives);
494
495 if (primitive.Material != null && primitive.Material.Material != null)
496 {
497 meshPrimtives.MaterialSymbol = "mat" + geometryInstance.Materials.Count;
498
499 geometryInstance.Materials.Add(new MaterialInstance
500 {
501 Symbol = meshPrimtives.MaterialSymbol,
502 Target = primitive.Material.Material,
503 Bindings = { new MaterialBinding("diffuse_TEXCOORD", texCoordInput) }
504 });
505 }
506 }
507
508 mainScene.Nodes.Add(new Node
509 {
510 Instances = { geometryInstance }
511 });
512 }
513
514 private Vector3 ComputeFaceNormal(ObjVertex[] vertices)
515 {
516 if (vertices.Length < 3)
517 return Vector3.Up;
518
519 var v1 = points[vertices[0].PointIndex];
520 var v2 = points[vertices[1].PointIndex];
521 var v3 = points[vertices[2].PointIndex];
522
523 return Vector3.Normalize(Vector3.Cross(v2 - v1, v3 - v1));
524 }
525
526 private static IEnumerable<string> ReadLines(string filePath)
527 {
528 using (var reader = File.OpenText(filePath))
529 {
530 for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
531 {
532 line = line.Trim();
533
534 if (line.Length == 0)
535 continue;
536
537 int commentStart = line.IndexOf('#');
538
539 if (commentStart != -1)
540 {
541 line = line.Substring(0, commentStart).Trim();
542
543 if (line.Length == 0)
544 continue;
545 }
546
547 yield return line;
548 }
549 }
550 }
551 }
552}
Note: See TracBrowser for help on using the repository browser.