Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 66 additions & 10 deletions src/MeshIO.Tests/Formats/Gltf/GltfReaderLocalTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,62 @@ namespace MeshIO.Tests.Formats.Gltf;

public class GltfReaderLocalTest : IOTestsBase
{
public static readonly TheoryData<FileModel> V1Files = new();
public static readonly TheoryData<FileModel> GlbV1Files = new();

public static readonly TheoryData<FileModel> V2Files = new();
public static readonly TheoryData<FileModel> GlbV2Files = new();

public static readonly TheoryData<FileModel> GltfV1Files = new();

public static readonly TheoryData<FileModel> GltfV2Files = new();

private const string _samplesFolder = "..\\..\\..\\..\\..\\..\\glTF-Sample-Models";

static GltfReaderLocalTest()
{
if (!Directory.Exists(_samplesFolder))
{
V1Files.Add(new FileModel());
V2Files.Add(new FileModel());
GlbV1Files.Add(new FileModel());
GlbV2Files.Add(new FileModel());
GltfV1Files.Add(new FileModel());
GltfV2Files.Add(new FileModel());
return;
}

foreach (string file in Directory.GetFiles(Path.Combine(_samplesFolder, "1.0"), "*.glb", SearchOption.AllDirectories))
{
FileModel model = new FileModel(file);
V1Files.Add(model);
GlbV1Files.Add(model);
}

foreach (string file in Directory.GetFiles(Path.Combine(_samplesFolder, "1.0"), "*.gltf", SearchOption.AllDirectories))
{
if (file.Contains("glTF-Embedded"))
continue;

FileModel model = new FileModel(file);
GltfV1Files.Add(model);
}

foreach (string file in Directory.GetFiles(Path.Combine(_samplesFolder, "2.0"), "*.glb", SearchOption.AllDirectories))
{
V2Files.Add(new FileModel(file));
GlbV2Files.Add(new FileModel(file));
}

foreach (string file in Directory.GetFiles(Path.Combine(_samplesFolder, "2.0"), "*.gltf", SearchOption.AllDirectories))
{
if (file.Contains("Draco") || file.Contains("glTF-Embedded"))
continue;

GltfV2Files.Add(new FileModel(file));
}
}

public GltfReaderLocalTest(ITestOutputHelper output) : base(output) { }
public GltfReaderLocalTest(ITestOutputHelper output) : base(output)
{
}

[Theory]
[MemberData(nameof(V1Files))]
[MemberData(nameof(GlbV1Files))]
public void ReadGlbV1(FileModel test)
{
if (string.IsNullOrEmpty(test.Path))
Expand All @@ -57,7 +82,7 @@ public void ReadGlbV1(FileModel test)
}

[Theory]
[MemberData(nameof(V2Files))]
[MemberData(nameof(GlbV2Files))]
public void ReadGlbV2(FileModel test)
{
if (string.IsNullOrEmpty(test.Path))
Expand All @@ -69,4 +94,35 @@ public void ReadGlbV2(FileModel test)
reader.Read();
}
}
}

[Theory]
[MemberData(nameof(GltfV1Files))]
public void ReadGltfV1(FileModel test)
{
if (string.IsNullOrEmpty(test.Path))
return;

Scene scene = null;
using (GltfReader reader = new GltfReader(test.Path))
{
reader.OnNotification += this.onNotification;
scene = reader.Read();
}

Assert.NotNull(scene);
}

[Theory]
[MemberData(nameof(GltfV2Files))]
public void ReadGltfV2(FileModel test)
{
if (string.IsNullOrEmpty(test.Path))
return;

using (GltfReader reader = new GltfReader(test.Path))
{
reader.OnNotification += this.onNotification;
reader.Read();
}
}
}
4 changes: 2 additions & 2 deletions src/MeshIO.Tests/Formats/Gltf/GltfReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ public void ReadGlb(FileModel test)
}
}

