From 087bc8aafb2749d7870bb5a93e7023ed2464718e Mon Sep 17 00:00:00 2001 From: Bezaleel Olakunori Date: Sat, 14 Sep 2024 19:06:43 +0200 Subject: [PATCH 1/7] Move Ctm reading files to the IO subfolder and namespace --- src/ThreeDModels/Format/Ctm/{ => IO}/CompressionMethod.cs | 2 +- src/ThreeDModels/Format/Ctm/{ => IO}/Constants.cs | 2 +- src/ThreeDModels/Format/Ctm/{ => IO}/CtmReader.cs | 2 +- src/ThreeDModels/Format/Ctm/{ => IO}/Flag.cs | 2 +- src/ThreeDModels/Format/Ctm/{ => IO}/Identifier.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename src/ThreeDModels/Format/Ctm/{ => IO}/CompressionMethod.cs (72%) rename src/ThreeDModels/Format/Ctm/{ => IO}/Constants.cs (76%) rename src/ThreeDModels/Format/Ctm/{ => IO}/CtmReader.cs (99%) rename src/ThreeDModels/Format/Ctm/{ => IO}/Flag.cs (64%) rename src/ThreeDModels/Format/Ctm/{ => IO}/Identifier.cs (91%) diff --git a/src/ThreeDModels/Format/Ctm/CompressionMethod.cs b/src/ThreeDModels/Format/Ctm/IO/CompressionMethod.cs similarity index 72% rename from src/ThreeDModels/Format/Ctm/CompressionMethod.cs rename to src/ThreeDModels/Format/Ctm/IO/CompressionMethod.cs index 0e9f6c1..dbef3cb 100644 --- a/src/ThreeDModels/Format/Ctm/CompressionMethod.cs +++ b/src/ThreeDModels/Format/Ctm/IO/CompressionMethod.cs @@ -1,4 +1,4 @@ -namespace ThreeDModels.Format.Ctm; +namespace ThreeDModels.Format.Ctm.IO; public enum CompressionMethod { diff --git a/src/ThreeDModels/Format/Ctm/Constants.cs b/src/ThreeDModels/Format/Ctm/IO/Constants.cs similarity index 76% rename from src/ThreeDModels/Format/Ctm/Constants.cs rename to src/ThreeDModels/Format/Ctm/IO/Constants.cs index e09997c..1bd845b 100644 --- a/src/ThreeDModels/Format/Ctm/Constants.cs +++ b/src/ThreeDModels/Format/Ctm/IO/Constants.cs @@ -1,4 +1,4 @@ -namespace ThreeDModels.Format.Ctm; +namespace ThreeDModels.Format.Ctm.IO; public static class Constants { diff --git a/src/ThreeDModels/Format/Ctm/CtmReader.cs b/src/ThreeDModels/Format/Ctm/IO/CtmReader.cs similarity index 99% rename from src/ThreeDModels/Format/Ctm/CtmReader.cs rename to src/ThreeDModels/Format/Ctm/IO/CtmReader.cs index 696be98..0f5c22f 100644 --- a/src/ThreeDModels/Format/Ctm/CtmReader.cs +++ b/src/ThreeDModels/Format/Ctm/IO/CtmReader.cs @@ -1,7 +1,7 @@ using SevenZip.Compression.LZMA; using ThreeDModels.Format.Ctm.Elements; -namespace ThreeDModels.Format.Ctm; +namespace ThreeDModels.Format.Ctm.IO; public class CtmReader { diff --git a/src/ThreeDModels/Format/Ctm/Flag.cs b/src/ThreeDModels/Format/Ctm/IO/Flag.cs similarity index 64% rename from src/ThreeDModels/Format/Ctm/Flag.cs rename to src/ThreeDModels/Format/Ctm/IO/Flag.cs index 30141c6..e0aa2e0 100644 --- a/src/ThreeDModels/Format/Ctm/Flag.cs +++ b/src/ThreeDModels/Format/Ctm/IO/Flag.cs @@ -1,4 +1,4 @@ -namespace ThreeDModels.Format.Ctm; +namespace ThreeDModels.Format.Ctm.IO; public static class Flag { diff --git a/src/ThreeDModels/Format/Ctm/Identifier.cs b/src/ThreeDModels/Format/Ctm/IO/Identifier.cs similarity index 91% rename from src/ThreeDModels/Format/Ctm/Identifier.cs rename to src/ThreeDModels/Format/Ctm/IO/Identifier.cs index b2c5fcb..69c16ee 100644 --- a/src/ThreeDModels/Format/Ctm/Identifier.cs +++ b/src/ThreeDModels/Format/Ctm/IO/Identifier.cs @@ -1,4 +1,4 @@ -namespace ThreeDModels.Format.Ctm; +namespace ThreeDModels.Format.Ctm.IO; public static class Identifier { From aeb6bb804173715053fd225511433dee60f84d1e Mon Sep 17 00:00:00 2001 From: Bezaleel Olakunori Date: Sat, 14 Sep 2024 19:28:30 +0200 Subject: [PATCH 2/7] Move exposed enums to the Elements subfolder and namespace --- .../Format/Ctm/{IO => Elements}/CompressionMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/ThreeDModels/Format/Ctm/{IO => Elements}/CompressionMethod.cs (69%) diff --git a/src/ThreeDModels/Format/Ctm/IO/CompressionMethod.cs b/src/ThreeDModels/Format/Ctm/Elements/CompressionMethod.cs similarity index 69% rename from src/ThreeDModels/Format/Ctm/IO/CompressionMethod.cs rename to src/ThreeDModels/Format/Ctm/Elements/CompressionMethod.cs index dbef3cb..7e62e31 100644 --- a/src/ThreeDModels/Format/Ctm/IO/CompressionMethod.cs +++ b/src/ThreeDModels/Format/Ctm/Elements/CompressionMethod.cs @@ -1,4 +1,4 @@ -namespace ThreeDModels.Format.Ctm.IO; +namespace ThreeDModels.Format.Ctm.Elements; public enum CompressionMethod { From 8447ae9e5cff9ca6ca2fb8a9ce1456504699173b Mon Sep 17 00:00:00 2001 From: Bezaleel Olakunori Date: Sat, 14 Sep 2024 20:18:00 +0200 Subject: [PATCH 3/7] Extract method-specific routines to separate classes and functions --- src/ThreeDModels/Format/Ctm/IO/CtmReader.cs | 435 +------------------- src/ThreeDModels/Format/Ctm/IO/Data3D.cs | 11 + src/ThreeDModels/Format/Ctm/IO/Mg1Reader.cs | 185 +++++++++ src/ThreeDModels/Format/Ctm/IO/Mg2Header.cs | 3 + src/ThreeDModels/Format/Ctm/IO/Mg2Reader.cs | 218 ++++++++++ src/ThreeDModels/Format/Ctm/IO/RawReader.cs | 122 ++++++ 6 files changed, 543 insertions(+), 431 deletions(-) create mode 100644 src/ThreeDModels/Format/Ctm/IO/Data3D.cs create mode 100644 src/ThreeDModels/Format/Ctm/IO/Mg1Reader.cs create mode 100644 src/ThreeDModels/Format/Ctm/IO/Mg2Header.cs create mode 100644 src/ThreeDModels/Format/Ctm/IO/Mg2Reader.cs create mode 100644 src/ThreeDModels/Format/Ctm/IO/RawReader.cs diff --git a/src/ThreeDModels/Format/Ctm/IO/CtmReader.cs b/src/ThreeDModels/Format/Ctm/IO/CtmReader.cs index 0f5c22f..6997864 100644 --- a/src/ThreeDModels/Format/Ctm/IO/CtmReader.cs +++ b/src/ThreeDModels/Format/Ctm/IO/CtmReader.cs @@ -41,17 +41,17 @@ public Ctm Execute(Stream stream) { case CompressionMethod.RAW: { - ReadBodyAsRaw(reader, verticesCount, trianglesCount, uvMapsCount, attributeMapsCount, ref ctm); + RawReader.Read(reader, verticesCount, trianglesCount, uvMapsCount, attributeMapsCount, ref ctm); break; } case CompressionMethod.MG1: { - ReadBodyAsMg1(reader, verticesCount, trianglesCount, uvMapsCount, attributeMapsCount, ref ctm); + Mg1Reader.Read(reader, verticesCount, trianglesCount, uvMapsCount, attributeMapsCount, ref ctm); break; } case CompressionMethod.MG2: { - ReadBodyAsMg2(reader, verticesCount, trianglesCount, uvMapsCount, attributeMapsCount, ref ctm); + Mg2Reader.Read(reader, verticesCount, trianglesCount, uvMapsCount, attributeMapsCount, ref ctm); break; } default: @@ -62,428 +62,7 @@ public Ctm Execute(Stream stream) return ctm; } - internal void ReadBodyAsRaw(BinaryReader reader, int verticesCount, int trianglesCount, int uvMapsCount, int attributeMapsCount, ref Ctm ctm) - { - for (var i = 0; i < trianglesCount; i++) - { - if (reader.ReadInt32() != Identifier.Indices) - { - throw new InvalidDataException("Invalid indices section"); - } - ctm.Triangles.Add(new Triangle() - { - Vertex1 = reader.ReadInt32(), - Vertex2 = reader.ReadInt32(), - Vertex3 = reader.ReadInt32(), - }); - } - for (var i = 0; i < verticesCount; i++) - { - if (reader.ReadInt32() != Identifier.Vertices) - { - throw new InvalidDataException("Invalid vertices section"); - } - ctm.Vertices.Add(new Vertex() - { - X = reader.ReadSingle(), - Y = reader.ReadSingle(), - Z = reader.ReadSingle(), - }); - } - if ((ctm.Flags & Flag.HasNormals) == Flag.HasNormals) - { - for (var i = 0; i < verticesCount; i++) - { - if (reader.ReadInt32() != Identifier.Normals) - { - throw new InvalidDataException("Invalid normals section"); - } - ctm.Vertices[i].Normal = new Normal() - { - X = reader.ReadSingle(), - Y = reader.ReadSingle(), - Z = reader.ReadSingle(), - }; - } - } - for (var i = 0; i < uvMapsCount; i++) - { - if (reader.ReadInt32() != Identifier.UVMaps) - { - throw new InvalidDataException("Invalid uv maps section"); - } - var uvMap = new UvMap() - { - Name = ReadString(reader), - Filename = ReadString(reader), - Coordinates = new List(verticesCount), - }; - for (var j = 0; j < verticesCount; j++) - { - uvMap.Coordinates.Add(new UvCoordinate() - { - U = reader.ReadSingle(), - V = reader.ReadSingle(), - }); - } - ctm.UvMaps.Add(uvMap); - } - for (var i = 0; i < attributeMapsCount; i++) - { - if (reader.ReadInt32() != Identifier.AttributeMaps) - { - throw new InvalidDataException("Invalid attribute maps section"); - } - var attributeMap = new AttributeMap() - { - Name = ReadString(reader), - Values = new List(verticesCount), - }; - for (var j = 0; j < verticesCount; j++) - { - attributeMap.Values.Add(new AttributeValue() - { - A = reader.ReadSingle(), - B = reader.ReadSingle(), - C = reader.ReadSingle(), - D = reader.ReadSingle(), - }); - } - ctm.AttributeMaps.Add(attributeMap); - } - } - - internal void ReadBodyAsMg1(BinaryReader reader, int verticesCount, int trianglesCount, int uvMapsCount, int attributeMapsCount, ref Ctm ctm) - { - if (trianglesCount > 0 && reader.ReadInt32() != Identifier.Indices) - { - throw new InvalidDataException("Invalid indices section"); - } - using var trianglesReader = new BinaryReader(Unpack(reader, trianglesCount * 3 * sizeof(int))); - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < trianglesCount; j++) - { - var deltaIndex = trianglesReader.ReadInt32(); - if (i == 0) - { - ctm.Triangles.Add(new Triangle() - { - Vertex1 = j > 0 ? deltaIndex + ctm.Triangles[j - 1].Vertex1 : deltaIndex, - Vertex2 = 0, - Vertex3 = 0, - }); - } - else if (i == 1) - { - ctm.Triangles[j].Vertex2 = j > 0 && ctm.Triangles[j].Vertex1 == ctm.Triangles[j - 1].Vertex1 ? deltaIndex + ctm.Triangles[j - 1].Vertex2 : deltaIndex + ctm.Triangles[j].Vertex1; - } - else - { - ctm.Triangles[j].Vertex3 = deltaIndex + ctm.Triangles[j].Vertex1; - } - } - } - if (verticesCount > 0 && reader.ReadInt32() != Identifier.Vertices) - { - throw new InvalidDataException("Invalid vertices section"); - } - using var verticesReader = new BinaryReader(Unpack(reader, verticesCount * 3 * sizeof(float))); - for (var i = 0; i < verticesCount; i++) - { - ctm.Vertices.Add(new Vertex() - { - X = verticesReader.ReadSingle(), - Y = verticesReader.ReadSingle(), - Z = verticesReader.ReadSingle(), - }); - } - if ((ctm.Flags & Flag.HasNormals) == Flag.HasNormals) - { - if (verticesCount > 0 && reader.ReadInt32() != Identifier.Normals) - { - throw new InvalidDataException("Invalid normals section"); - } - using var normalsReader = new BinaryReader(Unpack(reader, verticesCount * 3 * sizeof(float))); - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < verticesCount; j++) - { - if (i == 0) - { - ctm.Vertices[j].Normal = new Normal() - { - X = normalsReader.ReadSingle(), - Y = 0, - Z = 0, - }; - } - else if (i == 1) - { - ctm.Vertices[j].Normal!.Y = normalsReader.ReadSingle(); - } - else - { - ctm.Vertices[j].Normal!.Z = normalsReader.ReadSingle(); - } - - } - } - } - for (var i = 0; i < uvMapsCount; i++) - { - if (reader.ReadInt32() != Identifier.UVMaps) - { - throw new InvalidDataException("Invalid uv maps section"); - } - var uvMap = new UvMap() - { - Name = ReadString(reader), - Filename = ReadString(reader), - Coordinates = new List(verticesCount), - }; - using var uvCoordinatesReader = new BinaryReader(Unpack(reader, verticesCount * 2 * sizeof(float))); - for (int j = 0; j < 2; j++) - { - for (int k = 0; k < verticesCount; k++) - { - if (j == 0) - { - uvMap.Coordinates.Add(new UvCoordinate() - { - U = uvCoordinatesReader.ReadSingle(), - V = 0, - }); - } - else - { - uvMap.Coordinates[k].V = uvCoordinatesReader.ReadSingle(); - } - } - } - ctm.UvMaps.Add(uvMap); - } - for (var i = 0; i < attributeMapsCount; i++) - { - if (reader.ReadInt32() != Identifier.AttributeMaps) - { - throw new InvalidDataException("Invalid attribute maps section"); - } - var attributeMap = new AttributeMap() - { - Name = ReadString(reader), - Values = new List(verticesCount), - }; - using var attributeValuesReader = new BinaryReader(Unpack(reader, verticesCount * 4 * sizeof(float))); - for (int j = 0; j < 4; j++) - { - for (int k = 0; k < verticesCount; k++) - { - if (j == 0) - { - attributeMap.Values.Add(new AttributeValue() - { - A = attributeValuesReader.ReadSingle(), - B = 0, - C = 0, - D = 0, - }); - } - else if (j == 1) - { - attributeMap.Values[k].B = attributeValuesReader.ReadSingle(); - } - else if (j == 2) - { - attributeMap.Values[k].C = attributeValuesReader.ReadSingle(); - } - else - { - attributeMap.Values[k].D = attributeValuesReader.ReadSingle(); - } - } - } - ctm.AttributeMaps.Add(attributeMap); - } - } - - internal void ReadBodyAsMg2(BinaryReader reader, int verticesCount, int trianglesCount, int uvMapsCount, int attributeMapsCount, ref Ctm ctm) - { - if (reader.ReadInt32() != Identifier.MG2H) - { - throw new InvalidDataException("Invalid MG2 header"); - } - var vertexPrecision = reader.ReadSingle(); - var normalPrecision = reader.ReadSingle(); - var lbX = reader.ReadSingle(); - var lbY = reader.ReadSingle(); - var lbZ = reader.ReadSingle(); - var ubX = reader.ReadSingle(); - var ubY = reader.ReadSingle(); - var ubZ = reader.ReadSingle(); - var divX = reader.ReadInt32(); - var divY = reader.ReadInt32(); - var divZ = reader.ReadInt32(); - if (verticesCount > 0 && reader.ReadInt32() != Identifier.Vertices) - { - throw new InvalidDataException("Invalid vertices section"); - } - using var verticesReader = new BinaryReader(Unpack(reader, verticesCount * 3 * sizeof(float))); - var gridIndices = new List(verticesCount); - if (verticesCount > 0 && reader.ReadInt32() != Identifier.GridIndices) - { - throw new InvalidDataException("Invalid grid indices section"); - } - using var gridIndicesReader = new BinaryReader(Unpack(reader, verticesCount * sizeof(int))); - for (var i = 0; i < verticesCount; i++) - { - var deltaGridIndex = gridIndicesReader.ReadInt32(); - gridIndices.Add(i == 0 ? deltaGridIndex : deltaGridIndex + gridIndices[i - 1]); - } - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < verticesCount; j++) - { - var deltaIndex = verticesReader.ReadInt32(); - if (i == 0) - { - var dx = j > 0 && gridIndices[j] == gridIndices[j - 1] - ? deltaIndex + ((ctm.Vertices[j - 1].X - GridOrigin(lbX, ubX, divX, gridIndices[j - 1])) / vertexPrecision) - : deltaIndex; - ctm.Vertices.Add(new Vertex() - { - X = vertexPrecision * dx + GridOrigin(lbX, ubX, divX, gridIndices[j]), - Y = 0, - Z = 0, - }); - } - else if (i == 1) - { - ctm.Vertices[j].Y = vertexPrecision * deltaIndex + GridOrigin(lbY, ubY, divY, gridIndices[j]); - } - else - { - ctm.Vertices[j].Z = vertexPrecision * deltaIndex + GridOrigin(lbZ, ubZ, divZ, gridIndices[j]); - } - } - } - if (trianglesCount > 0 && reader.ReadInt32() != Identifier.Indices) - { - throw new InvalidDataException("Invalid indices section"); - } - using var trianglesReader = new BinaryReader(Unpack(reader, trianglesCount * 3 * sizeof(int))); - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < trianglesCount; j++) - { - var deltaIndex = trianglesReader.ReadInt32(); - if (i == 0) - { - ctm.Triangles.Add(new Triangle() - { - Vertex1 = j > 0 ? deltaIndex + ctm.Triangles[j - 1].Vertex1 : deltaIndex, - Vertex2 = 0, - Vertex3 = 0, - }); - } - else if (i == 1) - { - ctm.Triangles[j].Vertex2 = j > 0 && ctm.Triangles[j].Vertex1 == ctm.Triangles[j - 1].Vertex1 ? deltaIndex + ctm.Triangles[j - 1].Vertex2 : deltaIndex + ctm.Triangles[j].Vertex1; - } - else - { - ctm.Triangles[j].Vertex3 = deltaIndex + ctm.Triangles[j].Vertex1; - } - } - } - if ((ctm.Flags & Flag.HasNormals) == Flag.HasNormals) - { - // TODO: Implement normal decoding - Unpack(reader, verticesCount * 2 * sizeof(float)); - throw new NotImplementedException(); - } - for (var i = 0; i < uvMapsCount; i++) - { - if (reader.ReadInt32() != Identifier.UVMaps) - { - throw new InvalidDataException("Invalid uv maps section"); - } - var uvMap = new UvMap() - { - Name = ReadString(reader), - Filename = ReadString(reader), - Coordinates = new List(verticesCount), - }; - var uvCoordinatePrecision = reader.ReadSingle(); - using var uvCoordinatesReader = new BinaryReader(Unpack(reader, verticesCount * 2 * sizeof(float))); - for (int j = 0; j < 2; j++) - { - for (int k = 0; k < verticesCount; k++) - { - var deltaCoordinate = uvCoordinatesReader.ReadInt32(); - if (j == 0) - { - uvMap.Coordinates.Add(new UvCoordinate() - { - U = k == 0 ? uvCoordinatePrecision * deltaCoordinate : uvCoordinatePrecision * (deltaCoordinate + uvMap.Coordinates[k - 1].U), - V = 0, - }); - } - else - { - uvMap.Coordinates[k].V = k == 0 ? uvCoordinatePrecision * deltaCoordinate : uvCoordinatePrecision * (deltaCoordinate + uvMap.Coordinates[k - 1].V); - } - } - } - ctm.UvMaps.Add(uvMap); - } - for (var i = 0; i < attributeMapsCount; i++) - { - if (reader.ReadInt32() != Identifier.AttributeMaps) - { - throw new InvalidDataException("Invalid attribute maps section"); - } - var attributeMap = new AttributeMap() - { - Name = ReadString(reader), - Values = new List(verticesCount), - }; - var attributeValuePrecision = reader.ReadSingle(); - using var attributeValuesReader = new BinaryReader(Unpack(reader, verticesCount * 4 * sizeof(float))); - for (int j = 0; j < 4; j++) - { - for (int k = 0; k < verticesCount; k++) - { - var deltaValue = attributeValuesReader.ReadInt32(); - if (j == 0) - { - attributeMap.Values.Add(new AttributeValue() - { - A = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].A), - B = 0, - C = 0, - D = 0, - }); - } - else if (j == 1) - { - attributeMap.Values[k].B = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].B); - } - else if (j == 2) - { - attributeMap.Values[k].C = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].C); - } - else - { - attributeMap.Values[k].D = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].D); - } - } - } - ctm.AttributeMaps.Add(attributeMap); - } - } - - internal Stream Unpack(BinaryReader reader, int unpackedSize) + internal static Stream UnpackLzmaStream(BinaryReader reader, int unpackedSize) { var packedSize = reader.ReadInt32(); var ms = new MemoryStream(unpackedSize); @@ -491,15 +70,9 @@ internal Stream Unpack(BinaryReader reader, int unpackedSize) decoder.SetDecoderProperties(reader.ReadBytes(Constants.PackedDataPropsSize)); decoder.Code(reader.BaseStream, ms, packedSize, unpackedSize, null); ms.Position = 0; - // packedSize += sizeof(int) + Constants.PackedDataPropsSize; return ms; } - internal float GridOrigin(float lb, float ub, int div, int g) - { - return lb + ((ub - lb) / div * g); - } - internal static string ReadString(BinaryReader reader) { var length = reader.ReadInt32(); diff --git a/src/ThreeDModels/Format/Ctm/IO/Data3D.cs b/src/ThreeDModels/Format/Ctm/IO/Data3D.cs new file mode 100644 index 0000000..8650066 --- /dev/null +++ b/src/ThreeDModels/Format/Ctm/IO/Data3D.cs @@ -0,0 +1,11 @@ +namespace ThreeDModels.Format.Ctm.IO; + +/// +/// Represents data about a 3D-axial reference. +/// +/// +/// +/// +/// +/// +internal record Data3D(T X, T Y, T Z); diff --git a/src/ThreeDModels/Format/Ctm/IO/Mg1Reader.cs b/src/ThreeDModels/Format/Ctm/IO/Mg1Reader.cs new file mode 100644 index 0000000..9d3839f --- /dev/null +++ b/src/ThreeDModels/Format/Ctm/IO/Mg1Reader.cs @@ -0,0 +1,185 @@ +using ThreeDModels.Format.Ctm.Elements; + +namespace ThreeDModels.Format.Ctm.IO; + +internal static class Mg1Reader +{ + internal static void Read(BinaryReader reader, int verticesCount, int trianglesCount, int uvMapsCount, int attributeMapsCount, ref Ctm ctm) + { + ReadTriangles(reader, trianglesCount, ref ctm); + ReadVertices(reader, verticesCount, ref ctm); + ReadVertexNormals(reader, verticesCount, ref ctm); + ReadUvMaps(reader, uvMapsCount, verticesCount, ref ctm); + ReadAttributeMaps(reader, attributeMapsCount, verticesCount, ref ctm); + } + + private static void ReadTriangles(BinaryReader reader, int trianglesCount, ref Ctm ctm) + { + if (trianglesCount > 0 && reader.ReadInt32() != Identifier.Indices) + { + throw new InvalidDataException("Invalid indices section"); + } + using var trianglesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, trianglesCount * 3 * sizeof(int))); + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < trianglesCount; j++) + { + var deltaIndex = trianglesReader.ReadInt32(); + if (i == 0) + { + ctm.Triangles.Add(new Triangle() + { + Vertex1 = j > 0 ? deltaIndex + ctm.Triangles[j - 1].Vertex1 : deltaIndex, + Vertex2 = 0, + Vertex3 = 0, + }); + } + else if (i == 1) + { + ctm.Triangles[j].Vertex2 = j > 0 && ctm.Triangles[j].Vertex1 == ctm.Triangles[j - 1].Vertex1 ? deltaIndex + ctm.Triangles[j - 1].Vertex2 : deltaIndex + ctm.Triangles[j].Vertex1; + } + else + { + ctm.Triangles[j].Vertex3 = deltaIndex + ctm.Triangles[j].Vertex1; + } + } + } + } + + private static void ReadVertices(BinaryReader reader, int verticesCount, ref Ctm ctm) + { + if (verticesCount > 0 && reader.ReadInt32() != Identifier.Vertices) + { + throw new InvalidDataException("Invalid vertices section"); + } + using var verticesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * 3 * sizeof(float))); + for (var i = 0; i < verticesCount; i++) + { + ctm.Vertices.Add(new Vertex() + { + X = verticesReader.ReadSingle(), + Y = verticesReader.ReadSingle(), + Z = verticesReader.ReadSingle(), + }); + } + } + + private static void ReadVertexNormals(BinaryReader reader, int verticesCount, ref Ctm ctm) + { + if ((ctm.Flags & Flag.HasNormals) == Flag.HasNormals) + { + if (verticesCount > 0 && reader.ReadInt32() != Identifier.Normals) + { + throw new InvalidDataException("Invalid normals section"); + } + using var normalsReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * 3 * sizeof(float))); + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < verticesCount; j++) + { + if (i == 0) + { + ctm.Vertices[j].Normal = new Normal() + { + X = normalsReader.ReadSingle(), + Y = 0, + Z = 0, + }; + } + else if (i == 1) + { + ctm.Vertices[j].Normal!.Y = normalsReader.ReadSingle(); + } + else + { + ctm.Vertices[j].Normal!.Z = normalsReader.ReadSingle(); + } + + } + } + } + } + + private static void ReadUvMaps(BinaryReader reader, int uvMapsCount, int verticesCount, ref Ctm ctm) + { + for (var i = 0; i < uvMapsCount; i++) + { + if (reader.ReadInt32() != Identifier.UVMaps) + { + throw new InvalidDataException("Invalid uv maps section"); + } + var uvMap = new UvMap() + { + Name = CtmReader.ReadString(reader), + Filename = CtmReader.ReadString(reader), + Coordinates = new List(verticesCount), + }; + using var uvCoordinatesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * 2 * sizeof(float))); + for (int j = 0; j < 2; j++) + { + for (int k = 0; k < verticesCount; k++) + { + if (j == 0) + { + uvMap.Coordinates.Add(new UvCoordinate() + { + U = uvCoordinatesReader.ReadSingle(), + V = 0, + }); + } + else + { + uvMap.Coordinates[k].V = uvCoordinatesReader.ReadSingle(); + } + } + } + ctm.UvMaps.Add(uvMap); + } + } + + private static void ReadAttributeMaps(BinaryReader reader, int attributeMapsCount, int verticesCount, ref Ctm ctm) + { + for (var i = 0; i < attributeMapsCount; i++) + { + if (reader.ReadInt32() != Identifier.AttributeMaps) + { + throw new InvalidDataException("Invalid attribute maps section"); + } + var attributeMap = new AttributeMap() + { + Name = CtmReader.ReadString(reader), + Values = new List(verticesCount), + }; + using var attributeValuesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * 4 * sizeof(float))); + for (int j = 0; j < 4; j++) + { + for (int k = 0; k < verticesCount; k++) + { + if (j == 0) + { + attributeMap.Values.Add(new AttributeValue() + { + A = attributeValuesReader.ReadSingle(), + B = 0, + C = 0, + D = 0, + }); + } + else if (j == 1) + { + attributeMap.Values[k].B = attributeValuesReader.ReadSingle(); + } + else if (j == 2) + { + attributeMap.Values[k].C = attributeValuesReader.ReadSingle(); + } + else + { + attributeMap.Values[k].D = attributeValuesReader.ReadSingle(); + } + } + } + ctm.AttributeMaps.Add(attributeMap); + } + } +} diff --git a/src/ThreeDModels/Format/Ctm/IO/Mg2Header.cs b/src/ThreeDModels/Format/Ctm/IO/Mg2Header.cs new file mode 100644 index 0000000..0a1e09d --- /dev/null +++ b/src/ThreeDModels/Format/Ctm/IO/Mg2Header.cs @@ -0,0 +1,3 @@ +namespace ThreeDModels.Format.Ctm.IO; + +internal record Mg2Header(float VertexPrecision, float NormalPrecision, Data3D LowerBoundCoordinates, Data3D UpperBoundCoordinates, Data3D GridDivisions); diff --git a/src/ThreeDModels/Format/Ctm/IO/Mg2Reader.cs b/src/ThreeDModels/Format/Ctm/IO/Mg2Reader.cs new file mode 100644 index 0000000..4f3756d --- /dev/null +++ b/src/ThreeDModels/Format/Ctm/IO/Mg2Reader.cs @@ -0,0 +1,218 @@ +using ThreeDModels.Format.Ctm.Elements; + +namespace ThreeDModels.Format.Ctm.IO; + +internal static class Mg2Reader +{ + internal static void Read(BinaryReader reader, int verticesCount, int trianglesCount, int uvMapsCount, int attributeMapsCount, ref Ctm ctm) + { + if (reader.ReadInt32() != Identifier.MG2H) + { + throw new InvalidDataException("Invalid MG2 header"); + } + var header = ReadHeader(reader); + ReadVertices(reader, header, verticesCount, ref ctm); + ReadTriangles(reader, trianglesCount, ref ctm); + ReadVertexNormals(reader, verticesCount, ref ctm); + ReadUvMaps(reader, uvMapsCount, verticesCount, ref ctm); + ReadAttributeMaps(reader, attributeMapsCount, verticesCount, ref ctm); + } + + private static void ReadTriangles(BinaryReader reader, int trianglesCount, ref Ctm ctm) + { + if (trianglesCount > 0 && reader.ReadInt32() != Identifier.Indices) + { + throw new InvalidDataException("Invalid indices section"); + } + using var trianglesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, trianglesCount * 3 * sizeof(int))); + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < trianglesCount; j++) + { + var deltaIndex = trianglesReader.ReadInt32(); + if (i == 0) + { + ctm.Triangles.Add(new Triangle() + { + Vertex1 = j > 0 ? deltaIndex + ctm.Triangles[j - 1].Vertex1 : deltaIndex, + Vertex2 = 0, + Vertex3 = 0, + }); + } + else if (i == 1) + { + ctm.Triangles[j].Vertex2 = j > 0 && ctm.Triangles[j].Vertex1 == ctm.Triangles[j - 1].Vertex1 ? deltaIndex + ctm.Triangles[j - 1].Vertex2 : deltaIndex + ctm.Triangles[j].Vertex1; + } + else + { + ctm.Triangles[j].Vertex3 = deltaIndex + ctm.Triangles[j].Vertex1; + } + } + } + } + + private static Mg2Header ReadHeader(BinaryReader reader) + { + var vertexPrecision = reader.ReadSingle(); + var normalPrecision = reader.ReadSingle(); + var lbX = reader.ReadSingle(); + var lbY = reader.ReadSingle(); + var lbZ = reader.ReadSingle(); + var ubX = reader.ReadSingle(); + var ubY = reader.ReadSingle(); + var ubZ = reader.ReadSingle(); + var divX = reader.ReadInt32(); + var divY = reader.ReadInt32(); + var divZ = reader.ReadInt32(); + return new Mg2Header(vertexPrecision, normalPrecision, new Data3D(X: lbX, Y: lbY, Z: lbZ), new Data3D(X: ubX, Y: ubY, Z: ubZ), new Data3D(X: divX, Y: divY, Z: divZ)); + } + + private static void ReadVertices(BinaryReader reader, Mg2Header header, int verticesCount, ref Ctm ctm) + { + if (verticesCount > 0 && reader.ReadInt32() != Identifier.Vertices) + { + throw new InvalidDataException("Invalid vertices section"); + } + using var verticesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * 3 * sizeof(float))); + var gridIndices = new List(verticesCount); + if (verticesCount > 0 && reader.ReadInt32() != Identifier.GridIndices) + { + throw new InvalidDataException("Invalid grid indices section"); + } + using var gridIndicesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * sizeof(int))); + for (var i = 0; i < verticesCount; i++) + { + var deltaGridIndex = gridIndicesReader.ReadInt32(); + gridIndices.Add(i == 0 ? deltaGridIndex : deltaGridIndex + gridIndices[i - 1]); + } + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < verticesCount; j++) + { + var deltaIndex = verticesReader.ReadInt32(); + if (i == 0) + { + var dx = j > 0 && gridIndices[j] == gridIndices[j - 1] + ? deltaIndex + ((ctm.Vertices[j - 1].X - GridOrigin(header.LowerBoundCoordinates.X, header.UpperBoundCoordinates.X, header.GridDivisions.X, gridIndices[j - 1])) / header.VertexPrecision) + : deltaIndex; + ctm.Vertices.Add(new Vertex() + { + X = header.VertexPrecision * dx + GridOrigin(header.LowerBoundCoordinates.X, header.UpperBoundCoordinates.X, header.GridDivisions.X, gridIndices[j]), + Y = 0, + Z = 0, + }); + } + else if (i == 1) + { + ctm.Vertices[j].Y = header.VertexPrecision * deltaIndex + GridOrigin(header.LowerBoundCoordinates.Y, header.UpperBoundCoordinates.Y, header.GridDivisions.Y, gridIndices[j]); + } + else + { + ctm.Vertices[j].Z = header.VertexPrecision * deltaIndex + GridOrigin(header.LowerBoundCoordinates.Z, header.UpperBoundCoordinates.Z, header.GridDivisions.Z, gridIndices[j]); + } + } + } + } + + private static void ReadVertexNormals(BinaryReader reader, int verticesCount, ref Ctm ctm) + { + if ((ctm.Flags & Flag.HasNormals) == Flag.HasNormals) + { + // TODO: Implement normal decoding + CtmReader.UnpackLzmaStream(reader, verticesCount * 2 * sizeof(float)); + throw new NotImplementedException("CTM MG2 Normal decoding is not implemented"); + } + } + + private static void ReadUvMaps(BinaryReader reader, int uvMapsCount, int verticesCount, ref Ctm ctm) + { + for (var i = 0; i < uvMapsCount; i++) + { + if (reader.ReadInt32() != Identifier.UVMaps) + { + throw new InvalidDataException("Invalid uv maps section"); + } + var uvMap = new UvMap() + { + Name = CtmReader.ReadString(reader), + Filename = CtmReader.ReadString(reader), + Coordinates = new List(verticesCount), + }; + var uvCoordinatePrecision = reader.ReadSingle(); + using var uvCoordinatesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * 2 * sizeof(float))); + for (int j = 0; j < 2; j++) + { + for (int k = 0; k < verticesCount; k++) + { + var deltaCoordinate = uvCoordinatesReader.ReadInt32(); + if (j == 0) + { + uvMap.Coordinates.Add(new UvCoordinate() + { + U = k == 0 ? uvCoordinatePrecision * deltaCoordinate : uvCoordinatePrecision * (deltaCoordinate + uvMap.Coordinates[k - 1].U), + V = 0, + }); + } + else + { + uvMap.Coordinates[k].V = k == 0 ? uvCoordinatePrecision * deltaCoordinate : uvCoordinatePrecision * (deltaCoordinate + uvMap.Coordinates[k - 1].V); + } + } + } + ctm.UvMaps.Add(uvMap); + } + } + + private static void ReadAttributeMaps(BinaryReader reader, int attributeMapsCount, int verticesCount, ref Ctm ctm) + { + for (var i = 0; i < attributeMapsCount; i++) + { + if (reader.ReadInt32() != Identifier.AttributeMaps) + { + throw new InvalidDataException("Invalid attribute maps section"); + } + var attributeMap = new AttributeMap() + { + Name = CtmReader.ReadString(reader), + Values = new List(verticesCount), + }; + var attributeValuePrecision = reader.ReadSingle(); + using var attributeValuesReader = new BinaryReader(CtmReader.UnpackLzmaStream(reader, verticesCount * 4 * sizeof(float))); + for (int j = 0; j < 4; j++) + { + for (int k = 0; k < verticesCount; k++) + { + var deltaValue = attributeValuesReader.ReadInt32(); + if (j == 0) + { + attributeMap.Values.Add(new AttributeValue() + { + A = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].A), + B = 0, + C = 0, + D = 0, + }); + } + else if (j == 1) + { + attributeMap.Values[k].B = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].B); + } + else if (j == 2) + { + attributeMap.Values[k].C = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].C); + } + else + { + attributeMap.Values[k].D = k == 0 ? attributeValuePrecision * deltaValue : attributeValuePrecision * (deltaValue + attributeMap.Values[k - 1].D); + } + } + } + ctm.AttributeMaps.Add(attributeMap); + } + } + + internal static float GridOrigin(float lb, float ub, int div, int g) + { + return lb + ((ub - lb) / div * g); + } +} diff --git a/src/ThreeDModels/Format/Ctm/IO/RawReader.cs b/src/ThreeDModels/Format/Ctm/IO/RawReader.cs new file mode 100644 index 0000000..1637ad7 --- /dev/null +++ b/src/ThreeDModels/Format/Ctm/IO/RawReader.cs @@ -0,0 +1,122 @@ +using ThreeDModels.Format.Ctm.Elements; + +namespace ThreeDModels.Format.Ctm.IO; + +internal static class RawReader +{ + internal static void Read(BinaryReader reader, int verticesCount, int trianglesCount, int uvMapsCount, int attributeMapsCount, ref Ctm ctm) + { + ReadTriangles(reader, trianglesCount, ref ctm); + ReadVertices(reader, verticesCount, ref ctm); + ReadVertexNormals(reader, verticesCount, ref ctm); + ReadUvMaps(reader, uvMapsCount, verticesCount, ref ctm); + ReadAttributeMaps(reader, attributeMapsCount, verticesCount, ref ctm); + } + + private static void ReadTriangles(BinaryReader reader, int trianglesCount, ref Ctm ctm) + { + for (var i = 0; i < trianglesCount; i++) + { + if (reader.ReadInt32() != Identifier.Indices) + { + throw new InvalidDataException("Invalid indices section"); + } + ctm.Triangles.Add(new Triangle() + { + Vertex1 = reader.ReadInt32(), + Vertex2 = reader.ReadInt32(), + Vertex3 = reader.ReadInt32(), + }); + } + } + + private static void ReadVertices(BinaryReader reader, int verticesCount, ref Ctm ctm) + { + for (var i = 0; i < verticesCount; i++) + { + if (reader.ReadInt32() != Identifier.Vertices) + { + throw new InvalidDataException("Invalid vertices section"); + } + ctm.Vertices.Add(new Vertex() + { + X = reader.ReadSingle(), + Y = reader.ReadSingle(), + Z = reader.ReadSingle(), + }); + } + } + + private static void ReadVertexNormals(BinaryReader reader, int verticesCount, ref Ctm ctm) + { + if ((ctm.Flags & Flag.HasNormals) == Flag.HasNormals) + { + for (var i = 0; i < verticesCount; i++) + { + if (reader.ReadInt32() != Identifier.Normals) + { + throw new InvalidDataException("Invalid normals section"); + } + ctm.Vertices[i].Normal = new Normal() + { + X = reader.ReadSingle(), + Y = reader.ReadSingle(), + Z = reader.ReadSingle(), + }; + } + } + } + + private static void ReadUvMaps(BinaryReader reader, int uvMapsCount, int verticesCount, ref Ctm ctm) + { + for (var i = 0; i < uvMapsCount; i++) + { + if (reader.ReadInt32() != Identifier.UVMaps) + { + throw new InvalidDataException("Invalid uv maps section"); + } + var uvMap = new UvMap() + { + Name = CtmReader.ReadString(reader), + Filename = CtmReader.ReadString(reader), + Coordinates = new List(verticesCount), + }; + for (var j = 0; j < verticesCount; j++) + { + uvMap.Coordinates.Add(new UvCoordinate() + { + U = reader.ReadSingle(), + V = reader.ReadSingle(), + }); + } + ctm.UvMaps.Add(uvMap); + } + } + + private static void ReadAttributeMaps(BinaryReader reader, int attributeMapsCount, int verticesCount, ref Ctm ctm) + { + for (var i = 0; i < attributeMapsCount; i++) + { + if (reader.ReadInt32() != Identifier.AttributeMaps) + { + throw new InvalidDataException("Invalid attribute maps section"); + } + var attributeMap = new AttributeMap() + { + Name = CtmReader.ReadString(reader), + Values = new List(verticesCount), + }; + for (var j = 0; j < verticesCount; j++) + { + attributeMap.Values.Add(new AttributeValue() + { + A = reader.ReadSingle(), + B = reader.ReadSingle(), + C = reader.ReadSingle(), + D = reader.ReadSingle(), + }); + } + ctm.AttributeMaps.Add(attributeMap); + } + } +} From b757181d6450df2909a2e54ef4dbf8fe0c21dbbd Mon Sep 17 00:00:00 2001 From: Bezaleel Olakunori Date: Sat, 14 Sep 2024 20:23:27 +0200 Subject: [PATCH 4/7] Move Obj reading files to the IO subfolder and namespace --- src/ThreeDModels/Format/Obj/{ => IO}/Constants.cs | 2 +- src/ThreeDModels/Format/Obj/{ => IO}/ObjReader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/ThreeDModels/Format/Obj/{ => IO}/Constants.cs (83%) rename src/ThreeDModels/Format/Obj/{ => IO}/ObjReader.cs (99%) diff --git a/src/ThreeDModels/Format/Obj/Constants.cs b/src/ThreeDModels/Format/Obj/IO/Constants.cs similarity index 83% rename from src/ThreeDModels/Format/Obj/Constants.cs rename to src/ThreeDModels/Format/Obj/IO/Constants.cs index 8ac1a85..59b53ee 100644 --- a/src/ThreeDModels/Format/Obj/Constants.cs +++ b/src/ThreeDModels/Format/Obj/IO/Constants.cs @@ -1,4 +1,4 @@ -namespace ThreeDModels.Format.Obj; +namespace ThreeDModels.Format.Obj.IO; public class Constants { diff --git a/src/ThreeDModels/Format/Obj/ObjReader.cs b/src/ThreeDModels/Format/Obj/IO/ObjReader.cs similarity index 99% rename from src/ThreeDModels/Format/Obj/ObjReader.cs rename to src/ThreeDModels/Format/Obj/IO/ObjReader.cs index 2058256..bfae3be 100644 --- a/src/ThreeDModels/Format/Obj/ObjReader.cs +++ b/src/ThreeDModels/Format/Obj/IO/ObjReader.cs @@ -3,7 +3,7 @@ using ThreeDModels.Format.Obj.Grouping; using ThreeDModels.Format.Obj.Vertex; -namespace ThreeDModels.Format.Obj; +namespace ThreeDModels.Format.Obj.IO; public class ObjReader { From 089b88d30c1587767f81c7ad9fd443c974b0f632 Mon Sep 17 00:00:00 2001 From: Bezaleel Olakunori Date: Sun, 15 Sep 2024 09:16:55 +0200 Subject: [PATCH 5/7] Create a context for the obj reader commands --- .../Format/Obj/IO/ObjReaderContext.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/ThreeDModels/Format/Obj/IO/ObjReaderContext.cs diff --git a/src/ThreeDModels/Format/Obj/IO/ObjReaderContext.cs b/src/ThreeDModels/Format/Obj/IO/ObjReaderContext.cs new file mode 100644 index 0000000..b0c8d90 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/ObjReaderContext.cs @@ -0,0 +1,21 @@ +using ThreeDModels.Format.Obj.Display; +using ThreeDModels.Format.Obj.Elements; +using ThreeDModels.Format.Obj.Grouping; + +namespace ThreeDModels.Format.Obj.IO; + +internal class ObjReaderContext +{ + public Group Group { get; set; } + public string ElementName { get; set; } + public DisplayAttributes? Display { get; set; } + public FreeFormGeometry? FreeFormGeometry { get; set; } + + public ObjReaderContext(Group group, string elementName, DisplayAttributes? display, FreeFormGeometry? freeFormGeometry) + { + Group = group; + ElementName = elementName; + Display = display; + FreeFormGeometry = freeFormGeometry; + } +} From 384c046d9167d973c5c8c23af1f07f850f3a77ea Mon Sep 17 00:00:00 2001 From: Bezaleel Olakunori Date: Sun, 15 Sep 2024 09:17:24 +0200 Subject: [PATCH 6/7] Create a context for the material reader commands --- .../Format/Obj/IO/MaterialReaderContext.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/ThreeDModels/Format/Obj/IO/MaterialReaderContext.cs diff --git a/src/ThreeDModels/Format/Obj/IO/MaterialReaderContext.cs b/src/ThreeDModels/Format/Obj/IO/MaterialReaderContext.cs new file mode 100644 index 0000000..24a44d9 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/MaterialReaderContext.cs @@ -0,0 +1,15 @@ +using ThreeDModels.Format.Obj.Display; + +namespace ThreeDModels.Format.Obj.IO; + +internal class MaterialReaderContext +{ + public Material? Material { get; set; } + public List Materials { get; } + + public MaterialReaderContext(Material? material, List materials) + { + Material = material; + Materials = materials; + } +} From 2d714babd297b427947bd2bca19c10f7bfa61a69 Mon Sep 17 00:00:00 2001 From: Bezaleel Olakunori Date: Sun, 15 Sep 2024 09:18:40 +0200 Subject: [PATCH 7/7] Move obj reader and obj material reader commands to separate classes and functions --- .../DisplayAttributes/BevelCommand.cs | 19 + .../ColorInterpolationCommand.cs | 17 + .../DissolveInterpolationCommand.cs | 17 + .../DisplayAttributes/LevelOfDetailCommand.cs | 17 + .../MaterialLibraryCommand.cs | 66 ++ .../DisplayAttributes/UseMapCommand.cs | 17 + .../DisplayAttributes/UseMaterialCommand.cs | 17 + .../BasisMatrixCommand.cs | 34 + .../CurveSurfaceTypeCommand.cs | 30 + .../DegreeCommand.cs | 21 + .../StepCommand.cs | 21 + .../EndFreeFormGeometryCommand.cs | 38 + .../HoleCommand.cs | 33 + .../ParameterCommand.cs | 34 + .../SpecialCurveCommand.cs | 33 + .../SpecialPointCommand.cs | 26 + .../TrimCommand.cs | 33 + .../Curve2Command.cs | 32 + .../CurveCommand.cs | 41 + .../FaceCommand.cs | 50 ++ .../LineCommand.cs | 33 + .../PointCommand.cs | 28 + .../SurfaceCommand.cs | 60 ++ .../Obj/IO/Commands/Grouping/GroupCommand.cs | 26 + .../Commands/Grouping/MergingGroupCommand.cs | 31 + .../Obj/IO/Commands/Grouping/ObjectCommand.cs | 16 + .../Grouping/SmoothingGroupCommand.cs | 28 + .../Format/Obj/IO/Commands/ICommand.cs | 10 + .../Obj/IO/Commands/IMaterialCommand.cs | 10 + .../Commands/Materials/AmbientColorCommand.cs | 27 + .../Commands/Materials/DiffuseColorCommand.cs | 27 + .../IO/Commands/Materials/DissolveCommand.cs | 20 + .../Materials/IlluminationModelCommand.cs | 20 + .../Commands/Materials/NewMaterialCommand.cs | 22 + .../Materials/SpecularColorCommand.cs | 27 + .../Materials/SpecularExponentCommand.cs | 20 + .../Commands/Materials/TransparencyCommand.cs | 20 + .../VertexData/GeometricVertexCommand.cs | 24 + .../VertexData/ParameterSpaceVertexCommand.cs | 23 + .../VertexData/TextureVertexCommand.cs | 23 + .../VertexData/VertexNormalCommand.cs | 23 + src/ThreeDModels/Format/Obj/IO/ObjReader.cs | 716 ++---------------- .../Format/Obj.UnitTests/ObjReaderTests.cs | 1 + 43 files changed, 1170 insertions(+), 661 deletions(-) create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/BevelCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/ColorInterpolationCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/DissolveInterpolationCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/LevelOfDetailCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/MaterialLibraryCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMapCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/DisplayAttributes/UseMaterialCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/BasisMatrixCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/CurveSurfaceTypeCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/DegreeCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceAttributes/StepCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/EndFreeFormGeometryCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/HoleCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/ParameterCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialCurveCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/SpecialPointCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceBodyStatements/TrimCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/Curve2Command.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/CurveCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/FaceCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/LineCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/PointCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/FreeFormCurveSurfaceElements/SurfaceCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Grouping/GroupCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Grouping/MergingGroupCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Grouping/ObjectCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Grouping/SmoothingGroupCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/ICommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/IMaterialCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/AmbientColorCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/DiffuseColorCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/DissolveCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/IlluminationModelCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/NewMaterialCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularColorCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/SpecularExponentCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/Materials/TransparencyCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/VertexData/GeometricVertexCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/VertexData/ParameterSpaceVertexCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/VertexData/TextureVertexCommand.cs create mode 100644 src/ThreeDModels/Format/Obj/IO/Commands/VertexData/VertexNormalCommand.cs 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..9470372 --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/MergingGroupCommand.cs @@ -0,0 +1,31 @@ +using ThreeDModels.Format.Obj.Grouping; + +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 > context.Group.Names!.Count) + { + throw new InvalidDataException("Invalid merging group number"); + } + if (commandLine[1] == Constants.Off || groupNumber == 0) + { + context.Group.MergingGroups = []; + } + else if (!context.Group.MergingGroups.Any(mergingGroup => mergingGroup.GroupIndex == groupNumber - 1)) + { + var resolution = float.Parse(commandLine[2]); + context.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..1d13663 --- /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) + { + context.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..5eb9bce --- /dev/null +++ b/src/ThreeDModels/Format/Obj/IO/Commands/Grouping/SmoothingGroupCommand.cs @@ -0,0 +1,28 @@ +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 > context.Group.Names!.Count) + { + throw new InvalidDataException("Invalid smoothing group number"); + } + if (commandLine[1] == Constants.Off || groupNumber == 0) + { + context.Group.SmoothingGroupIndices = []; + } + else if (!context.Group.SmoothingGroupIndices.Contains(groupNumber - 1)) + { + context.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) diff --git a/tests/ThreeDModels/Format/Obj.UnitTests/ObjReaderTests.cs b/tests/ThreeDModels/Format/Obj.UnitTests/ObjReaderTests.cs index 6217536..bca4218 100644 --- a/tests/ThreeDModels/Format/Obj.UnitTests/ObjReaderTests.cs +++ b/tests/ThreeDModels/Format/Obj.UnitTests/ObjReaderTests.cs @@ -1,5 +1,6 @@ using System.IO; using FluentAssertions; +using ThreeDModels.Format.Obj.IO; using Xunit; namespace ThreeDModels.Format.Obj.UnitTests;