< Summary

Information
Class: JeremyAnsel.Media.WavefrontObj.ObjFile
Assembly: JeremyAnsel.Media.WavefrontObj
File(s): C:\projects\jeremyansel-media-wavefrontobj\JeremyAnsel.Media.WavefrontObj\JeremyAnsel.Media.WavefrontObj\ObjFile.cs
Line coverage
96%
Covered lines: 94
Uncovered lines: 3
Coverable lines: 97
Total lines: 213
Line coverage: 96.9%
Branch coverage
89%
Covered branches: 41
Total branches: 46
Branch coverage: 89.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

C:\projects\jeremyansel-media-wavefrontobj\JeremyAnsel.Media.WavefrontObj\JeremyAnsel.Media.WavefrontObj\ObjFile.cs

#LineLine coverage
 1// <copyright file="ObjFile.cs" company="Jérémy Ansel">
 2// Copyright (c) 2017, 2019 Jérémy Ansel
 3// </copyright>
 4// <license>
 5// Licensed under the MIT license. See LICENSE.txt
 6// </license>
 7
 8using System.Numerics;
 9using System.Text;
 10
 11namespace JeremyAnsel.Media.WavefrontObj;
 12
 13public class ObjFile
 14{
 415    public ObjFile()
 16    {
 417        Vertices = new List<ObjVertex>();
 418        ParameterSpaceVertices = new List<ObjVector3>();
 419        VertexNormals = new List<ObjVector3>();
 420        TextureVertices = new List<ObjVector3>();
 421        Points = new List<ObjPoint>();
 422        Lines = new List<ObjLine>();
 423        Faces = new List<ObjFace>();
 424        Curves = new List<ObjCurve>();
 425        Curves2D = new List<ObjCurve2D>();
 426        Surfaces = new List<ObjSurface>();
 427        SurfaceConnections = new List<ObjSurfaceConnection>();
 428        DefaultGroup = new ObjGroup();
 429        Groups = new List<ObjGroup>();
 430        MergingGroupResolutions = new Dictionary<int, float>();
 431        MapLibraries = new List<string>();
 432        MaterialLibraries = new List<string>();
 433    }
 34
 435    public string? HeaderText { get; set; }
 36
 437    public List<ObjVertex> Vertices { get; private set; }
 38
 439    public List<ObjVector3> ParameterSpaceVertices { get; private set; }
 40
 441    public List<ObjVector3> VertexNormals { get; private set; }
 42
 443    public List<ObjVector3> TextureVertices { get; private set; }
 44
 445    public List<ObjPoint> Points { get; private set; }
 46
 447    public List<ObjLine> Lines { get; private set; }
 48
 449    public List<ObjFace> Faces { get; private set; }
 50
 451    public List<ObjCurve> Curves { get; private set; }
 52
 453    public List<ObjCurve2D> Curves2D { get; private set; }
 54
 455    public List<ObjSurface> Surfaces { get; private set; }
 56
 457    public List<ObjSurfaceConnection> SurfaceConnections { get; private set; }
 58
 459    public ObjGroup DefaultGroup { get; private set; }
 60
 461    public List<ObjGroup> Groups { get; private set; }
 62
 463    public Dictionary<int, float> MergingGroupResolutions { get; private set; }
 64
 465    public List<string> MapLibraries { get; private set; }
 66
 467    public List<string> MaterialLibraries { get; private set; }
 68
 469    public string? ShadowObjectFileName { get; set; }
 70
 471    public string? TraceObjectFileName { get; set; }
 72
 73    public static ObjFile FromFile(string? path)
 74    {
 475        return FromFile(path, ObjFileReaderSettings.Default);
 76    }
 77
 78    public static ObjFile FromFile(string? path, ObjFileReaderSettings settings)
 79    {
 480        if (path == null)
 81        {
 482            throw new ArgumentNullException(nameof(path));
 83        }
 84
 485        using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
 86        {
 487            return ObjFileReader.FromStream(stream, settings);
 88        }
 489    }
 90
 91    public static ObjFile FromStream(Stream? stream)
 92    {
 493        return FromStream(stream, ObjFileReaderSettings.Default);
 94    }
 95
 96    public static ObjFile FromStream(Stream? stream, ObjFileReaderSettings settings)
 97    {
 498        return ObjFileReader.FromStream(stream, settings);
 99    }
 100
 101    public void WriteTo(string? path)
 102    {
 4103        if (path == null)
 104        {
 4105            throw new ArgumentNullException(nameof(path));
 106        }
 107
 4108        using (var writer = new StreamWriter(path))
 109        {
 4110            ObjFileWriter.Write(this, writer);
 4111        }
 4112    }
 113
 114    public void WriteTo(Stream? stream)
 115    {
 4116        if (stream == null)
 117        {
 4118            throw new ArgumentNullException(nameof(stream));
 119        }
 120
 4121        using (var writer = new StreamWriter(stream, new UTF8Encoding(false, true), 1024, true))
 122        {
 4123            ObjFileWriter.Write(this, writer);
 4124        }
 4125    }
 126
 127    public void CalculateFaceNormals()
 128    {
 4129        var faceAreaDirections = new Vector3[Faces.Count];
 4130        var adjacentFacesMap = Enumerable.Range(0, Vertices.Count).Select(item => new List<int>()).ToArray();
 131
 4132        for (int faceindex = 0; faceindex < Faces.Count; faceindex++)
 133        {
 4134            var face = Faces[faceindex];
 135
 4136            faceAreaDirections[faceindex] = CalculateFaceAreaDirection(face);
 137
 4138            foreach (var triplet in face.Vertices)
 139            {
 4140                adjacentFacesMap[triplet.Vertex - 1].Add(faceindex);
 141            }
 142        }
 143
 4144        var reuseNormals = new Dictionary<Vector3, int>();
 145
 4146        for (int normalIndex = 0; normalIndex < VertexNormals.Count; ++normalIndex)
 147        {
 0148            reuseNormals[VertexNormals[normalIndex].ToVector3()] = normalIndex + 1;
 149        }
 150
 151        // 3dmax and other software interpret smooth groups as a bit mask.
 0152        Func<ObjPolygonalElement, ObjPolygonalElement, bool> smoothFunc = (l, r) => (l.SmoothingGroupNumber & r.Smoothin
 153
 154        // this should be used if no actual smooth groups are defined, but right now it's not possible to tell.
 4155        if (Faces.All(f => f.SmoothingGroupNumber == 0)) smoothFunc = (l, r) => true;
 156
 4157        for (int faceIndex = 0; faceIndex < Faces.Count; ++faceIndex)
 158        {
 4159            var face = Faces[faceIndex];
 160
 4161            for (int tripletIndex = 0; tripletIndex < face.Vertices.Count; ++tripletIndex)
 162            {
 4163                var v = face.Vertices[tripletIndex];
 4164                if (v.Normal > 0) continue;
 165
 4166                var adjacentFaces = adjacentFacesMap[v.Vertex - 1];
 167
 4168                Vector3 normal = faceAreaDirections[faceIndex];
 169
 4170                normal = adjacentFaces
 4171                        .Where(idx => idx != faceIndex && smoothFunc(face, Faces[idx]))
 4172                        .Aggregate(normal, (accum, fi) => accum + faceAreaDirections[fi]);
 173
 4174                if (normal == Vector3.Zero)
 175                {
 0176                    normal = Vertices[v.Vertex].Position.ToVector3();
 177                }
 178
 4179                normal = Vector3.Normalize(normal);
 180
 4181                if (float.IsNaN(normal.X) || float.IsNaN(normal.Y) || float.IsNaN(normal.Z)) throw new InvalidOperationE
 182
 4183                if (!reuseNormals.TryGetValue(normal, out var normalIdx))
 184                {
 4185                    VertexNormals.Add(normal);
 4186                    normalIdx = VertexNormals.Count;
 4187                    reuseNormals[normal] = normalIdx;
 188                }
 189
 4190                v.Normal = normalIdx;
 191
 4192                face.Vertices[tripletIndex] = v;
 193            }
 194        }
 4195    }
 196
 197    private Vector3 CalculateFaceAreaDirection(ObjFace face)
 198    {
 4199        var v0 = Vertices[face.Vertices[0].Vertex - 1].Position.ToVector3();
 200
 4201        Vector3 n = Vector3.Zero;
 202
 4203        for (int k = 2; k < face.Vertices.Count; k++)
 204        {
 4205            var v1 = Vertices[face.Vertices[k - 1].Vertex - 1].Position.ToVector3();
 4206            var v2 = Vertices[face.Vertices[k].Vertex - 1].Position.ToVector3();
 207
 4208            n += Vector3.Cross(v1 - v0, v2 - v0) / 2;
 209        }
 210
 4211        return n; // do NOT normalize, may be zero if triangle is degenerated.
 212    }
 213}

Methods/Properties

.ctor()
get_HeaderText()
set_HeaderText(string)
get_Vertices()
set_Vertices(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjVertex>)
get_ParameterSpaceVertices()
set_ParameterSpaceVertices(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjVector3>)
get_VertexNormals()
set_VertexNormals(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjVector3>)
get_TextureVertices()
set_TextureVertices(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjVector3>)
get_Points()
set_Points(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjPoint>)
get_Lines()
set_Lines(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjLine>)
get_Faces()
set_Faces(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjFace>)
get_Curves()
set_Curves(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjCurve>)
get_Curves2D()
set_Curves2D(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjCurve2D>)
get_Surfaces()
set_Surfaces(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjSurface>)
get_SurfaceConnections()
set_SurfaceConnections(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjSurfaceConnection>)
get_DefaultGroup()
set_DefaultGroup(JeremyAnsel.Media.WavefrontObj.ObjGroup)
get_Groups()
set_Groups(System.Collections.Generic.List<JeremyAnsel.Media.WavefrontObj.ObjGroup>)
get_MergingGroupResolutions()
set_MergingGroupResolutions(System.Collections.Generic.Dictionary<int, float>)
get_MapLibraries()
set_MapLibraries(System.Collections.Generic.List<string>)
get_MaterialLibraries()
set_MaterialLibraries(System.Collections.Generic.List<string>)
get_ShadowObjectFileName()
set_ShadowObjectFileName(string)
get_TraceObjectFileName()
set_TraceObjectFileName(string)
FromFile(string)
FromFile(string, JeremyAnsel.Media.WavefrontObj.ObjFileReaderSettings)
FromStream(System.IO.Stream)
FromStream(System.IO.Stream, JeremyAnsel.Media.WavefrontObj.ObjFileReaderSettings)
WriteTo(string)
WriteTo(System.IO.Stream)
CalculateFaceNormals()
CalculateFaceAreaDirection(JeremyAnsel.Media.WavefrontObj.ObjFace)