[Theory(Skip = "Gltf not implemented")]
[Theory]
[MemberData(nameof(GltfFiles))]
public void ReadGltf(FileModel test)
{
using (GlbReader reader = new GlbReader(test.Path))
using (GltfReader reader = new GltfReader(test.Path))
{
reader.OnNotification += this.onNotification;
reader.Read();
Expand Down
12 changes: 2 additions & 10 deletions src/MeshIO/Formats/Gltf/GlbReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ namespace MeshIO.Formats.Gltf;
/// the reading process.</remarks>
public class GlbReader : SceneReader<GltfReaderOptions>
{
private GlbHeader _header;

/// <inheritdoc/>
public GlbReader(string path, GltfReaderOptions options = null, NotificationEventHandler notification = null)
: base(path, options, notification) { }
Expand Down Expand Up @@ -59,16 +57,10 @@ public static Scene Read(Stream stream, GltfReaderOptions options = null, Notifi
}
}

/// <inheritdoc/>
public override void Dispose()
{
base.Dispose();
}

/// <inheritdoc/>
public override Scene Read()
{
GlbHeader header = GlbHeader.Read(_stream.Stream);
GltfHeader header = GltfHeader.Read(_stream);

IGlbFileBuilder reader;
switch (header.Version)
Expand All @@ -78,7 +70,7 @@ public override Scene Read()
reader = new GlbFileBuilder(header);
break;
default:
throw new NotSupportedException($"Version {this._header.Version} not supported.");
throw new NotSupportedException($"Version {header.Version} not supported.");
}

reader.OnNotification += this.onNotificationEvent;
Expand Down
112 changes: 112 additions & 0 deletions src/MeshIO/Formats/Gltf/GltfReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using CSUtilities.IO;
using MeshIO.Formats.Gltf.Readers;
using MeshIO.Formats.Gltf.Schema;
using MeshIO.Formats.Gltf.Schema.V2;
using System.IO;

namespace MeshIO.Formats.Gltf;

/// <summary>
/// Provides functionality to read and parse glTF (GL Transmission Format) files and streams into scene objects.
/// </summary>
/// <remarks>
/// The GltfReader supports reading standard glTF files. It can be
/// constructed from a file path or from streams, and allows for custom reader options and notification handling.
/// </remarks>
public class GltfReader : SceneReader<GltfReaderOptions>
{
private readonly StreamIO _bin;

/// <summary>
/// Initializes a new instance of the GltfReader class to read a GLTF file and its associated binary data.
/// </summary>
/// <remarks>The constructor automatically locates and opens the binary file associated with the specified GLTF
/// file. The reader supports concurrent access to the binary file if it is being used by other processes.</remarks>
/// <param name="path">The path to the GLTF file to be read. Must not be null or empty.</param>
/// <param name="options">Optional settings that control the behavior of the reader. If null, default options are used.</param>
/// <param name="notification">An optional event handler for receiving notifications during the reading process. If null, no notifications are
/// sent.</param>
public GltfReader(string path, GltfReaderOptions options = null, NotificationEventHandler notification = null)
: base(path, options, notification)
{
var binFile = Path.ChangeExtension(path, ".bin");
this._bin = new StreamIO(File.Open(binFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
}

/// <summary>
/// Initializes a new instance of the GltfReader class for reading glTF assets from the specified JSON and binary
/// streams.
/// </summary>
/// <remarks>The caller is responsible for managing the lifetime of the provided streams. Both streams must
/// remain open for the duration of the GltfReader instance.</remarks>
/// <param name="gltf">A stream containing the glTF JSON content. The stream must be readable and positioned at the start of the glTF
/// data.</param>
/// <param name="gltfBinary">A stream containing the binary buffer data associated with the glTF asset. The stream must be readable and
/// positioned at the start of the binary data.</param>
/// <param name="options">Optional settings that control how the glTF asset is read and processed. If null, default options are used.</param>
/// <param name="notification">An optional event handler for receiving notifications or warnings during the reading process. If null, no
/// notifications are raised.</param>
public GltfReader(Stream gltf, Stream gltfBinary, GltfReaderOptions options = null, NotificationEventHandler notification = null)
: base(gltf, options, notification)
{
this._bin = new StreamIO(gltfBinary);
}

/// <summary>
/// Reads a glTF file from the specified path and returns the parsed scene.
/// </summary>
/// <remarks>This method provides a convenient way to load a glTF file from disk in a single call. The caller is
/// responsible for ensuring that the file exists and is accessible. If the file is invalid or cannot be parsed, an
/// exception may be thrown.</remarks>
/// <param name="path">The file system path to the glTF file to read. Must not be null or empty.</param>
/// <param name="options">Optional settings that control how the glTF file is read and parsed. If null, default options are used.</param>
/// <param name="notification">An optional event handler for receiving notifications or warnings during the reading process. If null,
/// notifications are ignored.</param>
/// <returns>A Scene object representing the contents of the glTF file.</returns>
public static Scene Read(string path, GltfReaderOptions options = null, NotificationEventHandler notification = null)
{
using (GltfReader reader = new GltfReader(path, options, notification))
{
return reader.Read();
}
}

/// <summary>
/// Reads a glTF scene from the specified stream and optional binary buffer, using the provided options and
/// notification handler.
/// </summary>
/// <remarks>The caller is responsible for managing the lifetime of the input streams. This method does not
/// close or dispose the provided streams.</remarks>
/// <param name="stream">The stream containing the glTF JSON content to read. The stream must be readable and positioned at the start of the
/// glTF data.</param>
/// <param name="gltfBinary">An optional stream containing the binary buffer referenced by the glTF file, or null if the glTF does not use an
/// external binary buffer. The stream must be readable and positioned at the start of the binary data.</param>
/// <param name="options">Optional settings that control how the glTF is read and processed. If null, default options are used.</param>
/// <param name="notification">An optional event handler for receiving notifications or warnings encountered during reading. If null,
/// notifications are ignored.</param>
/// <returns>A Scene object representing the parsed glTF scene. Returns null if the scene could not be read or is invalid.</returns>
public static Scene Read(Stream stream, Stream gltfBinary, GltfReaderOptions options = null, NotificationEventHandler notification = null)
{
using (GltfReader reader = new GltfReader(stream, gltfBinary, options, notification))
{
return reader.Read();
}
}

/// <inheritdoc/>
public override void Dispose()
{
base.Dispose();
this._bin.Dispose();
}

/// <inheritdoc/>
public override Scene Read()
{
GltfHeader header = GltfHeader.Read(this._stream, this._bin);
GltfRoot root = header.GetRoot();
var reader = new GlbFileBuilder(header);
reader.OnNotification += this.onNotificationEvent;
return reader.Build();
}
}
9 changes: 9 additions & 0 deletions src/MeshIO/Formats/Gltf/JsonUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace MeshIO.Formats.Gltf;

internal static class JsonUtils
{
public static T Deserialize<T>(string json)
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json);
}
}
6 changes: 3 additions & 3 deletions src/MeshIO/Formats/Gltf/Readers/GlbFileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal class GlbFileBuilder : IGlbFileBuilder

private readonly Dictionary<string, GltfCameraBuilder> _cameras = new();

private readonly GlbHeader _header;
private readonly GltfHeader _header;

private readonly Dictionary<string, GltfMaterialBuilder> _materials = new();

Expand All @@ -33,7 +33,7 @@ internal class GlbFileBuilder : IGlbFileBuilder

private GltfRoot _root;

public GlbFileBuilder(GlbHeader header)
public GlbFileBuilder(GltfHeader header)
{
this._header = header;
}
Expand Down Expand Up @@ -181,7 +181,7 @@ private void createBuilders<Builder, Gltf>(Dictionary<string, Builder> collectio

for (int i = 0; i < gltfArray.Length; i++)
{
var gltf = gltfArray[i];
Gltf gltf = gltfArray[i];
Builder builder = new();
builder.GltfObject = gltf;

Expand Down
Loading