diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/BevelCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/BevelCommand.cs new file mode 100644 index 0000000..302591a --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/BevelCommand.cs @@ -0,0 +1,19 @@ +using ThreeDModels.Format.Obj.Display; + +namespace ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; + +internal class BevelCommand : ICommand +{ + public static string Name => "bevel"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + context.Display ??= new Display.DisplayAttributes(); + context.Display.BevelInterpolationEnabled = ObjReader.GetBool(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/ColorInterpolationCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/ColorInterpolationCommand.cs new file mode 100644 index 0000000..b9b1ccd --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/ColorInterpolationCommand.cs @@ -0,0 +1,17 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; + +internal class ColorInterpolationCommand : ICommand +{ + public static string Name => "c_interp"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + context.Display ??= new Display.DisplayAttributes(); + context.Display.ColorInterpolationEnabled = ObjReader.GetBool(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/DissolveInterpolationCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/DissolveInterpolationCommand.cs new file mode 100644 index 0000000..dc45181 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/DissolveInterpolationCommand.cs @@ -0,0 +1,17 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; + +internal class DissolveInterpolationCommand : ICommand +{ + public static string Name => "d_interp"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + context.Display ??= new Display.DisplayAttributes(); + context.Display.DissolveInterpolationEnabled = ObjReader.GetBool(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/LevelOfDetailCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/LevelOfDetailCommand.cs new file mode 100644 index 0000000..c72ef1b --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/LevelOfDetailCommand.cs @@ -0,0 +1,17 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; + +internal class LevelOfDetailCommand : ICommand +{ + public static string Name => "lod"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + context.Display ??= new Display.DisplayAttributes(); + context.Display.LevelOfDetail = int.Parse(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/MaterialLibraryCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/MaterialLibraryCommand.cs new file mode 100644 index 0000000..a80eed4 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/MaterialLibraryCommand.cs @@ -0,0 +1,66 @@ +using ThreeDModels.Format.Obj.Display; +using ThreeDModels.Format.Obj.IO.Commands.Materials; + +namespace ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; + +internal class MaterialLibraryCommand : ICommand +{ + private static readonly Dictionary _commands = new() + { + { NewMaterialCommand.Name, NewMaterialCommand.Read }, + { AmbientColorCommand.Name, AmbientColorCommand.Read }, + { DiffuseColorCommand.Name, DiffuseColorCommand.Read }, + { SpecularColorCommand.Name, SpecularColorCommand.Read }, + { SpecularExponentCommand.Name, SpecularExponentCommand.Read }, + { DissolveCommand.Name, DissolveCommand.Read }, + { TransparencyCommand.Name, TransparencyCommand.Read }, + { IlluminationModelCommand.Name, IlluminationModelCommand.Read }, + }; + public static string Name => "mtllib"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + for (int i = 1; i < commandLine.Count; i++) + { + obj.MaterialLibraries.Add(GetMaterials(commandLine[i])); + } + } + + public static void Write() + { + throw new NotImplementedException(); + } + + private static List GetMaterials(string path) + { + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); + return GetMaterials(fs); + } + + private static List GetMaterials(Stream stream) + { + var context = new MaterialReaderContext(material: null, materials: []); + using var textReader = new StreamReader(stream); + while (stream.Position < stream.Length) + { + var commandLine = ObjReader.GetCommandLine(textReader); + if (commandLine.Count == 0) + { + continue; + } + if (_commands.TryGetValue(commandLine[0], out var readCommand)) + { + readCommand.Invoke(commandLine, context); + } + else + { + throw new InvalidDataException($"Unknown material command '{commandLine[0]}'"); + } + } + if (context.Material != null) + { + context.Materials.Add(context.Material); + } + return context.Materials; + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMapCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMapCommand.cs new file mode 100644 index 0000000..af71d9f --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMapCommand.cs @@ -0,0 +1,17 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; + +internal class UseMapCommand : ICommand +{ + public static string Name => "usemap"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + context.Display ??= new Display.DisplayAttributes(); + context.Display.TextureMapName = commandLine.Count > 1 && commandLine[1] != Constants.Off ? commandLine[1] : string.Empty; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMaterialCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMaterialCommand.cs new file mode 100644 index 0000000..5e8c7e0 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMaterialCommand.cs @@ -0,0 +1,17 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; + +internal class UseMaterialCommand : ICommand +{ + public static string Name => "usemtl"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + context.Display ??= new Display.DisplayAttributes(); + context.Display.MaterialName = commandLine.Count > 1 ? commandLine[1] : string.Empty; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/BasisMatrixCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/BasisMatrixCommand.cs new file mode 100644 index 0000000..31b5cc3 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/BasisMatrixCommand.cs @@ -0,0 +1,34 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceAttributes; + +internal class BasisMatrixCommand : ICommand +{ + public static string Name => "bmat"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("BasisMatrix (bmat) must follow a curve or surface element"); + } + var matrix = new List(); + for (int i = 2; i < commandLine.Count; i++) + { + matrix.Add(float.Parse(commandLine[i])); + } + if (commandLine[1] == "u") + { + context.FreeFormGeometry.BasisMatrixU = matrix; + } + else + { + context.FreeFormGeometry.BasisMatrixV = commandLine[1] == "v" + ? matrix + : throw new InvalidDataException($"BasisMatrix (bmat) has an invalid direction '{commandLine[1]}'"); + } + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/CurveSurfaceTypeCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/CurveSurfaceTypeCommand.cs new file mode 100644 index 0000000..1c527eb --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/CurveSurfaceTypeCommand.cs @@ -0,0 +1,30 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceAttributes; + +internal class CurveSurfaceTypeCommand : ICommand +{ + public static string Name => "cstype"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("CurveSurfaceType (cstype) must follow a curve or surface element"); + } + if (commandLine.Count > 2) + { + context.FreeFormGeometry.IsRational = commandLine[1] == "rat"; + context.FreeFormGeometry.Type = Enum.Parse(commandLine[2]); + } + else + { + context.FreeFormGeometry.Type = Enum.Parse(commandLine[1]); + } + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/DegreeCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/DegreeCommand.cs new file mode 100644 index 0000000..2e92e2d --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/DegreeCommand.cs @@ -0,0 +1,21 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceAttributes; + +internal class DegreeCommand : ICommand +{ + public static string Name => "deg"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("Degree (deg) must follow a curve or surface element"); + } + context.FreeFormGeometry.DegreeU = int.Parse(commandLine[1]); + context.FreeFormGeometry.DegreeV = commandLine.Count > 2 ? int.Parse(commandLine[2]) : null; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/StepCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/StepCommand.cs new file mode 100644 index 0000000..c4fb4a7 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/StepCommand.cs @@ -0,0 +1,21 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceAttributes; + +internal class StepCommand : ICommand +{ + public static string Name => "step"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("Step (step) must follow a curve or surface element"); + } + context.FreeFormGeometry.StepU = int.Parse(commandLine[1]); + context.FreeFormGeometry.StepV = commandLine.Count > 2 ? int.Parse(commandLine[2]) : null; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/EndFreeFormGeometryCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/EndFreeFormGeometryCommand.cs new file mode 100644 index 0000000..9b78781 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/EndFreeFormGeometryCommand.cs @@ -0,0 +1,38 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceBodyStatements; + +internal class EndFreeFormGeometryCommand : ICommand +{ + public static string Name => "end"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("The curve or surface element is not initialized"); + } + else if (context.FreeFormGeometry is Curve curve) + { + context.Group.Curves.Add(curve); + } + else if (context.FreeFormGeometry is Curve2D curve2D) + { + context.Group.Curves2D.Add(curve2D); + } + else if (context.FreeFormGeometry is Surface surface) + { + context.Group.Surfaces.Add(surface); + } + else + { + throw new InvalidDataException("Unknown free-form geometry type"); + } + context.FreeFormGeometry = null; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/HoleCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/HoleCommand.cs new file mode 100644 index 0000000..1eac146 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/HoleCommand.cs @@ -0,0 +1,33 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceBodyStatements; + +internal class HoleCommand : ICommand +{ + public static string Name => "hole"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("Hole (hole) must follow a curve or surface element"); + } + var curve2Ds = new List(); + for (int i = 1; i < commandLine.Count; i += 3) + { + curve2Ds.Add(new() + { + Start = float.Parse(commandLine[i]), + End = float.Parse(commandLine[i + 1]), + Curve2DIndex = int.Parse(commandLine[i + 2]), + }); + } + context.FreeFormGeometry.Holes ??= []; + context.FreeFormGeometry.Holes.Add(curve2Ds); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/ParameterCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/ParameterCommand.cs new file mode 100644 index 0000000..837f90b --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/ParameterCommand.cs @@ -0,0 +1,34 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceBodyStatements; + +internal class ParameterCommand : ICommand +{ + public static string Name => "parm"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("Parameter (parm) must follow a curve or surface element"); + } + var matrix = new List(); + for (int i = 2; i < commandLine.Count; i++) + { + matrix.Add(float.Parse(commandLine[i])); + } + if (commandLine[1] == "u") + { + context.FreeFormGeometry.ParameterU = matrix; + } + else + { + context.FreeFormGeometry.ParameterV = commandLine[1] == "v" + ? matrix + : throw new InvalidDataException($"BasisMatrix (bmat) has an invalid direction '{commandLine[1]}'"); + } + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialCurveCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialCurveCommand.cs new file mode 100644 index 0000000..181c25a --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialCurveCommand.cs @@ -0,0 +1,33 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceBodyStatements; + +internal class SpecialCurveCommand : ICommand +{ + public static string Name => "scrv"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("SpecialCurve (scrv) must follow a curve or surface element"); + } + var curve2Ds = new List(); + for (int i = 1; i < commandLine.Count; i += 3) + { + curve2Ds.Add(new() + { + Start = float.Parse(commandLine[i]), + End = float.Parse(commandLine[i + 1]), + Curve2DIndex = int.Parse(commandLine[i + 2]), + }); + } + context.FreeFormGeometry.SpecialCurves ??= []; + context.FreeFormGeometry.SpecialCurves.Add(curve2Ds); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialPointCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialPointCommand.cs new file mode 100644 index 0000000..0ccfc6c --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialPointCommand.cs @@ -0,0 +1,26 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceBodyStatements; + +internal class SpecialPointCommand : ICommand +{ + public static string Name => "sp"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("SpecialPoint (sp) must follow a curve or surface element"); + } + var geometricPointsIndices = new List(); + for (int i = 1; i < commandLine.Count; i += 3) + { + geometricPointsIndices.Add(int.Parse(commandLine[i])); + } + context.FreeFormGeometry.SpecialCurves ??= []; + context.FreeFormGeometry.SpecialPoints.Add(geometricPointsIndices); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/TrimCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/TrimCommand.cs new file mode 100644 index 0000000..6ecc833 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/TrimCommand.cs @@ -0,0 +1,33 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceBodyStatements; + +internal class TrimCommand : ICommand +{ + public static string Name => "trim"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.FreeFormGeometry == null) + { + throw new InvalidDataException("Trim (trim) must follow a curve or surface element"); + } + var curve2Ds = new List(); + for (int i = 1; i < commandLine.Count; i += 3) + { + curve2Ds.Add(new() + { + Start = float.Parse(commandLine[i]), + End = float.Parse(commandLine[i + 1]), + Curve2DIndex = int.Parse(commandLine[i + 2]), + }); + } + context.FreeFormGeometry.TrimmingCurves ??= []; + context.FreeFormGeometry.TrimmingCurves.Add(curve2Ds); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/Curve2Command.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/Curve2Command.cs new file mode 100644 index 0000000..f4176ff --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/Curve2Command.cs @@ -0,0 +1,32 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceElements; + +internal class Curve2Command : ICommand +{ + public static string Name => "curv2"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (commandLine.Count < 3) + { + throw new InvalidDataException("Curve2D (curv2) must have at least 2 control points"); + } + var curve2D = new Curve2D() + { + ParameterVertexIndices = [], + Name = context.ElementName, + Display = context.Display, + }; + for (int i = 1; i < commandLine.Count; i++) + { + curve2D.ParameterVertexIndices.Add(int.Parse(commandLine[i])); + } + context.FreeFormGeometry = curve2D; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/CurveCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/CurveCommand.cs new file mode 100644 index 0000000..3d8d7c2 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/CurveCommand.cs @@ -0,0 +1,41 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceElements; + +internal class CurveCommand : ICommand +{ + public static string Name => "curv"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (commandLine.Count == 1) + { + throw new InvalidDataException("Curve (curv) must have a starting parameter value"); + } + if (commandLine.Count == 2) + { + throw new InvalidDataException("Curve (curv) must have an ending parameter value"); + } + if (commandLine.Count < 5) + { + throw new InvalidDataException("Curve (curv) must have at least 2 control vertices"); + } + var curve = new Curve() + { + ParameterRange = new Range(start: float.Parse(commandLine[1]), end: float.Parse(commandLine[2])), + ControlVertexIndices = [], + Name = context.ElementName, + Display = context.Display, + }; + for (int i = 3; i < commandLine.Count; i++) + { + curve.ControlVertexIndices.Add(int.Parse(commandLine[i])); + } + context.FreeFormGeometry = curve; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/FaceCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/FaceCommand.cs new file mode 100644 index 0000000..5a632be --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/FaceCommand.cs @@ -0,0 +1,50 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceElements; + +internal class FaceCommand : ICommand +{ + public static string Name => "f"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + var face = new Face() + { + VertexReferences = [], + Name = context.ElementName, + Display = context.Display, + }; + if (commandLine.Count < 4) + { + throw new InvalidDataException("Face (f) must have at least 3 vertices"); + } + var requireVertexTextureIndex = false; + var requireVertexNormalIndex = false; + for (int i = 1; i < commandLine.Count; i++) + { + var fields = commandLine[i].Split(Constants.FieldSeparator); + if (i == 1) + { + requireVertexTextureIndex = fields.Length > 1 && !string.IsNullOrEmpty(fields[1]); + requireVertexNormalIndex = fields.Length > 2 && !string.IsNullOrEmpty(fields[2]); + } + if ((requireVertexTextureIndex && (fields.Length < 2 || string.IsNullOrEmpty(fields[1]))) + || (requireVertexNormalIndex && (fields.Length < 3 || string.IsNullOrEmpty(fields[2])))) + { + throw new InvalidDataException("Inconsistent face (f) fields"); + } + face.VertexReferences.Add(new VertexReference() + { + VertexIndex = int.Parse(fields[0]), + VertexTextureIndex = requireVertexTextureIndex ? int.Parse(fields[1]) : null, + VertexNormalIndex = requireVertexNormalIndex ? int.Parse(fields[2]) : null + }); + } + context.Group.Faces.Add(face); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/LineCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/LineCommand.cs new file mode 100644 index 0000000..e81fb48 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/LineCommand.cs @@ -0,0 +1,33 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceElements; + +internal class LineCommand : ICommand +{ + public static string Name => "l"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + var line = new Line() + { + VertexIndices = [], + Name = context.ElementName, + Display = context.Display, + }; + for (int i = 1; i < commandLine.Count; i++) + { + var fields = commandLine[i].Split(Constants.FieldSeparator); + line.VertexIndices.Add(new VertexReference + { + VertexIndex = int.Parse(fields[0]), + VertexTextureIndex = fields.Length > 1 ? int.Parse(fields[1]) : null + }); + } + context.Group.Lines.Add(line); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/PointCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/PointCommand.cs new file mode 100644 index 0000000..73a7fd1 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/PointCommand.cs @@ -0,0 +1,28 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceElements; + +internal class PointCommand : ICommand +{ + public static string Name => "p"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + var point = new Point() + { + VertexIndices = [], + Name = context.ElementName, + Display = context.Display, + }; + for (int i = 1; i < commandLine.Count; i++) + { + point.VertexIndices.Add(int.Parse(commandLine[i])); + } + context.Group.Points.Add(point); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/SurfaceCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/SurfaceCommand.cs new file mode 100644 index 0000000..95cd2c0 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/SurfaceCommand.cs @@ -0,0 +1,60 @@ +using ThreeDModels.Format.Obj.Elements; + +namespace ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceElements; + +internal class SurfaceCommand : ICommand +{ + public static string Name => "surf"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (commandLine.Count == 1) + { + throw new InvalidDataException("Curve (curv) must have a starting parameter value"); + } + if (commandLine.Count == 2) + { + throw new InvalidDataException("Curve (curv) must have an ending parameter value"); + } + if (commandLine.Count < 5) + { + throw new InvalidDataException("Curve (curv) must have at least 2 control vertices"); + } + var surface = new Surface() + { + UParameterRange = new Range(start: float.Parse(commandLine[1]), end: float.Parse(commandLine[2])), + VParameterRange = new Range(start: float.Parse(commandLine[3]), end: float.Parse(commandLine[4])), + VertexReferences = [], + Name = context.ElementName, + Display = context.Display, + }; + var requireVertexTextureIndex = false; + var requireVertexNormalIndex = false; + for (int i = 4; i < commandLine.Count; i++) + { + var fields = commandLine[i].Split(Constants.FieldSeparator); + if (i == 1) + { + requireVertexTextureIndex = fields.Length > 1 && !string.IsNullOrEmpty(fields[1]); + requireVertexNormalIndex = fields.Length > 2 && !string.IsNullOrEmpty(fields[2]); + } + if ((requireVertexTextureIndex && (fields.Length < 2 || string.IsNullOrEmpty(fields[1]))) + || (requireVertexNormalIndex && fields.Length < 3 && !string.IsNullOrEmpty(fields[2]))) + { + throw new InvalidDataException("Inconsistent surface (surf) fields"); + } + surface.VertexReferences.Add(new VertexReference() + { + VertexIndex = int.Parse(fields[0]), + VertexTextureIndex = requireVertexTextureIndex ? int.Parse(fields[1]) : null, + VertexNormalIndex = requireVertexNormalIndex ? int.Parse(fields[2]) : null + }); + } + context.FreeFormGeometry = surface; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/GroupCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/GroupCommand.cs new file mode 100644 index 0000000..5f1540f --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/GroupCommand.cs @@ -0,0 +1,26 @@ +using ThreeDModels.Format.Obj.Grouping; + +namespace ThreeDModels.Format.Obj.IO.Commands.Grouping; + +internal class GroupCommand : ICommand +{ + public static string Name => "g"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + if (context.Group != null) + { + obj.Groups.Add(context.Group); + } + context.Group = new Group() + { + Names = commandLine.Count == 1 ? [Constants.DefaultGroup] : commandLine.GetRange(1, commandLine.Count - 1), + }; + context.Display = null; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/MergingGroupCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/MergingGroupCommand.cs new file mode 100644 index 0000000..4b0474f --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/MergingGroupCommand.cs @@ -0,0 +1,30 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.Grouping; + +internal class MergingGroupCommand : ICommand +{ + public static string Name => "mg"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + int groupNumber = -1; + if (!int.TryParse(commandLine[1], out groupNumber) && commandLine[1] != Constants.Off || groupNumber < 0 || groupNumber > group.Names!.Count) + { + throw new InvalidDataException("Invalid merging group number"); + } + if (commandLine[1] == Constants.Off || groupNumber == 0) + { + group.MergingGroups = []; + break; + } + else if (!group.MergingGroups.Any(mergingGroup => mergingGroup.GroupIndex == groupNumber - 1)) + { + var resolution = float.Parse(commandLine[2]); + group.MergingGroups.Add(new MergingGroup { GroupIndex = groupNumber - 1, Resolution = resolution }); + } + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/ObjectCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/ObjectCommand.cs new file mode 100644 index 0000000..51b6b0f --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/ObjectCommand.cs @@ -0,0 +1,16 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.Grouping; + +internal class ObjectCommand : ICommand +{ + public static string Name => "o"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + elementName = commandLine[1]; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/SmoothingGroupCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/SmoothingGroupCommand.cs new file mode 100644 index 0000000..e5297dd --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/SmoothingGroupCommand.cs @@ -0,0 +1,29 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.Grouping; + +internal class SmoothingGroupCommand : ICommand +{ + public static string Name => "sg"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + int groupNumber = -1; + if (!int.TryParse(commandLine[1], out groupNumber) && commandLine[1] != Constants.Off || groupNumber < 0 || groupNumber > group.Names!.Count) + { + throw new InvalidDataException("Invalid smoothing group number"); + } + if (commandLine[1] == Constants.Off || groupNumber == 0) + { + group.SmoothingGroupIndices = []; + break; + } + else if (!group.SmoothingGroupIndices.Contains(groupNumber - 1)) + { + group.SmoothingGroupIndices.Add(groupNumber - 1); + } + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/ICommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/ICommand.cs new file mode 100644 index 0000000..704bcfe --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/ICommand.cs @@ -0,0 +1,10 @@ +namespace ThreeDModels.Format.Obj.IO.Commands; + +delegate void ICommandRead(List commandLine, ObjReaderContext context, ref Obj obj); + +internal interface ICommand +{ + abstract static string Name { get; } + abstract static void Read(List commandLine, ObjReaderContext context, ref Obj obj); + abstract static void Write(); +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/IMaterialCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/IMaterialCommand.cs new file mode 100644 index 0000000..713cf4d --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/IMaterialCommand.cs @@ -0,0 +1,10 @@ +namespace ThreeDModels.Format.Obj.IO.Commands; + +delegate void IMaterialCommandRead(List commandLine, MaterialReaderContext context); + +internal interface IMaterialCommand +{ + abstract static string Name { get; } + abstract static void Read(List commandLine, MaterialReaderContext context); + abstract static void Write(); +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/AmbientColorCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/AmbientColorCommand.cs new file mode 100644 index 0000000..c377293 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/AmbientColorCommand.cs @@ -0,0 +1,27 @@ +using ThreeDModels.Format.Obj.Display; + +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class AmbientColorCommand : IMaterialCommand +{ + public static string Name => "Ka"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material == null) + { + throw new InvalidDataException("Ambient color (Ka) must follow a material definition (newmtl)"); + } + context.Material.Ambient = new Color + { + Red = float.Parse(commandLine[1]), + Green = float.Parse(commandLine[2]), + Blue = float.Parse(commandLine[3]), + }; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/DiffuseColorCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/DiffuseColorCommand.cs new file mode 100644 index 0000000..01c472f --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/DiffuseColorCommand.cs @@ -0,0 +1,27 @@ +using ThreeDModels.Format.Obj.Display; + +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class DiffuseColorCommand : IMaterialCommand +{ + public static string Name => "Kd"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material == null) + { + throw new InvalidDataException("Diffuse color (Kd) must follow a material definition (newmtl)"); + } + context.Material.Diffuse = new Color + { + Red = float.Parse(commandLine[1]), + Green = float.Parse(commandLine[2]), + Blue = float.Parse(commandLine[3]), + }; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/DissolveCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/DissolveCommand.cs new file mode 100644 index 0000000..c668286 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/DissolveCommand.cs @@ -0,0 +1,20 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class DissolveCommand : IMaterialCommand +{ + public static string Name => "d"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material == null) + { + throw new InvalidDataException("Dissolve (d) must follow a material definition (newmtl)"); + } + context.Material.Transparency = 1 - float.Parse(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/IlluminationModelCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/IlluminationModelCommand.cs new file mode 100644 index 0000000..740205e --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/IlluminationModelCommand.cs @@ -0,0 +1,20 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class IlluminationModelCommand : IMaterialCommand +{ + public static string Name => "illum"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material == null) + { + throw new InvalidDataException("Illumination model (illum) must follow a material definition (newmtl)"); + } + context.Material.IlluminationModel = byte.Parse(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/NewMaterialCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/NewMaterialCommand.cs new file mode 100644 index 0000000..21617a3 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/NewMaterialCommand.cs @@ -0,0 +1,22 @@ +using ThreeDModels.Format.Obj.Display; + +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class NewMaterialCommand : IMaterialCommand +{ + public static string Name => "newmtl"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material != null) + { + context.Materials.Add(context.Material); + } + context.Material = new Material { Name = commandLine[1] }; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularColorCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularColorCommand.cs new file mode 100644 index 0000000..f2ad810 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularColorCommand.cs @@ -0,0 +1,27 @@ +using ThreeDModels.Format.Obj.Display; + +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class SpecularColorCommand : IMaterialCommand +{ + public static string Name => "Ks"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material == null) + { + throw new InvalidDataException("Specular color (Ks) must follow a material definition (newmtl)"); + } + context.Material.Specular = new Color + { + Red = float.Parse(commandLine[1]), + Green = float.Parse(commandLine[2]), + Blue = float.Parse(commandLine[3]), + }; + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularExponentCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularExponentCommand.cs new file mode 100644 index 0000000..0381fcb --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularExponentCommand.cs @@ -0,0 +1,20 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class SpecularExponentCommand : IMaterialCommand +{ + public static string Name => "Ns"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material == null) + { + throw new InvalidDataException("Specular exponent (Ns) must follow a material definition (newmtl)"); + } + context.Material.SpecularExponent = float.Parse(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/Materials/TransparencyCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/TransparencyCommand.cs new file mode 100644 index 0000000..f355467 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Materials/TransparencyCommand.cs @@ -0,0 +1,20 @@ +namespace ThreeDModels.Format.Obj.IO.Commands.Materials; + +internal class TransparencyCommand : IMaterialCommand +{ + public static string Name => "Tr"; + + public static void Read(List commandLine, MaterialReaderContext context) + { + if (context.Material == null) + { + throw new InvalidDataException("Transparency (Tr) must follow a material definition (newmtl)"); + } + context.Material.Transparency = float.Parse(commandLine[1]); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/GeometricVertexCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/GeometricVertexCommand.cs new file mode 100644 index 0000000..8e485aa --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/GeometricVertexCommand.cs @@ -0,0 +1,24 @@ +using ThreeDModels.Format.Obj.Vertex; + +namespace ThreeDModels.Format.Obj.IO.Commands.VertexData; + +internal class GeometricVertexCommand : ICommand +{ + public static string Name => "v"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + obj.GeometricVertices.Add(new GeometricVertex + { + X = float.Parse(commandLine[1]), + Y = float.Parse(commandLine[2]), + Z = float.Parse(commandLine[3]), + W = commandLine.Count > 4 ? float.Parse(commandLine[4]) : 1, + }); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/ParameterSpaceVertexCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/ParameterSpaceVertexCommand.cs new file mode 100644 index 0000000..b026608 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/ParameterSpaceVertexCommand.cs @@ -0,0 +1,23 @@ +using ThreeDModels.Format.Obj.Vertex; + +namespace ThreeDModels.Format.Obj.IO.Commands.VertexData; + +internal class ParameterSpaceVertexCommand : ICommand +{ + public static string Name => "vp"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + obj.ParameterSpaceVertices.Add(new ParameterSpaceVertex + { + U = float.Parse(commandLine[1]), + V = commandLine.Count > 2 ? float.Parse(commandLine[2]) : 0.0f, + W = commandLine.Count > 3 ? float.Parse(commandLine[3]) : 1.0f, + }); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/TextureVertexCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/TextureVertexCommand.cs new file mode 100644 index 0000000..bfaadc7 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/TextureVertexCommand.cs @@ -0,0 +1,23 @@ +using ThreeDModels.Format.Obj.Vertex; + +namespace ThreeDModels.Format.Obj.IO.Commands.VertexData; + +internal class TextureVertexCommand : ICommand +{ + public static string Name => "vt"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + obj.TextureVertices.Add(new TextureVertex + { + U = float.Parse(commandLine[1]), + V = commandLine.Count > 2 ? float.Parse(commandLine[2]) : 0.0f, + W = commandLine.Count > 3 ? float.Parse(commandLine[3]) : 0.0f, + }); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/VertexNormalCommand.cs b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/VertexNormalCommand.cs new file mode 100644 index 0000000..a4c84b1 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/VertexData/VertexNormalCommand.cs @@ -0,0 +1,23 @@ +using ThreeDModels.Format.Obj.Vertex; + +namespace ThreeDModels.Format.Obj.IO.Commands.VertexData; + +internal class VertexNormalCommand : ICommand +{ + public static string Name => "vn"; + + public static void Read(List commandLine, ObjReaderContext context, ref Obj obj) + { + obj.VertexNormals.Add(new VertexNormal + { + I = float.Parse(commandLine[1]), + J = float.Parse(commandLine[2]), + K = float.Parse(commandLine[3]), + }); + } + + public static void Write() + { + throw new NotImplementedException(); + } +} diff --git a/src/ThreeDModels/Format/Obj/IO/ObjReader.cs b/src/ThreeDModels/Format/Obj/IO/ObjReader.cs index bfae3be..6c526bb 100644 --- a/src/ThreeDModels/Format/Obj/IO/ObjReader.cs +++ b/src/ThreeDModels/Format/Obj/IO/ObjReader.cs @@ -1,12 +1,57 @@ -using ThreeDModels.Format.Obj.Display; -using ThreeDModels.Format.Obj.Elements; using ThreeDModels.Format.Obj.Grouping; -using ThreeDModels.Format.Obj.Vertex; +using ThreeDModels.Format.Obj.IO.Commands; +using ThreeDModels.Format.Obj.IO.Commands.DisplayAttributes; +using ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceAttributes; +using ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceBodyStatements; +using ThreeDModels.Format.Obj.IO.Commands.FreeFormCurveSurfaceElements; +using ThreeDModels.Format.Obj.IO.Commands.Grouping; +using ThreeDModels.Format.Obj.IO.Commands.VertexData; namespace ThreeDModels.Format.Obj.IO; public class ObjReader { + private readonly Dictionary _commands = new() + { + // Vertex data + {GeometricVertexCommand.Name, GeometricVertexCommand.Read}, + {TextureVertexCommand.Name, TextureVertexCommand.Read}, + {VertexNormalCommand.Name, VertexNormalCommand.Read}, + {ParameterSpaceVertexCommand.Name, ParameterSpaceVertexCommand.Read}, + // Free-form curve/surface elements + {PointCommand.Name, PointCommand.Read}, + {LineCommand.Name, LineCommand.Read}, + {FaceCommand.Name, FaceCommand.Read}, + {CurveCommand.Name, CurveCommand.Read}, + {Curve2Command.Name, Curve2Command.Read}, + {SurfaceCommand.Name, SurfaceCommand.Read}, + // Free-form curve/surface attributes + {CurveSurfaceTypeCommand.Name, CurveSurfaceTypeCommand.Read}, + {DegreeCommand.Name, DegreeCommand.Read}, + {BasisMatrixCommand.Name, BasisMatrixCommand.Read}, + {StepCommand.Name, StepCommand.Read}, + // Free-form curve/surface body statements + {ParameterCommand.Name, ParameterCommand.Read}, + {TrimCommand.Name, TrimCommand.Read}, + {HoleCommand.Name, HoleCommand.Read}, + {SpecialCurveCommand.Name, SpecialCurveCommand.Read}, + {SpecialPointCommand.Name, SpecialPointCommand.Read}, + {EndFreeFormGeometryCommand.Name, EndFreeFormGeometryCommand.Read}, + // Grouping + {GroupCommand.Name, GroupCommand.Read}, + {SmoothingGroupCommand.Name, SmoothingGroupCommand.Read}, + {MergingGroupCommand.Name, MergingGroupCommand.Read}, + {ObjectCommand.Name, ObjectCommand.Read}, + // Display/render attributes + {BevelCommand.Name, BevelCommand.Read}, + {ColorInterpolationCommand.Name, ColorInterpolationCommand.Read}, + {DissolveInterpolationCommand.Name, DissolveInterpolationCommand.Read}, + {LevelOfDetailCommand.Name, LevelOfDetailCommand.Read}, + {MaterialLibraryCommand.Name, MaterialLibraryCommand.Read}, + {UseMapCommand.Name, UseMapCommand.Read}, + {UseMaterialCommand.Name, UseMaterialCommand.Read}, + }; + public Obj Execute(string path) { using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); @@ -16,10 +61,7 @@ public Obj Execute(string path) public Obj Execute(Stream stream) { var obj = new Obj(); - var group = new Group(); - var elementName = string.Empty; - DisplayAttributes? display = null; - FreeFormGeometry? freeFormGeometry = null; + var context = new ObjReaderContext(group: new Group(), elementName: string.Empty, display: null, freeFormGeometry: null); using var textReader = new StreamReader(stream); while (stream.Position < stream.Length) { @@ -28,665 +70,17 @@ public Obj Execute(Stream stream) { continue; } - switch (commandLine[0]) - { - #region Vertex data - case "v": - { - obj.GeometricVertices.Add(new GeometricVertex - { - X = float.Parse(commandLine[1]), - Y = float.Parse(commandLine[2]), - Z = float.Parse(commandLine[3]), - W = commandLine.Count > 4 ? float.Parse(commandLine[4]) : 1, - }); - break; - } - case "vt": - { - obj.TextureVertices.Add(new TextureVertex - { - U = float.Parse(commandLine[1]), - V = commandLine.Count > 2 ? float.Parse(commandLine[2]) : 0.0f, - W = commandLine.Count > 3 ? float.Parse(commandLine[3]) : 0.0f, - }); - break; - } - case "vn": - { - obj.VertexNormals.Add(new VertexNormal - { - I = float.Parse(commandLine[1]), - J = float.Parse(commandLine[2]), - K = float.Parse(commandLine[3]), - }); - break; - } - case "vp": - { - obj.ParameterSpaceVertices.Add(new ParameterSpaceVertex - { - U = float.Parse(commandLine[1]), - V = commandLine.Count > 2 ? float.Parse(commandLine[2]) : 0.0f, - W = commandLine.Count > 3 ? float.Parse(commandLine[3]) : 1.0f, - }); - break; - } - #endregion - #region Free-form curve/surface elements - case "p": - { - var point = new Point() - { - VertexIndices = [], - Name = elementName, - Display = display, - }; - for (int i = 1; i < commandLine.Count; i++) - { - point.VertexIndices.Add(int.Parse(commandLine[i])); - } - group.Points.Add(point); - break; - } - case "l": - { - var line = new Line() - { - VertexIndices = [], - Name = elementName, - Display = display, - }; - for (int i = 1; i < commandLine.Count; i++) - { - var fields = commandLine[i].Split(Constants.FieldSeparator); - line.VertexIndices.Add(new VertexReference - { - VertexIndex = int.Parse(fields[0]), - VertexTextureIndex = fields.Length > 1 ? int.Parse(fields[1]) : null - }); - } - group.Lines.Add(line); - break; - } - case "f": - { - var face = new Face() - { - VertexReferences = [], - Name = elementName, - Display = display, - }; - if (commandLine.Count < 4) - { - throw new InvalidDataException("Face (f) must have at least 3 vertices"); - } - var requireVertexTextureIndex = false; - var requireVertexNormalIndex = false; - for (int i = 1; i < commandLine.Count; i++) - { - var fields = commandLine[i].Split(Constants.FieldSeparator); - if (i == 1) - { - requireVertexTextureIndex = fields.Length > 1 && !string.IsNullOrEmpty(fields[1]); - requireVertexNormalIndex = fields.Length > 2 && !string.IsNullOrEmpty(fields[2]); - } - if ((requireVertexTextureIndex && (fields.Length < 2 || string.IsNullOrEmpty(fields[1]))) - || (requireVertexNormalIndex && (fields.Length < 3 || string.IsNullOrEmpty(fields[2])))) - { - throw new InvalidDataException("Inconsistent face (f) fields"); - } - face.VertexReferences.Add(new VertexReference() - { - VertexIndex = int.Parse(fields[0]), - VertexTextureIndex = requireVertexTextureIndex ? int.Parse(fields[1]) : null, - VertexNormalIndex = requireVertexNormalIndex ? int.Parse(fields[2]) : null - }); - } - group.Faces.Add(face); - break; - } - case "curv": - { - if (commandLine.Count == 1) - { - throw new InvalidDataException("Curve (curv) must have a starting parameter value"); - } - if (commandLine.Count == 2) - { - throw new InvalidDataException("Curve (curv) must have an ending parameter value"); - } - if (commandLine.Count < 5) - { - throw new InvalidDataException("Curve (curv) must have at least 2 control vertices"); - } - var curve = new Curve() - { - ParameterRange = new Range(start: float.Parse(commandLine[1]), end: float.Parse(commandLine[2])), - ControlVertexIndices = [], - Name = elementName, - Display = display, - }; - for (int i = 3; i < commandLine.Count; i++) - { - curve.ControlVertexIndices.Add(int.Parse(commandLine[i])); - } - freeFormGeometry = curve; - break; - } - case "curv2": - { - if (commandLine.Count < 3) - { - throw new InvalidDataException("Curve2D (curv2) must have at least 2 control points"); - } - var curve2D = new Curve2D() - { - ParameterVertexIndices = [], - Name = elementName, - Display = display, - }; - for (int i = 1; i < commandLine.Count; i++) - { - curve2D.ParameterVertexIndices.Add(int.Parse(commandLine[i])); - } - freeFormGeometry = curve2D; - break; - } - case "surf": - { - if (commandLine.Count == 1) - { - throw new InvalidDataException("Curve (curv) must have a starting parameter value"); - } - if (commandLine.Count == 2) - { - throw new InvalidDataException("Curve (curv) must have an ending parameter value"); - } - if (commandLine.Count < 5) - { - throw new InvalidDataException("Curve (curv) must have at least 2 control vertices"); - } - var surface = new Surface() - { - UParameterRange = new Range(start: float.Parse(commandLine[1]), end: float.Parse(commandLine[2])), - VParameterRange = new Range(start: float.Parse(commandLine[3]), end: float.Parse(commandLine[4])), - VertexReferences = [], - Name = elementName, - Display = display, - }; - var requireVertexTextureIndex = false; - var requireVertexNormalIndex = false; - for (int i = 4; i < commandLine.Count; i++) - { - var fields = commandLine[i].Split(Constants.FieldSeparator); - if (i == 1) - { - requireVertexTextureIndex = fields.Length > 1 && !string.IsNullOrEmpty(fields[1]); - requireVertexNormalIndex = fields.Length > 2 && !string.IsNullOrEmpty(fields[2]); - } - if ((requireVertexTextureIndex && (fields.Length < 2 || string.IsNullOrEmpty(fields[1]))) - || (requireVertexNormalIndex && fields.Length < 3 && !string.IsNullOrEmpty(fields[2]))) - { - throw new InvalidDataException("Inconsistent surface (surf) fields"); - } - surface.VertexReferences.Add(new VertexReference() - { - VertexIndex = int.Parse(fields[0]), - VertexTextureIndex = requireVertexTextureIndex ? int.Parse(fields[1]) : null, - VertexNormalIndex = requireVertexNormalIndex ? int.Parse(fields[2]) : null - }); - } - freeFormGeometry = surface; - break; - } - #endregion - #region Free-form curve/surface attributes - case "cstype": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("CurveSurfaceType (cstype) must follow a curve or surface element"); - } - if (commandLine.Count > 2) - { - freeFormGeometry.IsRational = commandLine[1] == "rat"; - freeFormGeometry.Type = Enum.Parse(commandLine[2]); - } - else - { - freeFormGeometry.Type = Enum.Parse(commandLine[1]); - } - break; - } - case "deg": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("Degree (deg) must follow a curve or surface element"); - } - freeFormGeometry.DegreeU = int.Parse(commandLine[1]); - freeFormGeometry.DegreeV = commandLine.Count > 2 ? int.Parse(commandLine[2]) : null; - break; - } - case "bmat": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("BasisMatrix (bmat) must follow a curve or surface element"); - } - var matrix = new List(); - for (int i = 2; i < commandLine.Count; i++) - { - matrix.Add(float.Parse(commandLine[i])); - } - if (commandLine[1] == "u") - { - freeFormGeometry.BasisMatrixU = matrix; - } - else - { - freeFormGeometry.BasisMatrixV = commandLine[1] == "v" - ? matrix - : throw new InvalidDataException($"BasisMatrix (bmat) has an invalid direction '{commandLine[1]}'"); - } - break; - } - case "step": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("Step (step) must follow a curve or surface element"); - } - freeFormGeometry.StepU = int.Parse(commandLine[1]); - freeFormGeometry.StepV = commandLine.Count > 2 ? int.Parse(commandLine[2]) : null; - break; - } - #endregion - #region Free-form curve/surface body statements - case "parm": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("Parm (parm) must follow a curve or surface element"); - } - var matrix = new List(); - for (int i = 2; i < commandLine.Count; i++) - { - matrix.Add(float.Parse(commandLine[i])); - } - if (commandLine[1] == "u") - { - freeFormGeometry.ParameterU = matrix; - } - else - { - freeFormGeometry.ParameterV = commandLine[1] == "v" - ? matrix - : throw new InvalidDataException($"BasisMatrix (bmat) has an invalid direction '{commandLine[1]}'"); - } - break; - } - case "trim": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("Trim (trim) must follow a curve or surface element"); - } - var curve2Ds = new List(); - for (int i = 1; i < commandLine.Count; i += 3) - { - curve2Ds.Add(new() - { - Start = float.Parse(commandLine[i]), - End = float.Parse(commandLine[i + 1]), - Curve2DIndex = int.Parse(commandLine[i + 2]), - }); - } - freeFormGeometry.TrimmingCurves ??= []; - freeFormGeometry.TrimmingCurves.Add(curve2Ds); - break; - } - case "hole": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("Hole (hole) must follow a curve or surface element"); - } - var curve2Ds = new List(); - for (int i = 1; i < commandLine.Count; i += 3) - { - curve2Ds.Add(new() - { - Start = float.Parse(commandLine[i]), - End = float.Parse(commandLine[i + 1]), - Curve2DIndex = int.Parse(commandLine[i + 2]), - }); - } - freeFormGeometry.Holes ??= []; - freeFormGeometry.Holes.Add(curve2Ds); - break; - } - case "scrv": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("SpecialCurve (scrv) must follow a curve or surface element"); - } - var curve2Ds = new List(); - for (int i = 1; i < commandLine.Count; i += 3) - { - curve2Ds.Add(new() - { - Start = float.Parse(commandLine[i]), - End = float.Parse(commandLine[i + 1]), - Curve2DIndex = int.Parse(commandLine[i + 2]), - }); - } - freeFormGeometry.SpecialCurves ??= []; - freeFormGeometry.SpecialCurves.Add(curve2Ds); - break; - } - case "sp": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("SpecialPoint (sp) must follow a curve or surface element"); - } - var geometricPointsIndices = new List(); - for (int i = 1; i < commandLine.Count; i += 3) - { - geometricPointsIndices.Add(int.Parse(commandLine[i])); - } - freeFormGeometry.SpecialCurves ??= []; - freeFormGeometry.SpecialPoints.Add(geometricPointsIndices); - break; - } - case "end": - { - if (freeFormGeometry == null) - { - throw new InvalidDataException("The curve or surface element is not initialized"); - } - else if (freeFormGeometry is Curve curve) - { - group.Curves.Add(curve); - } - else if (freeFormGeometry is Curve2D curve2D) - { - group.Curves2D.Add(curve2D); - } - else if (freeFormGeometry is Surface surface) - { - group.Surfaces.Add(surface); - } - else - { - throw new InvalidDataException("Unknown free-form geometry type"); - } - freeFormGeometry = null; - break; - } - #endregion - #region Grouping - case "g": - { - if (group != null) - { - obj.Groups.Add(group); - } - group = new Group() - { - Names = commandLine.Count == 1 ? [Constants.DefaultGroup] : commandLine.GetRange(1, commandLine.Count - 1), - }; - display = null; - break; - } - case "sg": - { - int groupNumber = -1; - if (!int.TryParse(commandLine[1], out groupNumber) && commandLine[1] != Constants.Off || groupNumber < 0 || groupNumber > group.Names!.Count) - { - throw new InvalidDataException("Invalid smoothing group number"); - } - if (commandLine[1] == Constants.Off || groupNumber == 0) - { - group.SmoothingGroupIndices = []; - break; - } - else if (!group.SmoothingGroupIndices.Contains(groupNumber - 1)) - { - group.SmoothingGroupIndices.Add(groupNumber - 1); - } - break; - } - case "mg": - { - int groupNumber = -1; - if (!int.TryParse(commandLine[1], out groupNumber) && commandLine[1] != Constants.Off || groupNumber < 0 || groupNumber > group.Names!.Count) - { - throw new InvalidDataException("Invalid merging group number"); - } - if (commandLine[1] == Constants.Off || groupNumber == 0) - { - group.MergingGroups = []; - break; - } - else if (!group.MergingGroups.Any(mergingGroup => mergingGroup.GroupIndex == groupNumber - 1)) - { - var resolution = float.Parse(commandLine[2]); - group.MergingGroups.Add(new MergingGroup { GroupIndex = groupNumber - 1, Resolution = resolution }); - } - break; - } - case "o": - { - elementName = commandLine[1]; - break; - } - #endregion - #region Display/render attributes - case "bevel": - { - display ??= new DisplayAttributes(); - display.BevelInterpolationEnabled = GetBool(commandLine[1]); - break; - } - case "c_interp": - { - display ??= new DisplayAttributes(); - display.ColorInterpolationEnabled = GetBool(commandLine[1]); - break; - } - case "d_interp": - { - display ??= new DisplayAttributes(); - display.DissolveInterpolationEnabled = GetBool(commandLine[1]); - break; - } - case "lod": - { - display ??= new DisplayAttributes(); - display.LevelOfDetail = int.Parse(commandLine[1]); - break; - } - case "maplib": - { - // TODO: Implement maplib - break; - } - case "mtllib": - { - for (int i = 1; i < commandLine.Count; i++) - { - obj.MaterialLibraries.Add(GetMaterials(commandLine[i])); - } - break; - } - case "usemap": - { - display ??= new DisplayAttributes(); - display.TextureMapName = commandLine.Count > 1 && commandLine[1] != Constants.Off ? commandLine[1] : string.Empty; - break; - } - case "usemtl": - { - display ??= new DisplayAttributes(); - display.MaterialName = commandLine.Count > 1 ? commandLine[1] : string.Empty; - break; - } - case "shadow_obj": - { - // TODO: Implement shadow_obj - break; - } - case "trace_obj": - { - // TODO: Implement trace_obj - break; - } - case "ctech": - { - // TODO: Implement ctech - break; - } - case "stech": - { - // TODO: Implement stech - break; - } - #endregion - default: - { - throw new InvalidDataException($"Unknown command '{commandLine[0]}'"); - } - } - } - if (group != null) - { - obj.Groups.Add(group); - } - return obj; - } - - public List GetMaterials(string path) - { - using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); - return GetMaterials(fs); - } - - public List GetMaterials(Stream stream) - { - var materials = new List(); - Material? material = null; - using var textReader = new StreamReader(stream); - while (stream.Position < stream.Length) - { - var commandLine = GetCommandLine(textReader); - if (commandLine.Count == 0) + if (_commands.TryGetValue(commandLine[0], out var readCommand)) { - continue; + readCommand.Invoke(commandLine, context, ref obj); } - switch (commandLine[0]) + else { - case "newmtl": - { - if (material != null) - { - materials.Add(material); - } - material = new Material { Name = commandLine[1] }; - break; - } - case "Ka": - { - if (material == null) - { - throw new InvalidDataException("Ambient color (Ka) must follow a material definition (newmtl)"); - } - material.Ambient = new Color - { - Red = float.Parse(commandLine[1]), - Green = float.Parse(commandLine[2]), - Blue = float.Parse(commandLine[3]), - }; - break; - } - case "Kd": - { - if (material == null) - { - throw new InvalidDataException("Diffuse color (Kd) must follow a material definition (newmtl)"); - } - material.Diffuse = new Color - { - Red = float.Parse(commandLine[1]), - Green = float.Parse(commandLine[2]), - Blue = float.Parse(commandLine[3]), - }; - break; - } - case "Ks": - { - if (material == null) - { - throw new InvalidDataException("Specular color (Ks) must follow a material definition (newmtl)"); - } - material.Specular = new Color - { - Red = float.Parse(commandLine[1]), - Green = float.Parse(commandLine[2]), - Blue = float.Parse(commandLine[3]), - }; - break; - } - case "Ns": - { - if (material == null) - { - throw new InvalidDataException("Specular exponent (Ns) must follow a material definition (newmtl)"); - } - material.SpecularExponent = float.Parse(commandLine[1]); - break; - } - case "d": - { - if (material == null) - { - throw new InvalidDataException("Transparency (d) must follow a material definition (newmtl)"); - } - material.Transparency = 1 - float.Parse(commandLine[1]); - break; - } - case "Tr": - { - if (material == null) - { - throw new InvalidDataException("Transparency (Tr) must follow a material definition (newmtl)"); - } - material.Transparency = float.Parse(commandLine[1]); - break; - } - case "illum": - { - if (material == null) - { - throw new InvalidDataException("Illumination model (illum) must follow a material definition (newmtl)"); - } - material.IlluminationModel = byte.Parse(commandLine[1]); - break; - } - default: - { - throw new InvalidDataException($"Unknown command '{commandLine[0]}'"); - } + throw new InvalidDataException($"Unknown command '{commandLine[0]}'"); } } - if (material != null) - { - materials.Add(material); - } - return materials; + obj.Groups.Add(context.Group); + return obj; } internal static bool GetBool(string value)