From 129341ff6a9f06322c95d5af9b29afd4a6284c91 Mon Sep 17 00:00:00 2001 From: JoeTwizzle Date: Fri, 23 Oct 2026 19:09:04 +0200 Subject: [PATCH 1/6] Update to dotnet 10 --- ReFuel.Gltf/ReFuel.Gltf.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReFuel.Gltf/ReFuel.Gltf.csproj b/ReFuel.Gltf/ReFuel.Gltf.csproj index ad9ab37..ad5109b 100644 --- a/ReFuel.Gltf/ReFuel.Gltf.csproj +++ b/ReFuel.Gltf/ReFuel.Gltf.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net10.0 enable enable 0.0.1 -- 2.52.0 From b93df6e769483f536719e440f11735a324c5d13e Mon Sep 17 00:00:00 2001 From: JoeTwizzle Date: Fri, 23 Oct 2026 19:15:36 +0200 Subject: [PATCH 2/6] Rewrite streamwrapper to fix bugs (I forgot which) --- ReFuel.Gltf/GltfDocument.cs | 15 +- ReFuel.Gltf/IO/StreamWrapper.cs | 431 ++++++++++++++++++++------------ 2 files changed, 284 insertions(+), 162 deletions(-) diff --git a/ReFuel.Gltf/GltfDocument.cs b/ReFuel.Gltf/GltfDocument.cs index b8ae939..16b39b4 100755 --- a/ReFuel.Gltf/GltfDocument.cs +++ b/ReFuel.Gltf/GltfDocument.cs @@ -133,7 +133,7 @@ namespace ReFuel.Gltf throw new Exception("File does not have a valid GLB header."); } - private static GltfDocument OpenBinary(StreamWrapper str, in GlbHeader header) + private static GltfDocument OpenBinary(StreamWrapper fileStream, in GlbHeader header) { if (header.Version < 2) { @@ -142,24 +142,23 @@ namespace ReFuel.Gltf JsonDocument? document = null; List binaryStreams = new List(); - - while (GlbChunk.Read(str, out GlbChunk chunk)) + while (GlbChunk.Read(fileStream, out GlbChunk chunk)) { - using StreamWrapper substream = str.ForkAbs(str.Position, str.Position + chunk.Size, true); - + using StreamWrapper chunkStream = fileStream.ForkAbs(fileStream.Position, fileStream.Position + chunk.Size, true); if (chunk.Type == GlbChunkType.Json) { - document = JsonDocument.Parse(substream.Fork(keepOpen:true)); + document = JsonDocument.Parse(chunkStream); } else { Stream copy = new MemoryStream(); - - substream.CopyTo(copy); + //TODO: is this reasonable? + chunkStream.CopyTo(copy); copy.Position = 0; binaryStreams.Add(new StreamWrapper(copy)); } + fileStream.Seek(chunk.Size, SeekOrigin.Current); } if (document == null) diff --git a/ReFuel.Gltf/IO/StreamWrapper.cs b/ReFuel.Gltf/IO/StreamWrapper.cs index 9434cf0..0c6b467 100644 --- a/ReFuel.Gltf/IO/StreamWrapper.cs +++ b/ReFuel.Gltf/IO/StreamWrapper.cs @@ -4,213 +4,327 @@ namespace ReFuel.Gltf.IO { public class StreamWrapper : Stream { - /// - /// The private synchronized proxy for the stream. - /// - private readonly Stream _proxy; + private readonly Stream _baseStream; - /// - /// Head pointer for stream r/w operations. - /// - public long Head { get; private init; } = 0; + // Shared across forks so that concurrent access to the same base stream is serialized. + private readonly object _sync; - /// - /// Tail pointer for stream r/w operations. -1 signifies unbound. - /// - public long Tail { get; private init; } = -1; - - /// - /// The number of bytes in the stream if there is a limit set. - /// - public long Limit - { - get - { - if (Tail <= 0) - { - return -1; - } - - if (CanWrite) - { - return Tail - Head; - } - - // Return the limit size or however many bytes are available. - return Math.Min(Tail - Head, _proxy.Length - Head); - } - } + // Logical position relative to Head. This is the fix for the "Position lies" problem. + private long _position; + public long Head { get; private init; } + public long Tail { get; private set; } = -1; // -1 = unbound /// /// The base stream for this wrapper. /// - public Stream BaseStream { get; } - + public Stream BaseStream => _baseStream; /// /// True if the stream has been set to be read only. /// - public bool IsReadOnly { get; private init; } = false; - + public bool IsReadOnly { get; private init; } /// /// True if the base stream should be kept open. /// public bool KeepOpen { get; } - public override bool CanRead => _proxy.CanRead; - public override bool CanSeek => _proxy.CanSeek; - public override bool CanWrite => !IsReadOnly || _proxy.CanWrite; - public override long Length => Tail < 0 ? _proxy.Length - Head : Math.Min(Tail - Head, _proxy.Length - Head); + public long Limit + { + get + { + if (Tail < 0) + { + return -1; + } + + long end = Math.Min(Tail, _baseStream.Length); + return Math.Max(0, end - Head); + } + } + + public override bool CanRead => _baseStream.CanRead; + public override bool CanSeek => _baseStream.CanSeek; + public override bool CanWrite => !IsReadOnly && _baseStream.CanWrite; + + public override long Length + { + get + { + long end = Tail >= 0 ? Math.Min(Tail, _baseStream.Length) : _baseStream.Length; + return Math.Max(0, end - Head); + } + } public override long Position { - get => ToRelOffset(_proxy.Position); - set => _proxy.Position = ToAbsOffset(value, true); + get { lock (_sync) + { + return _position; + } + } + set { lock (_sync) + { + Seek(value, SeekOrigin.Begin); + } + } } public StreamWrapper(Stream baseStream, bool keepOpen = false, bool isReadOnly = false) { - BaseStream = baseStream; + _baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); KeepOpen = keepOpen; IsReadOnly = isReadOnly; - - _proxy = Synchronized(baseStream); + _sync = new object(); + Head = 0; + Tail = -1; + _position = 0; } - public override void Flush() => _proxy.Flush(); + private StreamWrapper(Stream baseStream, object sync, + long head, long tail, + bool keepOpen, bool isReadOnly) + { + _baseStream = baseStream; + _sync = sync; + Head = head; + Tail = tail; + KeepOpen = keepOpen; + IsReadOnly = isReadOnly; + _position = 0; + } + + public override void Flush() + { + lock (_sync) + { + _baseStream.Flush(); + } + } public override int Read(byte[] buffer, int offset, int count) { - int clamped = ClampReadLength(); - int rc =_proxy.Read(buffer, offset, clamped); - Debug.Assert(Tail < 0 || _proxy.Position <= Tail); - return rc; + ValidateBufferArgs(buffer, offset, count); - int ClampReadLength() + lock (_sync) { - if (Tail > 0) + int clamped = ClampReadLength(count); + if (clamped == 0) { - return Math.Min(count, (int)Math.Min(int.MaxValue, Math.Max(0, Tail - _proxy.Position))); - } - else - { - return count; + return 0; } + + _baseStream.Seek(Head + _position, SeekOrigin.Begin); + int rc = _baseStream.Read(buffer, offset, clamped); + _position += rc; + return rc; } } + public override int Read(Span buffer) + { + lock (_sync) + { + int clamped = ClampReadLength(buffer.Length); + if (clamped == 0) + { + return 0; + } + + _baseStream.Seek(Head + _position, SeekOrigin.Begin); + int rc = _baseStream.Read(buffer.Slice(0, clamped)); + _position += rc; + return rc; + } + } + + public override int ReadByte() + { + lock (_sync) + { + if (ClampReadLength(1) == 0) + { + return -1; + } + + _baseStream.Seek(Head + _position, SeekOrigin.Begin); + int b = _baseStream.ReadByte(); + if (b >= 0) + { + _position++; + } + + return b; + } + } + + private int ClampReadLength(int count) + { + if (Tail < 0) + { + return count; + } + + long remaining = Tail - (Head + _position); + if (remaining <= 0) + { + return 0; + } + + return (int)Math.Min(count, remaining); + } public override long Seek(long offset, SeekOrigin origin) { - long absOffset = 0; - switch (origin) + lock (_sync) { - case SeekOrigin.Begin: - absOffset = ToAbsOffset(offset, true); - break; - case SeekOrigin.End: - if (Tail < 0) - { - try - { - return ToRelOffset(_proxy.Seek(offset, origin), true); - } - catch (ArgumentOutOfRangeException) - { - // Set the stream back to a known state. - _proxy.Seek(Tail, SeekOrigin.Begin); - throw; - } - } + long newRel; + switch (origin) + { + case SeekOrigin.Begin: + newRel = offset; + break; - absOffset = Math.Max(Head, Math.Min(Tail, Tail + offset)); - break; - case SeekOrigin.Current: - absOffset = Math.Max(Head, Math.Min(_proxy.Position + offset, Tail)); - break; + case SeekOrigin.Current: + newRel = _position + offset; + break; + + case SeekOrigin.End: + // End of our window, not necessarily end of the underlying stream. + long windowEndRel = Tail >= 0 + ? Tail - Head + : _baseStream.Length - Head; + newRel = windowEndRel + offset; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(origin)); + } + + if (newRel < 0) + { + throw new IOException("Cannot seek before the beginning of the stream."); + } + + long max = Length; + if (Tail >= 0 && newRel > max) + { + throw new IOException("Cannot seek past the end of the bounded stream."); + } + + _position = newRel; + return _position; + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + ValidateBufferArgs(buffer, offset, count); + + if (IsReadOnly) + { + throw new NotSupportedException("This stream is read only."); + } + + lock (_sync) + { + if (Tail >= 0 && Head + _position + count > Tail) + { + throw new NotSupportedException("Write would overrun the stream limit."); + } + + _baseStream.Seek(Head + _position, SeekOrigin.Begin); + _baseStream.Write(buffer, offset, count); + _position += count; + } + } + + public override void Write(ReadOnlySpan buffer) + { + if (IsReadOnly) + { + throw new NotSupportedException("This stream is read only."); + } + + lock (_sync) + { + if (Tail >= 0 && Head + _position + buffer.Length > Tail) + { + throw new NotSupportedException("Write would overrun the stream limit."); + } + + _baseStream.Seek(Head + _position, SeekOrigin.Begin); + _baseStream.Write(buffer); + _position += buffer.Length; } - return ToRelOffset(_proxy.Seek(absOffset, SeekOrigin.Begin)); } public override void SetLength(long value) { if (IsReadOnly) - throw new Exception("This stream is read only."); - - if (Tail < 0) - throw new NotSupportedException("Cannot expand the stream at this point."); - - if (Tail != _proxy.Length) - throw new NotSupportedException("Cannot resize the stream at this point."); - - _proxy.SetLength(Head + value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (IsReadOnly) - throw new Exception("This stream is read only."); - - if (Tail > 0 && _proxy.Position + count >= Tail) - throw new Exception("Source array will overrun the current limit."); - - _proxy.Write(buffer, offset, count); - } - - public StreamWrapper Fork(long head = 0, long tail = -1, bool keepOpen = false, bool isReadOnly = false) - { - return ForkAbs(ToAbsOffset(head, true), tail < 0 ? Tail : ToAbsOffset(tail, true), keepOpen, isReadOnly); - } - - public StreamWrapper ForkAbs(long head = 0, long tail = -1, bool keepOpen = false, bool isReadOnly = false) - { - if (head < 0 || head > BaseStream.Length) - throw new ArgumentOutOfRangeException(nameof(head), "Head pointer out of range."); - - if (tail >= 0 && tail > BaseStream.Length) - throw new ArgumentOutOfRangeException(nameof(tail), "Tail pointer is too large for the given offset."); - - return new StreamWrapper(BaseStream, keepOpen) { - Head = head, - Tail = tail, - IsReadOnly = IsReadOnly || isReadOnly, - }; - } - - private long ToAbsOffset(long relOffset, bool safe = false) - { - if (relOffset < 0) - throw new ArgumentOutOfRangeException(nameof(relOffset), relOffset, "The relative offset cannot be less than zero."); - - if (Limit >= 0 && relOffset > Limit) - throw new ArgumentOutOfRangeException(nameof(relOffset), relOffset, "The relative offset cannot be greater than the limit."); - - long value = relOffset + Head; - - if (safe) - { - if ((Tail > 0 && value >= Tail) || value < Head) - { - throw new ArgumentOutOfRangeException(nameof(relOffset)); - } + throw new NotSupportedException("This stream is read only."); } - return value; - } + ArgumentOutOfRangeException.ThrowIfNegative(value); - private long ToRelOffset(long absOffset, bool safe = false) - { - long value = absOffset - Head; - - if (safe) + lock (_sync) { - if ((Tail > 0 && value > Length) || value < 0) + long newEnd = Head + value; + + if (Tail >= 0 && newEnd > Tail) { - throw new ArgumentOutOfRangeException(nameof(absOffset)); + throw new NotSupportedException("Cannot grow the stream past the tail bound."); + } + + _baseStream.SetLength(newEnd); + + // Update our window end to match what we just did. + Tail = Tail >= 0 ? Tail : -1; + + if (_position > value) + { + _position = value; } } + } - return value; + public StreamWrapper Fork(long head = 0, long tail = -1, + bool keepOpen = false, bool isReadOnly = false) + { + long absHead = Head + head; + long absTail = tail < 0 ? Tail : Head + tail; + return ForkAbs(absHead, absTail, keepOpen, isReadOnly); + } + + public StreamWrapper ForkAbs(long head = 0, long tail = -1, + bool keepOpen = false, bool isReadOnly = false) + { + if (head < 0 || head > _baseStream.Length) + { + throw new ArgumentOutOfRangeException(nameof(head)); + } + + if (tail >= 0 && tail > _baseStream.Length) + { + throw new ArgumentOutOfRangeException(nameof(tail)); + } + + if (tail >= 0 && tail < head) + { + throw new ArgumentOutOfRangeException(nameof(tail)); + } + + return new StreamWrapper(_baseStream, _sync, head, tail, + keepOpen, IsReadOnly || isReadOnly); + } + + // ---------- Helpers ---------- + + private static void ValidateBufferArgs(byte[] buffer, int offset, int count) + { + ArgumentNullException.ThrowIfNull(buffer); + ArgumentOutOfRangeException.ThrowIfNegative(offset); + ArgumentOutOfRangeException.ThrowIfNegative(count); + ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Length - offset); } protected override void Dispose(bool disposing) @@ -218,10 +332,19 @@ namespace ReFuel.Gltf.IO base.Dispose(disposing); if (!disposing) + { return; + } - if (!KeepOpen) - _proxy.Dispose(); + if (KeepOpen) + { + return; + } + + lock (_sync) + { + _baseStream.Dispose(); + } } } } -- 2.52.0 From 71a89126352024d81b26359156eb900f642793c0 Mon Sep 17 00:00:00 2001 From: JoeTwizzle Date: Fri, 23 Oct 2026 19:21:18 +0200 Subject: [PATCH 3/6] Fix extension loading --- ReFuel.Gltf/GltfMesh.cs | 10 ++++++++-- ReFuel.Gltf/GltfObject.cs | 4 ++-- ReFuel.Gltf/GltfSerializedExtensionProvider.cs | 4 +--- ReFuel.Gltf/IGltfExtensionProvider.cs | 7 ------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/ReFuel.Gltf/GltfMesh.cs b/ReFuel.Gltf/GltfMesh.cs index 9fed229..91a034f 100644 --- a/ReFuel.Gltf/GltfMesh.cs +++ b/ReFuel.Gltf/GltfMesh.cs @@ -165,8 +165,14 @@ namespace ReFuel.Gltf this.material.Id = index; } - // TODO: materials. - // TODO: morph targets. + if (element.TryGetProperty("targets", out JsonElement targetsElement)) + { + foreach (JsonProperty property in materialElement.EnumerateObject()) + { + //TODO: targets + //this.Targets.Add(weights); + } + } if (element.TryGetProperty("mode", out JsonElement modeElement)) { diff --git a/ReFuel.Gltf/GltfObject.cs b/ReFuel.Gltf/GltfObject.cs index 316f399..5964218 100755 --- a/ReFuel.Gltf/GltfObject.cs +++ b/ReFuel.Gltf/GltfObject.cs @@ -29,7 +29,7 @@ namespace ReFuel.Gltf { public abstract GltfObjectKind Kind { get; } public GltfDocument Document { get; protected set; } - public GltfCollection Extension { get; protected set; } = new GltfCollection(); + public GltfCollection Extension { get; protected set; } = new GltfCollection(true); public GltfCollection Extras { get; protected set; } = new GltfCollection(); protected GltfObject() @@ -49,7 +49,7 @@ namespace ReFuel.Gltf { Extras.Parse(extras); } - if (element.TryGetProperty("extension", out JsonElement extension)) + if (element.TryGetProperty("extensions", out JsonElement extension)) { Extension.Parse(extension); } diff --git a/ReFuel.Gltf/GltfSerializedExtensionProvider.cs b/ReFuel.Gltf/GltfSerializedExtensionProvider.cs index 0bd8786..d7e8cee 100755 --- a/ReFuel.Gltf/GltfSerializedExtensionProvider.cs +++ b/ReFuel.Gltf/GltfSerializedExtensionProvider.cs @@ -5,12 +5,10 @@ using System.Text.Json.Serialization; namespace ReFuel.Gltf { - public class GltfSerializedExtensionProvider : IGltfExtensionProvider + public class GltfSerializedExtensionProvider : IGltfExtensionProvider { public string Name { get; } - public Type? DataType { get; } = typeof(T); - public GltfSerializedExtensionProvider(string name) { Name = name; diff --git a/ReFuel.Gltf/IGltfExtensionProvider.cs b/ReFuel.Gltf/IGltfExtensionProvider.cs index 3aa0dad..2d8ad55 100755 --- a/ReFuel.Gltf/IGltfExtensionProvider.cs +++ b/ReFuel.Gltf/IGltfExtensionProvider.cs @@ -6,15 +6,8 @@ namespace ReFuel.Gltf public interface IGltfExtensionProvider { string Name { get; } - Type? DataType { get; } object ParseExtensionObject(JsonElement element); void WriteExtensionObject(object obj, Utf8JsonWriter writer); } - - public interface IGltfExtensionProvider : IGltfExtensionProvider - { - T ParseExtension(JsonElement element); - void WriteExtension(T value, Utf8JsonWriter writer); - } } \ No newline at end of file -- 2.52.0 From fe9d87155cf34a2ebfd5de301d8216ad3a5ed765 Mon Sep 17 00:00:00 2001 From: JoeTwizzle Date: Fri, 23 Oct 2026 19:25:46 +0200 Subject: [PATCH 4/6] Add Extension parent context --- .../KhrMaterialsEmissiveStrength.cs | 37 +++++++++++++++++++ ReFuel.Gltf/GltfCollection.cs | 14 +++---- ReFuel.Gltf/GltfDocument.cs | 4 +- ReFuel.Gltf/GltfEmptyExtensionProvider.cs | 2 +- ReFuel.Gltf/GltfObject.cs | 4 +- .../GltfSerializedExtensionProvider.cs | 6 +-- ReFuel.Gltf/IGltfExtensionProvider.cs | 2 +- 7 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 ReFuel.Gltf/Extensions/KhrMaterialsEmissiveStrength.cs diff --git a/ReFuel.Gltf/Extensions/KhrMaterialsEmissiveStrength.cs b/ReFuel.Gltf/Extensions/KhrMaterialsEmissiveStrength.cs new file mode 100644 index 0000000..2e49d53 --- /dev/null +++ b/ReFuel.Gltf/Extensions/KhrMaterialsEmissiveStrength.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; + +namespace ReFuel.Gltf.Extensions; + +struct KhrMaterialsEmissiveStrengthExtension +{ + public double EmissiveStrenght; + + public KhrMaterialsEmissiveStrengthExtension(double emissiveStrenght) + { + EmissiveStrenght = emissiveStrenght; + } +} + +public sealed class KhrMaterialsEmissiveStrengthExtensionProvider : IGltfExtensionProvider +{ + public string Name => "KHR_materials_emissive_strength"; + + public object ParseExtensionObject(GltfObjectKind parent, JsonElement element) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(parent, GltfObjectKind.Material); + if (!element.TryGetProperty("emissiveStrength", out var property) || !property.TryGetDouble(out double d)) + { + d = 1.0; + } + + return new KhrMaterialsEmissiveStrengthExtension(d); + } + + public void WriteExtensionObject(object obj, Utf8JsonWriter writer) + { + throw new NotImplementedException(); + } +} diff --git a/ReFuel.Gltf/GltfCollection.cs b/ReFuel.Gltf/GltfCollection.cs index 4bf252d..8469f07 100755 --- a/ReFuel.Gltf/GltfCollection.cs +++ b/ReFuel.Gltf/GltfCollection.cs @@ -16,7 +16,7 @@ namespace ReFuel.Gltf this.extensionMode = extensionMode; } - public void Parse(JsonElement collection) + public void Parse(GltfObjectKind parent, JsonElement collection) { if (collection.ValueKind != JsonValueKind.Object) throw new Exception("Expected a JSON object."); @@ -25,7 +25,7 @@ namespace ReFuel.Gltf { if (extensionMode && GltfDocument.TryGetExtensionProvider(property.Name, out IGltfExtensionProvider? provider)) { - Add(property.Name, provider.ParseExtensionObject(property.Value)); + Add(property.Name, provider.ParseExtensionObject(parent, property.Value)); continue; } @@ -33,7 +33,7 @@ namespace ReFuel.Gltf { case JsonValueKind.Array: GltfList list = new GltfList(); - list.Parse(property.Value); + list.Parse(parent, property.Value); Add(property.Name, list); break; case JsonValueKind.False: @@ -58,7 +58,7 @@ namespace ReFuel.Gltf break; case JsonValueKind.Object: GltfCollection child = new GltfCollection(extensionMode); - child.Parse(property.Value); + child.Parse(parent, property.Value); Add(property.Name, child); break; case JsonValueKind.True: @@ -79,7 +79,7 @@ namespace ReFuel.Gltf { } - public void Parse(JsonElement collection) + public void Parse(GltfObjectKind parent, JsonElement collection) { if (collection.ValueKind != JsonValueKind.Array) throw new Exception("Expected a JSON array."); @@ -90,7 +90,7 @@ namespace ReFuel.Gltf { case JsonValueKind.Array: GltfList list = new GltfList(); - list.Parse(element); + list.Parse(parent, element); Add(list); break; case JsonValueKind.False: @@ -115,7 +115,7 @@ namespace ReFuel.Gltf break; case JsonValueKind.Object: GltfCollection child = new GltfCollection(false); - child.Parse(element); + child.Parse(parent, element); Add(child); break; case JsonValueKind.True: diff --git a/ReFuel.Gltf/GltfDocument.cs b/ReFuel.Gltf/GltfDocument.cs index 16b39b4..1be3147 100755 --- a/ReFuel.Gltf/GltfDocument.cs +++ b/ReFuel.Gltf/GltfDocument.cs @@ -293,8 +293,8 @@ namespace ReFuel.Gltf { switch (name) { - case "extension": Extension.Parse(element); break; - case "extra": Extras.Parse(element); break; + case "extension": Extension.Parse(Kind, element); break; + case "extra": Extras.Parse(Kind, element); break; case "scenes": AssertArray(element, "scenes"); foreach (JsonElement child in element.EnumerateArray()) diff --git a/ReFuel.Gltf/GltfEmptyExtensionProvider.cs b/ReFuel.Gltf/GltfEmptyExtensionProvider.cs index 37e9d24..4412314 100755 --- a/ReFuel.Gltf/GltfEmptyExtensionProvider.cs +++ b/ReFuel.Gltf/GltfEmptyExtensionProvider.cs @@ -14,7 +14,7 @@ namespace ReFuel.Gltf Name = name; } - public object ParseExtensionObject(JsonElement element) + public object ParseExtensionObject(GltfObjectKind parent, JsonElement element) { throw new NotSupportedException(); } diff --git a/ReFuel.Gltf/GltfObject.cs b/ReFuel.Gltf/GltfObject.cs index 5964218..5af5733 100755 --- a/ReFuel.Gltf/GltfObject.cs +++ b/ReFuel.Gltf/GltfObject.cs @@ -47,11 +47,11 @@ namespace ReFuel.Gltf { if (element.TryGetProperty("extras", out JsonElement extras)) { - Extras.Parse(extras); + Extras.Parse(Kind, extras); } if (element.TryGetProperty("extensions", out JsonElement extension)) { - Extension.Parse(extension); + Extension.Parse(Kind, extension); } } diff --git a/ReFuel.Gltf/GltfSerializedExtensionProvider.cs b/ReFuel.Gltf/GltfSerializedExtensionProvider.cs index d7e8cee..acaba27 100755 --- a/ReFuel.Gltf/GltfSerializedExtensionProvider.cs +++ b/ReFuel.Gltf/GltfSerializedExtensionProvider.cs @@ -16,14 +16,14 @@ namespace ReFuel.Gltf throw new Exception("This type is not JSON serializable."); } - public T ParseExtension(JsonElement element) + public T ParseExtension(GltfObjectKind parent, JsonElement element) { return JsonSerializer.Deserialize(element) ?? throw new Exception("Could not deserialize extension object."); } - public object ParseExtensionObject(JsonElement element) + public object ParseExtensionObject(GltfObjectKind parent, JsonElement element) { - return ParseExtension(element)!; + return ParseExtension(parent, element)!; } public void WriteExtension(T value, Utf8JsonWriter writer) diff --git a/ReFuel.Gltf/IGltfExtensionProvider.cs b/ReFuel.Gltf/IGltfExtensionProvider.cs index 2d8ad55..08ba209 100755 --- a/ReFuel.Gltf/IGltfExtensionProvider.cs +++ b/ReFuel.Gltf/IGltfExtensionProvider.cs @@ -7,7 +7,7 @@ namespace ReFuel.Gltf { string Name { get; } - object ParseExtensionObject(JsonElement element); + object ParseExtensionObject(GltfObjectKind parent, JsonElement element); void WriteExtensionObject(object obj, Utf8JsonWriter writer); } } \ No newline at end of file -- 2.52.0 From f10973aed69ce164fdc63cba6558a5ecdd8a6d74 Mon Sep 17 00:00:00 2001 From: JoeTwizzle Date: Fri, 23 Oct 2026 21:36:22 +0200 Subject: [PATCH 5/6] Add stream open to bufferview & image --- ReFuel.Gltf/GltfBufferView.cs | 8 +++++++- ReFuel.Gltf/GltfImage.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ReFuel.Gltf/GltfBufferView.cs b/ReFuel.Gltf/GltfBufferView.cs index fa2621b..5bb1365 100755 --- a/ReFuel.Gltf/GltfBufferView.cs +++ b/ReFuel.Gltf/GltfBufferView.cs @@ -1,4 +1,5 @@ -using System; +using ReFuel.Gltf.IO; +using System; using System.Text.Json; namespace ReFuel.Gltf @@ -26,6 +27,11 @@ namespace ReFuel.Gltf buffer = new GltfSmartId(document.Buffers); } + public Stream Open() + { + return ((StreamWrapper)buffer.Value.Open()).ForkAbs(ByteOffset, ByteOffset + ByteLength); + } + internal override void Deserialize(JsonElement element) { AssertObject(element); diff --git a/ReFuel.Gltf/GltfImage.cs b/ReFuel.Gltf/GltfImage.cs index a73c251..53f1da8 100644 --- a/ReFuel.Gltf/GltfImage.cs +++ b/ReFuel.Gltf/GltfImage.cs @@ -39,7 +39,7 @@ namespace ReFuel.Gltf { if (Uri == null) { - throw new Exception("Only images with a URI property are to be opened."); + return BufferView!.Open(); } if (Uri.IsAbsoluteUri) @@ -64,9 +64,13 @@ namespace ReFuel.Gltf // otherwise if (provider != null) + { return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString()); + } else + { return File.OpenRead(Path.Combine(pwd ?? string.Empty, Uri.LocalPath)); + } // throw new Exception("Unrecognized URI scheme."); } @@ -74,7 +78,9 @@ namespace ReFuel.Gltf internal override void Deserialize(JsonElement element) { if (element.ValueKind != JsonValueKind.Object) + { throw new Exception("Expected a JSON object for glTF image node."); + } foreach (JsonProperty property in element.EnumerateObject()) { -- 2.52.0 From e4e29a190711236857e40f46ee53f008fc77f5b1 Mon Sep 17 00:00:00 2001 From: JoeTwizzle Date: Sat, 24 Oct 2026 14:38:05 +0200 Subject: [PATCH 6/6] Remove BS comments --- ReFuel.Gltf/IO/StreamWrapper.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ReFuel.Gltf/IO/StreamWrapper.cs b/ReFuel.Gltf/IO/StreamWrapper.cs index 0c6b467..7178c2a 100644 --- a/ReFuel.Gltf/IO/StreamWrapper.cs +++ b/ReFuel.Gltf/IO/StreamWrapper.cs @@ -9,7 +9,7 @@ namespace ReFuel.Gltf.IO // Shared across forks so that concurrent access to the same base stream is serialized. private readonly object _sync; - // Logical position relative to Head. This is the fix for the "Position lies" problem. + // Logical position relative to Head. private long _position; public long Head { get; private init; } @@ -188,7 +188,6 @@ namespace ReFuel.Gltf.IO break; case SeekOrigin.End: - // End of our window, not necessarily end of the underlying stream. long windowEndRel = Tail >= 0 ? Tail - Head : _baseStream.Length - Head; @@ -277,7 +276,6 @@ namespace ReFuel.Gltf.IO _baseStream.SetLength(newEnd); - // Update our window end to match what we just did. Tail = Tail >= 0 ? Tail : -1; if (_position > value) @@ -317,8 +315,6 @@ namespace ReFuel.Gltf.IO keepOpen, IsReadOnly || isReadOnly); } - // ---------- Helpers ---------- - private static void ValidateBufferArgs(byte[] buffer, int offset, int count) { ArgumentNullException.ThrowIfNull(buffer); -- 2.52.0