public static ObjFile FromStream(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            var obj     = new ObjFile();
            var context = new ObjFileReaderContext(obj);

            foreach (var values in LineReader.Read(stream))
            {
                switch (values[0].ToLowerInvariant())
                {
                case "v":
                {
                    if (values.Length < 4)
                    {
                        throw new InvalidDataException("A v statement must specify at least 3 values.");
                    }

                    var v = new ObjVector4();
                    v.X = float.Parse(values[1], CultureInfo.InvariantCulture);
                    v.Y = float.Parse(values[2], CultureInfo.InvariantCulture);
                    v.Z = float.Parse(values[3], CultureInfo.InvariantCulture);

                    if (values.Length == 4)
                    {
                        v.W = 1.0f;
                    }
                    else if (values.Length == 5)
                    {
                        v.W = float.Parse(values[4], CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        throw new InvalidDataException("A v statement has too many values.");
                    }

                    obj.Vertices.Add(v);
                    break;
                }

                case "vp":
                {
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A vp statement must specify at least 1 value.");
                    }

                    var v = new ObjVector3();
                    v.X = float.Parse(values[1], CultureInfo.InvariantCulture);

                    if (values.Length == 2)
                    {
                        v.Y = 0.0f;
                        v.Z = 1.0f;
                    }
                    else if (values.Length == 3)
                    {
                        v.Y = float.Parse(values[2], CultureInfo.InvariantCulture);
                        v.Z = 1.0f;
                    }
                    else if (values.Length == 4)
                    {
                        v.Y = float.Parse(values[2], CultureInfo.InvariantCulture);
                        v.Z = float.Parse(values[3], CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        throw new InvalidDataException("A vp statement has too many values.");
                    }

                    obj.ParameterSpaceVertices.Add(v);
                    break;
                }

                case "vn":
                {
                    if (values.Length < 4)
                    {
                        throw new InvalidDataException("A vn statement must specify 3 values.");
                    }

                    if (values.Length != 4)
                    {
                        throw new InvalidDataException("A vn statement has too many values.");
                    }

                    var v = new ObjVector3();
                    v.X = float.Parse(values[1], CultureInfo.InvariantCulture);
                    v.Y = float.Parse(values[2], CultureInfo.InvariantCulture);
                    v.Z = float.Parse(values[3], CultureInfo.InvariantCulture);

                    obj.VertexNormals.Add(v);
                    break;
                }

                case "vt":
                {
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A vt statement must specify at least 1 value.");
                    }

                    var v = new ObjVector3();
                    v.X = float.Parse(values[1], CultureInfo.InvariantCulture);

                    if (values.Length == 2)
                    {
                        v.Y = 0.0f;
                        v.Z = 0.0f;
                    }
                    else if (values.Length == 3)
                    {
                        v.Y = float.Parse(values[2], CultureInfo.InvariantCulture);
                        v.Z = 0.0f;
                    }
                    else if (values.Length == 4)
                    {
                        v.Y = float.Parse(values[2], CultureInfo.InvariantCulture);
                        v.Z = float.Parse(values[3], CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        throw new InvalidDataException("A vt statement has too many values.");
                    }

                    obj.TextureVertices.Add(v);
                    break;
                }

                case "cstype":
                    ObjFileReader.ParseFreeFormType(context, values);
                    break;

                case "deg":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A deg statement must specify at least 1 value.");
                    }

                    if (values.Length == 2)
                    {
                        context.DegreeU = int.Parse(values[1]);
                        context.DegreeV = 0;
                    }
                    else if (values.Length == 3)
                    {
                        context.DegreeU = int.Parse(values[1], CultureInfo.InvariantCulture);
                        context.DegreeV = int.Parse(values[2], CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        throw new InvalidDataException("A deg statement has too many values.");
                    }

                    break;

                case "bmat":
                {
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A bmat statement must specify a direction.");
                    }

                    int d;

                    if (string.Equals(values[1], "u", StringComparison.OrdinalIgnoreCase))
                    {
                        d = 1;
                    }
                    else if (string.Equals(values[1], "v", StringComparison.OrdinalIgnoreCase))
                    {
                        d = 2;
                    }
                    else
                    {
                        throw new InvalidDataException("A bmat statement has an unknown direction.");
                    }

                    int count = (context.DegreeU + 1) * (context.DegreeV + 1);

                    if (values.Length != count + 2)
                    {
                        throw new InvalidDataException("A bmat statement has too many or too few values.");
                    }

                    var matrix = new float[count];

                    for (int i = 0; i < count; i++)
                    {
                        matrix[i] = float.Parse(values[2 + i], CultureInfo.InvariantCulture);
                    }

                    switch (d)
                    {
                    case 1:
                        context.BasicMatrixU = matrix;
                        break;

                    case 2:
                        context.BasicMatrixV = matrix;
                        break;
                    }

                    break;
                }

                case "step":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A step statement must specify at least 1 value.");
                    }

                    if (values.Length == 2)
                    {
                        context.StepU = float.Parse(values[1], CultureInfo.InvariantCulture);
                        context.StepV = 1.0f;
                    }
                    else if (values.Length == 3)
                    {
                        context.StepU = float.Parse(values[1], CultureInfo.InvariantCulture);
                        context.StepV = float.Parse(values[2], CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        throw new InvalidDataException("A step statement has too many values.");
                    }

                    break;

                case "p":
                {
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A p statement must specify at least 1 value.");
                    }

                    var point = new ObjPoint();

                    for (int i = 1; i < values.Length; i++)
                    {
                        point.Vertices.Add(ObjFileReader.ParseTriplet(obj, values[i]));
                    }

                    context.ApplyAttributesToElement(point);
                    context.ApplyAttributesToPolygonalElement(point);

                    obj.Points.Add(point);

                    foreach (var group in context.GetCurrentGroups())
                    {
                        group.Points.Add(point);
                    }

                    break;
                }

                case "l":
                {
                    if (values.Length < 3)
                    {
                        throw new InvalidDataException("A l statement must specify at least 2 values.");
                    }

                    var line = new ObjLine();

                    for (int i = 1; i < values.Length; i++)
                    {
                        line.Vertices.Add(ObjFileReader.ParseTriplet(obj, values[i]));
                    }

                    context.ApplyAttributesToElement(line);
                    context.ApplyAttributesToPolygonalElement(line);

                    obj.Lines.Add(line);

                    foreach (var group in context.GetCurrentGroups())
                    {
                        group.Lines.Add(line);
                    }

                    break;
                }

                case "f":
                case "fo":
                {
                    if (values.Length < 4)
                    {
                        throw new InvalidDataException("A f statement must specify at least 3 values.");
                    }

                    var face = new ObjFace();

                    for (int i = 1; i < values.Length; i++)
                    {
                        face.Vertices.Add(ObjFileReader.ParseTriplet(obj, values[i]));
                    }

                    context.ApplyAttributesToElement(face);
                    context.ApplyAttributesToPolygonalElement(face);

                    obj.Faces.Add(face);

                    foreach (var group in context.GetCurrentGroups())
                    {
                        group.Faces.Add(face);
                    }

                    break;
                }

                case "curv":
                {
                    if (values.Length < 5)
                    {
                        throw new InvalidDataException("A curv statement must specify at least 4 values.");
                    }

                    var curve = new ObjCurve();

                    curve.StartParameter = float.Parse(values[1], CultureInfo.InvariantCulture);
                    curve.EndParameter   = float.Parse(values[2], CultureInfo.InvariantCulture);

                    for (int i = 3; i < values.Length; i++)
                    {
                        int v = int.Parse(values[i], CultureInfo.InvariantCulture);

                        if (v == 0)
                        {
                            throw new InvalidDataException("A curv statement contains an invalid vertex index.");
                        }

                        if (v < 0)
                        {
                            v = obj.Vertices.Count + v + 1;
                        }

                        if (v <= 0 || v > obj.Vertices.Count)
                        {
                            throw new IndexOutOfRangeException();
                        }

                        curve.Vertices.Add(v);
                    }

                    context.ApplyAttributesToElement(curve);
                    context.ApplyAttributesToFreeFormElement(curve);
                    context.CurrentFreeFormElement = curve;

                    obj.Curves.Add(curve);

                    foreach (var group in context.GetCurrentGroups())
                    {
                        group.Curves.Add(curve);
                    }

                    break;
                }

                case "curv2":
                {
                    if (values.Length < 3)
                    {
                        throw new InvalidDataException("A curv2 statement must specify at least 2 values.");
                    }

                    var curve = new ObjCurve2D();

                    for (int i = 1; i < values.Length; i++)
                    {
                        int vp = int.Parse(values[i], CultureInfo.InvariantCulture);

                        if (vp == 0)
                        {
                            throw new InvalidDataException("A curv2 statement contains an invalid parameter space vertex index.");
                        }

                        if (vp < 0)
                        {
                            vp = obj.ParameterSpaceVertices.Count + vp + 1;
                        }

                        if (vp <= 0 || vp > obj.ParameterSpaceVertices.Count)
                        {
                            throw new IndexOutOfRangeException();
                        }

                        curve.ParameterSpaceVertices.Add(vp);
                    }

                    context.ApplyAttributesToElement(curve);
                    context.ApplyAttributesToFreeFormElement(curve);
                    context.CurrentFreeFormElement = curve;

                    obj.Curves2D.Add(curve);

                    foreach (var group in context.GetCurrentGroups())
                    {
                        group.Curves2D.Add(curve);
                    }

                    break;
                }

                case "surf":
                {
                    if (values.Length < 6)
                    {
                        throw new InvalidDataException("A surf statement must specify at least 5 values.");
                    }

                    var surface = new ObjSurface();

                    surface.StartParameterU = float.Parse(values[1], CultureInfo.InvariantCulture);
                    surface.EndParameterU   = float.Parse(values[2], CultureInfo.InvariantCulture);
                    surface.StartParameterV = float.Parse(values[3], CultureInfo.InvariantCulture);
                    surface.EndParameterV   = float.Parse(values[4], CultureInfo.InvariantCulture);

                    for (int i = 5; i < values.Length; i++)
                    {
                        surface.Vertices.Add(ObjFileReader.ParseTriplet(obj, values[i]));
                    }

                    context.ApplyAttributesToElement(surface);
                    context.ApplyAttributesToFreeFormElement(surface);
                    context.CurrentFreeFormElement = surface;

                    obj.Surfaces.Add(surface);

                    foreach (var group in context.GetCurrentGroups())
                    {
                        group.Surfaces.Add(surface);
                    }

                    break;
                }

                case "parm":
                    if (context.CurrentFreeFormElement == null)
                    {
                        break;
                    }

                    if (values.Length < 4)
                    {
                        throw new InvalidDataException("A parm statement must specify at least 3 values.");
                    }

                    IList <float> parameters;

                    if (string.Equals(values[1], "u", StringComparison.OrdinalIgnoreCase))
                    {
                        parameters = context.CurrentFreeFormElement.ParametersU;
                    }
                    else if (string.Equals(values[1], "v", StringComparison.OrdinalIgnoreCase))
                    {
                        parameters = context.CurrentFreeFormElement.ParametersV;
                    }
                    else
                    {
                        throw new InvalidDataException("A parm statement has an unknown direction.");
                    }

                    for (int i = 2; i < values.Length; i++)
                    {
                        parameters.Add(float.Parse(values[i], CultureInfo.InvariantCulture));
                    }

                    break;

                case "trim":
                    if (context.CurrentFreeFormElement == null)
                    {
                        break;
                    }

                    ObjFileReader.ParseCurveIndex(context.CurrentFreeFormElement.OuterTrimmingCurves, obj, values);
                    break;

                case "hole":
                    if (context.CurrentFreeFormElement == null)
                    {
                        break;
                    }

                    ObjFileReader.ParseCurveIndex(context.CurrentFreeFormElement.InnerTrimmingCurves, obj, values);
                    break;

                case "scrv":
                    if (context.CurrentFreeFormElement == null)
                    {
                        break;
                    }

                    ObjFileReader.ParseCurveIndex(context.CurrentFreeFormElement.SequenceCurves, obj, values);
                    break;

                case "sp":
                    if (context.CurrentFreeFormElement == null)
                    {
                        break;
                    }

                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A sp statement must specify at least 1 value.");
                    }

                    for (int i = 1; i < values.Length; i++)
                    {
                        int vp = int.Parse(values[i], CultureInfo.InvariantCulture);

                        if (vp == 0)
                        {
                            throw new InvalidDataException("A sp statement contains an invalid parameter space vertex index.");
                        }

                        if (vp < 0)
                        {
                            vp = obj.ParameterSpaceVertices.Count + vp + 1;
                        }

                        if (vp <= 0 || vp > obj.ParameterSpaceVertices.Count)
                        {
                            throw new IndexOutOfRangeException();
                        }

                        context.CurrentFreeFormElement.SpecialPoints.Add(vp);
                    }

                    break;

                case "end":
                    context.CurrentFreeFormElement = null;
                    break;

                case "con":
                    ObjFileReader.ParseSurfaceConnection(obj, values);
                    break;

                case "g":
                    ObjFileReader.ParseGroupName(values, context);
                    break;

                case "s":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A s statement must specify a value.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A s statement has too many values.");
                    }

                    if (string.Equals(values[1], "off", StringComparison.OrdinalIgnoreCase))
                    {
                        context.SmoothingGroupNumber = 0;
                    }
                    else
                    {
                        context.SmoothingGroupNumber = int.Parse(values[1], CultureInfo.InvariantCulture);
                    }

                    break;

                case "mg":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A mg statement must specify a value.");
                    }

                    if (string.Equals(values[1], "off", StringComparison.OrdinalIgnoreCase))
                    {
                        context.MergingGroupNumber = 0;
                    }
                    else
                    {
                        context.MergingGroupNumber = int.Parse(values[1], CultureInfo.InvariantCulture);
                    }

                    if (context.MergingGroupNumber == 0)
                    {
                        if (values.Length > 3)
                        {
                            throw new InvalidDataException("A mg statement has too many values.");
                        }
                    }
                    else
                    {
                        if (values.Length != 3)
                        {
                            throw new InvalidDataException("A mg statement has too many or too few values.");
                        }

                        float res = float.Parse(values[2], CultureInfo.InvariantCulture);

                        obj.MergingGroupResolutions[context.MergingGroupNumber] = res;
                    }

                    break;

                case "o":
                    if (values.Length == 1)
                    {
                        context.ObjectName = null;
                        break;
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A o statement has too many values.");
                    }

                    context.ObjectName = values[1];
                    break;

                case "bevel":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A bevel statement must specify a name.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A bevel statement has too many values.");
                    }

                    if (string.Equals(values[1], "on", StringComparison.OrdinalIgnoreCase))
                    {
                        context.IsBevelInterpolationEnabled = true;
                    }
                    else if (string.Equals(values[1], "off", StringComparison.OrdinalIgnoreCase))
                    {
                        context.IsBevelInterpolationEnabled = false;
                    }
                    else
                    {
                        throw new InvalidDataException("A bevel statement must specify on or off.");
                    }

                    break;

                case "c_interp":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A c_interp statement must specify a name.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A c_interp statement has too many values.");
                    }

                    if (string.Equals(values[1], "on", StringComparison.OrdinalIgnoreCase))
                    {
                        context.IsColorInterpolationEnabled = true;
                    }
                    else if (string.Equals(values[1], "off", StringComparison.OrdinalIgnoreCase))
                    {
                        context.IsColorInterpolationEnabled = false;
                    }
                    else
                    {
                        throw new InvalidDataException("A c_interp statement must specify on or off.");
                    }

                    break;

                case "d_interp":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A d_interp statement must specify a name.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A d_interp statement has too many values.");
                    }

                    if (string.Equals(values[1], "on", StringComparison.OrdinalIgnoreCase))
                    {
                        context.IsDissolveInterpolationEnabled = true;
                    }
                    else if (string.Equals(values[1], "off", StringComparison.OrdinalIgnoreCase))
                    {
                        context.IsDissolveInterpolationEnabled = false;
                    }
                    else
                    {
                        throw new InvalidDataException("A d_interp statement must specify on or off.");
                    }

                    break;

                case "lod":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A lod statement must specify a value.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A lod statement has too many values.");
                    }

                    context.LevelOfDetail = int.Parse(values[1], CultureInfo.InvariantCulture);
                    break;

                case "maplib":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A maplib statement must specify a file name.");
                    }

                    for (int i = 1; i < values.Length; i++)
                    {
                        if (!Path.HasExtension(values[i]))
                        {
                            throw new InvalidDataException("A file name must have an extension.");
                        }

                        obj.MapLibraries.Add(values[i]);
                    }

                    break;

                case "mtllib":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A mtllib statement must specify a file name.");
                    }

                    for (int i = 1; i < values.Length; i++)
                    {
                        if (!Path.HasExtension(values[i]))
                        {
                            throw new InvalidDataException("A file name must have an extension.");
                        }

                        obj.MaterialLibraries.Add(values[i]);
                    }

                    break;

                case "usemap":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A usemap statement must specify a value.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A usemap statement has too many values.");
                    }

                    if (string.Equals(values[1], "off", StringComparison.OrdinalIgnoreCase))
                    {
                        context.MapName = null;
                    }
                    else
                    {
                        context.MapName = values[1];
                    }

                    break;

                case "usemtl":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A usemtl statement must specify a value.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A usemtl statement has too many values.");
                    }

                    if (string.Equals(values[1], "off", StringComparison.OrdinalIgnoreCase))
                    {
                        context.MaterialName = null;
                    }
                    else
                    {
                        context.MaterialName = values[1];
                    }

                    break;

                case "shadow_obj":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A shadow_obj statement must specify a file name.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A shadow_obj statement has too many values.");
                    }

                    if (!Path.HasExtension(values[1]))
                    {
                        throw new InvalidDataException("A file name must have an extension.");
                    }

                    obj.ShadowObjectFileName = values[1];
                    break;

                case "trace_obj":
                    if (values.Length < 2)
                    {
                        throw new InvalidDataException("A trace_obj statement must specify a file name.");
                    }

                    if (values.Length != 2)
                    {
                        throw new InvalidDataException("A trace_obj statement has too many values.");
                    }

                    if (!Path.HasExtension(values[1]))
                    {
                        throw new InvalidDataException("A file name must have an extension.");
                    }

                    obj.TraceObjectFileName = values[1];
                    break;

                case "ctech":
                    context.CurveApproximationTechnique = ObjFileReader.ParseApproximationTechnique(values);
                    break;

                case "stech":
                    context.SurfaceApproximationTechnique = ObjFileReader.ParseApproximationTechnique(values);
                    break;

                case "bsp":
                case "bzp":
                case "cdc":
                case "cdp":
                case "res":
                    throw new NotImplementedException(string.Concat(values[0], " statement have been replaced by free-form geometry statements."));
                }
            }

            return(obj);
        }