Initial commit.
This commit is contained in:
commit
8256444c76
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# common editors
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.rider
|
||||||
|
**/.atom
|
||||||
|
**/.idea
|
||||||
|
|
||||||
|
# build artifacts
|
||||||
|
**/obj
|
||||||
|
**/bin
|
||||||
|
|
||||||
|
doc/html/**
|
||||||
|
doc/latex/**
|
||||||
|
|
22
ReFuel.Gltf.sln
Normal file
22
ReFuel.Gltf.sln
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReFuel.Gltf", "ReFuel.Gltf\ReFuel.Gltf.csproj", "{7DDA6046-BC8D-4969-BF6F-8450D515E8AE}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
59
ReFuel.Gltf/GlbStructures.cs
Executable file
59
ReFuel.Gltf/GlbStructures.cs
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 12)]
|
||||||
|
public struct GlbHeader
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public int Signature;
|
||||||
|
[FieldOffset(4)]
|
||||||
|
public int Version;
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public int Length;
|
||||||
|
|
||||||
|
public const int SIGNATURE_VALUE = 0x46546C67; // "glTF"
|
||||||
|
|
||||||
|
public static unsafe bool Read(Stream str, out GlbHeader header)
|
||||||
|
{
|
||||||
|
header = default;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
fixed (GlbHeader* pheader = &header)
|
||||||
|
len = str.Read(new Span<byte>(pheader, sizeof(GlbHeader)));
|
||||||
|
|
||||||
|
if (len != sizeof(GlbHeader))
|
||||||
|
return false;
|
||||||
|
else if (header.Signature == GlbHeader.SIGNATURE_VALUE)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 8)]
|
||||||
|
public struct GlbChunk
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public int Size;
|
||||||
|
[FieldOffset(4)]
|
||||||
|
public GlbChunkType Type;
|
||||||
|
|
||||||
|
public static unsafe bool Read(Stream str, out GlbChunk chunk)
|
||||||
|
|
||||||
|
{
|
||||||
|
chunk = default;
|
||||||
|
|
||||||
|
fixed (GlbChunk* pchunk = &chunk)
|
||||||
|
return str.Read(new Span<byte>(pchunk, sizeof(GlbChunk))) == sizeof(GlbChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GlbChunkType : int
|
||||||
|
{
|
||||||
|
Json = 0x4E4F534A,
|
||||||
|
Bin = 0x004E4942,
|
||||||
|
}
|
||||||
|
}
|
185
ReFuel.Gltf/GltfAccessor.cs
Executable file
185
ReFuel.Gltf/GltfAccessor.cs
Executable file
@ -0,0 +1,185 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfAccessor : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Accessor;
|
||||||
|
private GltfSmartId<GltfBufferView> bufferView;
|
||||||
|
|
||||||
|
public GltfBufferView? BufferView
|
||||||
|
{
|
||||||
|
get => bufferView.IsSet ? bufferView.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
bufferView.Reset();
|
||||||
|
else
|
||||||
|
bufferView.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long ByteOffset { get; set; } = 0;
|
||||||
|
public GltfComponentType ComponentType { get; set; } = GltfComponentType.Float;
|
||||||
|
public bool Normalized { get; set; } = false;
|
||||||
|
public int Count { get; set; } = 0;
|
||||||
|
public GltfAccessorType Type { get; set; } = GltfAccessorType.Scalar;
|
||||||
|
public float[]? Max { get; set; } = null;
|
||||||
|
public float[]? Min { get; set; } = null;
|
||||||
|
public object? Sparse { get; set; } = null;
|
||||||
|
public string? Name { get; set; } = null;
|
||||||
|
|
||||||
|
public GltfAccessor(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
bufferView = new GltfSmartId<GltfBufferView>(document.BufferViews);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
AssertObject(element);
|
||||||
|
|
||||||
|
if (element.TryGetProperty("bufferView", out JsonElement viewElement))
|
||||||
|
{
|
||||||
|
if (!viewElement.TryGetInt32(out int viewId))
|
||||||
|
{
|
||||||
|
PropertyFormatFail("bufferView", "expected an integer.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bufferView.Id = viewId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("byteOffset", out JsonElement offsetElement))
|
||||||
|
{
|
||||||
|
if (!offsetElement.TryGetInt64(out long offset))
|
||||||
|
{
|
||||||
|
PropertyFormatFail("byteOffset", "expected an integer.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ByteOffset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!element.TryGetProperty("componentType", out JsonElement componentElement))
|
||||||
|
{
|
||||||
|
RequiredProperty("componentType");
|
||||||
|
}
|
||||||
|
else if (!componentElement.TryGetInt32(out int i))
|
||||||
|
{
|
||||||
|
PropertyFormatFail("componentType", "expected an integer.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ComponentType = (GltfComponentType)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("normalized", out JsonElement normalizedElement))
|
||||||
|
{
|
||||||
|
switch (normalizedElement.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.True:
|
||||||
|
Normalized = true;
|
||||||
|
break;
|
||||||
|
case JsonValueKind.False:
|
||||||
|
Normalized = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
PropertyFormatFail("normalized", "expected a boolean.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!element.TryGetProperty("count", out JsonElement countElement))
|
||||||
|
RequiredProperty("count");
|
||||||
|
else if (!countElement.TryGetInt32(out int i))
|
||||||
|
PropertyFormatFail("count", "expected an integer.");
|
||||||
|
else
|
||||||
|
Count = i;
|
||||||
|
|
||||||
|
if (!element.TryGetProperty("type", out JsonElement typeElement) )
|
||||||
|
RequiredProperty("type");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (typeElement.GetString())
|
||||||
|
{
|
||||||
|
case "SCALAR":
|
||||||
|
Type = GltfAccessorType.Scalar;
|
||||||
|
break;
|
||||||
|
case "VEC2":
|
||||||
|
Type = GltfAccessorType.Vec2;
|
||||||
|
break;
|
||||||
|
case "VEC3":
|
||||||
|
Type = GltfAccessorType.Vec3;
|
||||||
|
break;
|
||||||
|
case "VEC4":
|
||||||
|
Type = GltfAccessorType.Vec4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "MAT2":
|
||||||
|
Type = GltfAccessorType.Mat2;
|
||||||
|
break;
|
||||||
|
case "MAT3":
|
||||||
|
Type = GltfAccessorType.Mat3;
|
||||||
|
break;
|
||||||
|
case "MAT4":
|
||||||
|
Type = GltfAccessorType.Mat4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
RequiredProperty("type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("min", out JsonElement minElement))
|
||||||
|
{
|
||||||
|
if (minElement.ValueKind != JsonValueKind.Array)
|
||||||
|
PropertyFormatFail("min", "expected an array.");
|
||||||
|
|
||||||
|
List<float> floats = new List<float>();
|
||||||
|
|
||||||
|
foreach (JsonElement child in minElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (!child.TryGetSingle(out float f))
|
||||||
|
PropertyFormatFail("min", "expected numeric array members.");
|
||||||
|
floats.Add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Min = floats.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("max", out JsonElement maxElement))
|
||||||
|
{
|
||||||
|
if (minElement.ValueKind != JsonValueKind.Array)
|
||||||
|
PropertyFormatFail("max", "expected an array.");
|
||||||
|
|
||||||
|
List<float> floats = new List<float>();
|
||||||
|
|
||||||
|
foreach (JsonElement child in minElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (!child.TryGetSingle(out float f))
|
||||||
|
PropertyFormatFail("max", "expected numeric array members.");
|
||||||
|
floats.Add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Max = floats.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: I'm skipping sparse for now.
|
||||||
|
|
||||||
|
if (element.TryGetProperty("name", out JsonElement nameElement))
|
||||||
|
{
|
||||||
|
Name = nameElement.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
ReFuel.Gltf/GltfAccessorType.cs
Executable file
13
ReFuel.Gltf/GltfAccessorType.cs
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfAccessorType
|
||||||
|
{
|
||||||
|
Scalar,
|
||||||
|
Vec2,
|
||||||
|
Vec3,
|
||||||
|
Vec4,
|
||||||
|
Mat2,
|
||||||
|
Mat3,
|
||||||
|
Mat4
|
||||||
|
}
|
||||||
|
}
|
68
ReFuel.Gltf/GltfAsset.cs
Executable file
68
ReFuel.Gltf/GltfAsset.cs
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfAsset : GltfObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Asset;
|
||||||
|
public Version Version { get; private set; } = new Version(2, 0);
|
||||||
|
public Version? MinVersion { get; private set; } = null;
|
||||||
|
public string? Generator { get; private set; } = null;
|
||||||
|
public string? Copyright { get; private set; } = null;
|
||||||
|
|
||||||
|
internal GltfAsset(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected a JSON object.");
|
||||||
|
|
||||||
|
JsonElement value;
|
||||||
|
if (element.TryGetProperty("version", out value))
|
||||||
|
{
|
||||||
|
string? str = value.GetString();
|
||||||
|
if (Version.TryParse(str, out Version? v))
|
||||||
|
{
|
||||||
|
Version = v;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Malformed version string.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("minVersion", out value))
|
||||||
|
{
|
||||||
|
string? str = value.GetString();
|
||||||
|
if (Version.TryParse(str, out Version? v))
|
||||||
|
{
|
||||||
|
MinVersion = v;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Malformed version string.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("generator", out value))
|
||||||
|
{
|
||||||
|
Generator = value.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("copyright", out value))
|
||||||
|
{
|
||||||
|
Copyright = value.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
ReFuel.Gltf/GltfAttributeType.cs
Executable file
14
ReFuel.Gltf/GltfAttributeType.cs
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfAttributeType
|
||||||
|
{
|
||||||
|
Position,
|
||||||
|
Normal,
|
||||||
|
Tangent,
|
||||||
|
TexCoord,
|
||||||
|
Color,
|
||||||
|
Joints,
|
||||||
|
Weights,
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
}
|
88
ReFuel.Gltf/GltfBuffer.cs
Executable file
88
ReFuel.Gltf/GltfBuffer.cs
Executable file
@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using ReFuel.Gltf.IO;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfBuffer : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Buffer;
|
||||||
|
|
||||||
|
public Uri? Uri { get; set; }
|
||||||
|
public long ByteLength { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public GltfBuffer(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream Open(string? pwd = null, IGltfStreamProvider? provider = null)
|
||||||
|
{
|
||||||
|
if (Uri == null)
|
||||||
|
{
|
||||||
|
return ((StreamWrapper)Document.BinaryStreams[Id]).Fork(keepOpen: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Uri.IsAbsoluteUri ? Uri.Scheme : "file") // Assume relative URIs are files.
|
||||||
|
{
|
||||||
|
case "file":
|
||||||
|
if (provider != null)
|
||||||
|
{
|
||||||
|
return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string path = Path.Combine(pwd ?? string.Empty, Uri.LocalPath);
|
||||||
|
return File.OpenRead(path);
|
||||||
|
}
|
||||||
|
case "data":
|
||||||
|
return new GltfUriReader(Uri);
|
||||||
|
default:
|
||||||
|
if (provider != null)
|
||||||
|
return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Unrecognized URI scheme.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object.");
|
||||||
|
|
||||||
|
if (element.TryGetProperty("byteLength", out JsonElement lengthElement))
|
||||||
|
{
|
||||||
|
if (!lengthElement.TryGetInt64(out long length))
|
||||||
|
throw new Exception("Length is not an integer.");
|
||||||
|
|
||||||
|
ByteLength = length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Byte length is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("name", out JsonElement nameElement))
|
||||||
|
{
|
||||||
|
Name = nameElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("uri", out JsonElement uriElement))
|
||||||
|
{
|
||||||
|
if (!Uri.TryCreate(uriElement.GetString(), UriKind.RelativeOrAbsolute, out Uri? uri))
|
||||||
|
throw new Exception("Malformed URI.");
|
||||||
|
|
||||||
|
Uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
ReFuel.Gltf/GltfBufferView.cs
Executable file
96
ReFuel.Gltf/GltfBufferView.cs
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfBufferView : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.BufferView;
|
||||||
|
|
||||||
|
private GltfSmartId<GltfBuffer> buffer;
|
||||||
|
|
||||||
|
public GltfBuffer Buffer
|
||||||
|
{
|
||||||
|
get => buffer.Value;
|
||||||
|
set => buffer.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long ByteOffset { get; set; } = 0;
|
||||||
|
public long ByteLength { get; set; } = 0;
|
||||||
|
public long Stride { get; set; } = 0;
|
||||||
|
public GltfTargetHint Target { get; set; } = GltfTargetHint.None;
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
internal GltfBufferView(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
buffer = new GltfSmartId<GltfBuffer>(document.Buffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
AssertObject(element);
|
||||||
|
|
||||||
|
if (!element.TryGetProperty("buffer", out JsonElement bufferElement))
|
||||||
|
{
|
||||||
|
RequiredProperty("buffer");
|
||||||
|
}
|
||||||
|
else if (!bufferElement.TryGetInt32(out int bufferId))
|
||||||
|
{
|
||||||
|
PropertyFormatFail("buffer", "expected an integer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer.Id = bufferId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!element.TryGetProperty("byteLength", out JsonElement byteLengthElement))
|
||||||
|
{
|
||||||
|
RequiredProperty("byteLength");
|
||||||
|
}
|
||||||
|
else if (!byteLengthElement.TryGetInt64(out long byteLength))
|
||||||
|
{
|
||||||
|
PropertyFormatFail("byteLength", "expected an integer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ByteLength = byteLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("byteOffset", out JsonElement byteOffset))
|
||||||
|
{
|
||||||
|
if (!byteOffset.TryGetInt64(out long l))
|
||||||
|
PropertyFormatFail("byteOffset", "excpected an integer.");
|
||||||
|
|
||||||
|
ByteOffset = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("byteStride", out JsonElement stride))
|
||||||
|
{
|
||||||
|
if (!stride.TryGetInt64(out long l))
|
||||||
|
PropertyFormatFail("byteStride", "excpected an integer.");
|
||||||
|
|
||||||
|
Stride = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("target", out JsonElement targetElement))
|
||||||
|
{
|
||||||
|
if (!targetElement.TryGetInt32(out int i))
|
||||||
|
PropertyFormatFail("target", "expected an integer.");
|
||||||
|
|
||||||
|
Target = (GltfTargetHint)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("name", out JsonElement nameElement))
|
||||||
|
{
|
||||||
|
Name = nameElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
ReFuel.Gltf/GltfCollection.cs
Executable file
134
ReFuel.Gltf/GltfCollection.cs
Executable file
@ -0,0 +1,134 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty object for storing glTF extra and extension properties.
|
||||||
|
/// </summary>
|
||||||
|
public class GltfCollection : Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
private bool extensionMode;
|
||||||
|
|
||||||
|
public GltfCollection(bool extensionMode = false)
|
||||||
|
{
|
||||||
|
this.extensionMode = extensionMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Parse(JsonElement collection)
|
||||||
|
{
|
||||||
|
if (collection.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected a JSON object.");
|
||||||
|
|
||||||
|
foreach (JsonProperty property in collection.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (extensionMode && GltfDocument.TryGetExtensionProvider(property.Name, out IGltfExtensionProvider? provider))
|
||||||
|
{
|
||||||
|
Add(property.Name, provider.ParseExtensionObject(property.Value));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (property.Value.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.Array:
|
||||||
|
GltfList list = new GltfList();
|
||||||
|
list.Parse(property.Value);
|
||||||
|
Add(property.Name, list);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.False:
|
||||||
|
Add(property.Name, false);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Null:
|
||||||
|
Add(property.Name, null);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
// How do we decide if the thing we want is an integer or a fp value?
|
||||||
|
// Integer compare them...
|
||||||
|
property.Value.TryGetDouble(out double d);
|
||||||
|
property.Value.TryGetInt64(out long l);
|
||||||
|
if (l == (long)d)
|
||||||
|
{
|
||||||
|
Add(property.Name, l);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Add(property.Name, d);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Object:
|
||||||
|
GltfCollection child = new GltfCollection(extensionMode);
|
||||||
|
child.Parse(property.Value);
|
||||||
|
Add(property.Name, child);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.True:
|
||||||
|
Add(property.Name, true);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.String:
|
||||||
|
Add(property.Name, property.Value.GetString());
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Undefined: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfList : List<object?>
|
||||||
|
{
|
||||||
|
public GltfList()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Parse(JsonElement collection)
|
||||||
|
{
|
||||||
|
if (collection.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected a JSON array.");
|
||||||
|
|
||||||
|
foreach (JsonElement element in collection.EnumerateArray())
|
||||||
|
{
|
||||||
|
switch (element.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.Array:
|
||||||
|
GltfList list = new GltfList();
|
||||||
|
list.Parse(element);
|
||||||
|
Add(list);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.False:
|
||||||
|
Add(false);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Null:
|
||||||
|
Add(null);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
// How do we decide if the thing we want is an integer or a fp value?
|
||||||
|
// Integer compare them...
|
||||||
|
element.TryGetDouble(out double d);
|
||||||
|
element.TryGetInt64(out long l);
|
||||||
|
if (l == (long)d)
|
||||||
|
{
|
||||||
|
Add(l);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Add(d);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Object:
|
||||||
|
GltfCollection child = new GltfCollection(false);
|
||||||
|
child.Parse(element);
|
||||||
|
Add(child);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.True:
|
||||||
|
Add(true);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.String:
|
||||||
|
Add(element.GetString());
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Undefined: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
ReFuel.Gltf/GltfComponentType.cs
Executable file
12
ReFuel.Gltf/GltfComponentType.cs
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfComponentType
|
||||||
|
{
|
||||||
|
Byte = 5120,
|
||||||
|
UnsignedByte = 5121,
|
||||||
|
Short = 5122,
|
||||||
|
UnsignedShort = 5123,
|
||||||
|
UnsignedInt = 5125,
|
||||||
|
Float = 5126
|
||||||
|
}
|
||||||
|
}
|
407
ReFuel.Gltf/GltfDocument.cs
Executable file
407
ReFuel.Gltf/GltfDocument.cs
Executable file
@ -0,0 +1,407 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using ReFuel.Gltf.IO;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum GltfDocumentKind
|
||||||
|
{
|
||||||
|
Json = 1 << 0,
|
||||||
|
Binary = 1 << 1,
|
||||||
|
Any = Json | Binary,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// glTF Document Object GLObjects
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(GltfJsonConverter))]
|
||||||
|
public class GltfDocument : GltfObject, IDisposable
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Document;
|
||||||
|
|
||||||
|
public List<Stream> BinaryStreams { get; private set; }
|
||||||
|
|
||||||
|
public int DefaultScene { get; set; } = 0;
|
||||||
|
public HashSet<string> Extensions { get; set; } = new HashSet<string>();
|
||||||
|
public HashSet<string> Required { get; set; } = new HashSet<string>();
|
||||||
|
public GltfAsset Asset { get; }
|
||||||
|
public GltfIdList<GltfScene> Scenes { get; } = new GltfIdList<GltfScene>();
|
||||||
|
public GltfIdList<GltfNode> Nodes { get; } = new GltfIdList<GltfNode>();
|
||||||
|
public GltfIdList<GltfMesh> Meshes { get; } = new GltfIdList<GltfMesh>();
|
||||||
|
public GltfIdList<GltfBuffer> Buffers { get; } = new GltfIdList<GltfBuffer>();
|
||||||
|
public GltfIdList<GltfBufferView> BufferViews { get; } = new GltfIdList<GltfBufferView>();
|
||||||
|
public GltfIdList<GltfAccessor> Accessors { get; } = new GltfIdList<GltfAccessor>();
|
||||||
|
public GltfIdList<GltfMaterial> Materials { get; } = new GltfIdList<GltfMaterial>();
|
||||||
|
public GltfIdList<GltfTexture> Textures { get; } = new GltfIdList<GltfTexture>();
|
||||||
|
public GltfIdList<GltfSampler> Samplers { get; } = new GltfIdList<GltfSampler>();
|
||||||
|
public GltfIdList<GltfImage> Images { get; } = new GltfIdList<GltfImage>();
|
||||||
|
public GltfIdList<GltfSkin> Skins { get; } = new GltfIdList<GltfSkin>();
|
||||||
|
|
||||||
|
private GltfDocument(JsonDocument doc, IEnumerable<StreamWrapper>? binaryStreams = null)
|
||||||
|
{
|
||||||
|
Document = this;
|
||||||
|
Asset = new GltfAsset(this);
|
||||||
|
|
||||||
|
if (binaryStreams == null)
|
||||||
|
BinaryStreams = new List<Stream>();
|
||||||
|
else
|
||||||
|
BinaryStreams = new List<Stream>(binaryStreams);
|
||||||
|
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
Deserialize(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteStringSet(HashSet<string> set, JsonElement stringArray)
|
||||||
|
{
|
||||||
|
if (stringArray.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected a string array.");
|
||||||
|
|
||||||
|
foreach (JsonElement item in stringArray.EnumerateArray())
|
||||||
|
{
|
||||||
|
string? str = item.GetString();
|
||||||
|
if (str != null)
|
||||||
|
set.Add(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(Stream str, GltfDocumentKind kind)
|
||||||
|
{
|
||||||
|
if (BinaryStreams.Count > 0)
|
||||||
|
{
|
||||||
|
if (kind == GltfDocumentKind.Json)
|
||||||
|
throw new NotSupportedException("Cannot write binary streams into a JSON style glTF document.");
|
||||||
|
else if (kind == GltfDocumentKind.Any)
|
||||||
|
kind = GltfDocumentKind.Binary;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case GltfDocumentKind.Json:
|
||||||
|
SaveJson(str);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case GltfDocumentKind.Binary:
|
||||||
|
SaveBinary(str);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveJson(Stream str)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveBinary(Stream str)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GltfDocument OpenJson(StreamWrapper str)
|
||||||
|
{
|
||||||
|
JsonDocument doc = JsonDocument.Parse(str, new JsonDocumentOptions()
|
||||||
|
{
|
||||||
|
AllowTrailingCommas = true,
|
||||||
|
CommentHandling = JsonCommentHandling.Skip
|
||||||
|
});
|
||||||
|
|
||||||
|
return new GltfDocument(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GltfDocument OpenJson(ref Utf8JsonReader reader)
|
||||||
|
{
|
||||||
|
if (!JsonDocument.TryParseValue(ref reader, out JsonDocument? document))
|
||||||
|
{
|
||||||
|
throw new JsonException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GltfDocument(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GltfDocument OpenBinary(StreamWrapper str)
|
||||||
|
{
|
||||||
|
if (GlbHeader.Read(str, out GlbHeader header))
|
||||||
|
{
|
||||||
|
return OpenBinary(str, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("File does not have a valid GLB header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GltfDocument OpenBinary(StreamWrapper str, in GlbHeader header)
|
||||||
|
{
|
||||||
|
if (header.Version < 2)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Only glTF version 2.0 files are supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDocument? document = null;
|
||||||
|
List<StreamWrapper> binaryStreams = new List<StreamWrapper>();
|
||||||
|
|
||||||
|
while (GlbChunk.Read(str, out GlbChunk chunk))
|
||||||
|
{
|
||||||
|
using StreamWrapper substream = str.ForkAbs(str.Position, chunk.Size, true);
|
||||||
|
|
||||||
|
if (chunk.Type == GlbChunkType.Json)
|
||||||
|
{
|
||||||
|
document = JsonDocument.Parse(substream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stream copy = new MemoryStream();
|
||||||
|
|
||||||
|
substream.CopyTo(copy);
|
||||||
|
copy.Position = 0;
|
||||||
|
|
||||||
|
binaryStreams.Add(new StreamWrapper(copy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
throw new Exception("This glTF document has no manifest JSON.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new GltfDocument(document, binaryStreams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GltfDocument Open(Stream str, GltfDocumentKind kind = GltfDocumentKind.Any)
|
||||||
|
{
|
||||||
|
using StreamWrapper wrapper = new StreamWrapper(str, true);
|
||||||
|
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case GltfDocumentKind.Json:
|
||||||
|
return OpenJson(wrapper);
|
||||||
|
case GltfDocumentKind.Binary:
|
||||||
|
return OpenBinary(wrapper);
|
||||||
|
default:
|
||||||
|
case GltfDocumentKind.Any:
|
||||||
|
if (GlbHeader.Read(wrapper, out GlbHeader header))
|
||||||
|
{
|
||||||
|
return OpenBinary(wrapper, header);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str.Seek(0, SeekOrigin.Begin);
|
||||||
|
return OpenJson(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isDisposed = false;
|
||||||
|
|
||||||
|
public void Dispose() => Dispose(true);
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (isDisposed) return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
foreach (Stream str in BinaryStreams)
|
||||||
|
str.Dispose();
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, IGltfExtensionProvider> extensionProviders = new Dictionary<string, IGltfExtensionProvider>();
|
||||||
|
private static HashSet<string> supportedExtensions = new HashSet<string>();
|
||||||
|
|
||||||
|
public static IReadOnlySet<string> SupportedExtensions => supportedExtensions;
|
||||||
|
|
||||||
|
static GltfDocument()
|
||||||
|
{
|
||||||
|
// AddExtension(RfChecksumProvider.ExtensionProviders);
|
||||||
|
// AddExtension(RfCompression.ExtensionProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddExtension(IGltfExtensionProvider provider)
|
||||||
|
{
|
||||||
|
supportedExtensions.Add(provider.Name);
|
||||||
|
extensionProviders.Add(provider.Name, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddExtension<T>() where T : IGltfExtensionProvider, new()
|
||||||
|
{
|
||||||
|
AddExtension(new T());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddExtension(IEnumerable<IGltfExtensionProvider> providers)
|
||||||
|
{
|
||||||
|
foreach (var provider in providers) AddExtension(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetExtensionProvider(string name, [NotNullWhen(true)] out IGltfExtensionProvider? provider)
|
||||||
|
{
|
||||||
|
return extensionProviders.TryGetValue(name, out provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement root)
|
||||||
|
{
|
||||||
|
if (root.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("The glTF JSON document has an object as its root element");
|
||||||
|
|
||||||
|
Dictionary<string, JsonElement> elements = new Dictionary<string, JsonElement>();
|
||||||
|
foreach (JsonProperty property in root.EnumerateObject())
|
||||||
|
{
|
||||||
|
// Some elements may be interpreted earlier than others.
|
||||||
|
// This is mainly the default child, asset information, and the extensions.
|
||||||
|
switch (property.Name)
|
||||||
|
{
|
||||||
|
case "asset":
|
||||||
|
Asset.Deserialize(property.Value);
|
||||||
|
if (Asset.MinVersion > new Version(2, 0) || Asset.Version != new Version(2, 0))
|
||||||
|
throw new Exception($"Unsupported glTF version {Asset.MinVersion ?? Asset.Version}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "extensionsUsed":
|
||||||
|
WriteStringSet(Extensions, property.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "requiredExtensions":
|
||||||
|
WriteStringSet(Required, property.Value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "child":
|
||||||
|
if (property.Value.TryGetInt32(out int defaultScene))
|
||||||
|
DefaultScene = defaultScene;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
elements.Add(property.Name, property.Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string value in Required)
|
||||||
|
{
|
||||||
|
if (!SupportedExtensions.Contains(value))
|
||||||
|
{
|
||||||
|
throw new Exception($"Need unsupported extension {value} for this glTF file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((string name, JsonElement element) in elements)
|
||||||
|
{
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case "extension": Extension.Parse(element); break;
|
||||||
|
case "extra": Extras.Parse(element); break;
|
||||||
|
case "scenes":
|
||||||
|
AssertArray(element, "scenes");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfScene scene = new GltfScene(this);
|
||||||
|
scene.Deserialize(child);
|
||||||
|
Scenes.Append(scene);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "nodes":
|
||||||
|
AssertArray(element, "nodes");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfNode node = new GltfNode(this);
|
||||||
|
node.Deserialize(child);
|
||||||
|
Nodes.Append(node);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "buffers":
|
||||||
|
AssertArray(element, "buffers");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfBuffer buffer = new GltfBuffer(this);
|
||||||
|
buffer.Deserialize(child);
|
||||||
|
Buffers.Append(buffer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "bufferViews":
|
||||||
|
AssertArray(element, "bufferViews");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfBufferView view = new GltfBufferView(this);
|
||||||
|
view.Deserialize(child);
|
||||||
|
BufferViews.Append(view);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "accessors":
|
||||||
|
AssertArray(element, "accessors");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfAccessor accessor = new GltfAccessor(this);
|
||||||
|
accessor.Deserialize(child);
|
||||||
|
Accessors.Append(accessor);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "meshes":
|
||||||
|
AssertArray(element, "meshes");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfMesh mesh = new GltfMesh(this);
|
||||||
|
mesh.Deserialize(child);
|
||||||
|
Meshes.Append(mesh);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "materials":
|
||||||
|
AssertArray(element, "materials");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfMaterial material = new GltfMaterial(this);
|
||||||
|
material.Deserialize(child);
|
||||||
|
Materials.Append(material);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "textures":
|
||||||
|
AssertArray(element, "textures");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfTexture texture = new GltfTexture(this);
|
||||||
|
texture.Deserialize(child);
|
||||||
|
Textures.Append(texture);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "samplers":
|
||||||
|
AssertArray(element, "samplers");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfSampler sampler = new GltfSampler(this);
|
||||||
|
sampler.Deserialize(child);
|
||||||
|
Samplers.Append(sampler);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "images":
|
||||||
|
AssertArray(element, "images");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfImage image = new GltfImage(this);
|
||||||
|
image.Deserialize(child);
|
||||||
|
Images.Append(image);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "skins":
|
||||||
|
AssertArray(element, "skins");
|
||||||
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfSkin skin = new GltfSkin(this);
|
||||||
|
skin.Deserialize(element);
|
||||||
|
Skins.Append(skin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
ReFuel.Gltf/GltfEmptyExtensionProvider.cs
Executable file
27
ReFuel.Gltf/GltfEmptyExtensionProvider.cs
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfEmptyExtensionProvider : IGltfExtensionProvider
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public Type? DataType { get; } = null;
|
||||||
|
|
||||||
|
public GltfEmptyExtensionProvider(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ParseExtensionObject(JsonElement element)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteExtensionObject(object obj, Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
ReFuel.Gltf/GltfExtensions.cs
Executable file
38
ReFuel.Gltf/GltfExtensions.cs
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public static class GltfExtensions
|
||||||
|
{
|
||||||
|
#region ReFuel Extensions
|
||||||
|
|
||||||
|
public const string RF_compression = nameof(RF_compression);
|
||||||
|
public const string RF_compression_deflate = nameof(RF_compression_deflate);
|
||||||
|
public const string RF_checksum = nameof(RF_checksum);
|
||||||
|
public const string RF_checksum_sha256 = nameof(RF_checksum_sha256);
|
||||||
|
public const string RF_texture_qoi = nameof(RF_texture_qoi);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Khronos Extensions
|
||||||
|
|
||||||
|
public const string KHR_lighs_punctual = nameof(KHR_lighs_punctual);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static ImmutableHashSet<string> BaseExtensions { get; }
|
||||||
|
|
||||||
|
static GltfExtensions()
|
||||||
|
{
|
||||||
|
Type t = typeof(GltfExtensions);
|
||||||
|
IEnumerable<string> fields = t.GetFields(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.Where(x => x.FieldType == typeof(string))
|
||||||
|
.Select(x => x.Name);
|
||||||
|
BaseExtensions = ImmutableHashSet.Create(fields.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
209
ReFuel.Gltf/GltfIdCollection.cs
Executable file
209
ReFuel.Gltf/GltfIdCollection.cs
Executable file
@ -0,0 +1,209 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public abstract class GltfIdObject : GltfObject
|
||||||
|
{
|
||||||
|
public int Id { get; internal set; }
|
||||||
|
|
||||||
|
public GltfIdObject(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfIdList<T> : IReadOnlyList<T>
|
||||||
|
where T : GltfIdObject
|
||||||
|
{
|
||||||
|
private readonly List<T> items = new List<T>();
|
||||||
|
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get => items[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => items.Count;
|
||||||
|
|
||||||
|
public int Append(T item)
|
||||||
|
{
|
||||||
|
int i = items.Count;
|
||||||
|
|
||||||
|
item.Id = i;
|
||||||
|
items.Add(item);
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSet(int i)
|
||||||
|
{
|
||||||
|
return i >= 0 && i <= Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
items.RemoveAt(index);
|
||||||
|
|
||||||
|
for (int i = index; i < Count; i++)
|
||||||
|
{
|
||||||
|
items[i].Id = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return items.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return items.GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfRefList<T> : ICollection<T>, IReadOnlyList<T> where T : GltfIdObject
|
||||||
|
{
|
||||||
|
private readonly List<ItemTuple> items = new List<ItemTuple>();
|
||||||
|
private readonly GltfIdList<T> list;
|
||||||
|
|
||||||
|
public IEnumerable<int> Ids
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
foreach (ItemTuple tuple in items)
|
||||||
|
{
|
||||||
|
yield return tuple.Item?.Id ?? tuple.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => items.Count;
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ItemTuple item = items[index];
|
||||||
|
|
||||||
|
if (item.Item != null)
|
||||||
|
return item.Item;
|
||||||
|
|
||||||
|
item.Item = list[item.Id];
|
||||||
|
items[index] = item;
|
||||||
|
return item.Item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfRefList(GltfIdList<T> referencedList)
|
||||||
|
{
|
||||||
|
list = referencedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
items.Add(new ItemTuple(item.Id, item));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(int item)
|
||||||
|
{
|
||||||
|
items.Add(new ItemTuple(item, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
items.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
return items.FindIndex(x => (x.Item?.Id ?? x.Id) == item.Id) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
array[arrayIndex + i] = this[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
yield return this[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
int idx = items.FindIndex(x => (x.Item?.Id ?? x.Id) == item.Id);
|
||||||
|
|
||||||
|
if (idx == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
items.RemoveAt(idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(int item)
|
||||||
|
{
|
||||||
|
int idx = items.FindIndex(x => (x.Item?.Id ?? x.Id) == item);
|
||||||
|
|
||||||
|
if (idx == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
items.RemoveAt(idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record struct ItemTuple(int Id, T? Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct GltfSmartId<T> where T : GltfIdObject
|
||||||
|
{
|
||||||
|
private readonly GltfIdList<T> list;
|
||||||
|
private int id = -1;
|
||||||
|
private T? value = null;
|
||||||
|
|
||||||
|
public int Id
|
||||||
|
{
|
||||||
|
get => value?.Id ?? id;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
id = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Value
|
||||||
|
{
|
||||||
|
get => value ?? (value = list[id]);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
id = value.Id;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSet => list.IsSet(id);
|
||||||
|
|
||||||
|
public GltfSmartId(GltfIdList<T> list)
|
||||||
|
{
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
id = -1;
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator T(GltfSmartId<T> id) => id.Value;
|
||||||
|
}
|
||||||
|
}
|
105
ReFuel.Gltf/GltfImage.cs
Normal file
105
ReFuel.Gltf/GltfImage.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using ReFuel.Gltf.IO;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfImage : GltfIdObject
|
||||||
|
{
|
||||||
|
private GltfSmartId<GltfBufferView> _bufferView = new GltfSmartId<GltfBufferView>();
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Image;
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public Uri? Uri { get; set; }
|
||||||
|
public string? MediaType { get; set; }
|
||||||
|
|
||||||
|
public GltfBufferView? BufferView
|
||||||
|
{
|
||||||
|
get => _bufferView.IsSet ? _bufferView.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
_bufferView.Reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_bufferView.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal GltfImage(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream Open(string? pwd = null, IGltfStreamProvider? provider = null)
|
||||||
|
{
|
||||||
|
if (Uri == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Only images with a URI property are to be opened.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Uri.IsAbsoluteUri)
|
||||||
|
{
|
||||||
|
switch (Uri.Scheme)
|
||||||
|
{
|
||||||
|
case "file":
|
||||||
|
if (provider != null)
|
||||||
|
{
|
||||||
|
return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string path = Path.Combine(pwd ?? string.Empty, Uri.LocalPath);
|
||||||
|
return File.OpenRead(path);
|
||||||
|
}
|
||||||
|
case "data":
|
||||||
|
return new GltfUriReader(Uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
switch (property.Name)
|
||||||
|
{
|
||||||
|
case "uri":
|
||||||
|
Uri = new Uri(property.Value.GetString() ?? throw new Exception("Expected a string."), UriKind.RelativeOrAbsolute);
|
||||||
|
break;
|
||||||
|
case "mimeType":
|
||||||
|
MediaType = property.Value.GetString() ?? throw new Exception("Expected a string.");
|
||||||
|
break;
|
||||||
|
case "bufferView":
|
||||||
|
_bufferView.Id = property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
case "name":
|
||||||
|
Name = property.Value.GetString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
ReFuel.Gltf/GltfJsonSerializer.cs
Normal file
22
ReFuel.Gltf/GltfJsonSerializer.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfJsonConverter : JsonConverter<GltfDocument>
|
||||||
|
{
|
||||||
|
public override GltfDocument? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (typeToConvert != typeof(GltfDocument))
|
||||||
|
throw new JsonException();
|
||||||
|
|
||||||
|
return GltfDocument.OpenJson(ref reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, GltfDocument value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
value.Serialize(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
313
ReFuel.Gltf/GltfMaterial.cs
Normal file
313
ReFuel.Gltf/GltfMaterial.cs
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfMaterialAlphaMode
|
||||||
|
{
|
||||||
|
Opaque,
|
||||||
|
Mask,
|
||||||
|
Blend,
|
||||||
|
Other
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfMaterialTextureInfo : GtlfMiscObject
|
||||||
|
{
|
||||||
|
private GltfSmartId<GltfTexture> _texture;
|
||||||
|
|
||||||
|
public GltfTexture? Texture
|
||||||
|
{
|
||||||
|
get => _texture.IsSet ? _texture.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
_texture.Reset();
|
||||||
|
else
|
||||||
|
_texture.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TexCoordIndex { get; set; } = 0;
|
||||||
|
|
||||||
|
internal GltfMaterialTextureInfo(GltfDocument document) : base (document)
|
||||||
|
{
|
||||||
|
_texture = new GltfSmartId<GltfTexture>(document.Textures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfMaterialTextureInfo(GltfDocument document, GltfTexture? texture, int texCoordIndex) : this(document)
|
||||||
|
{
|
||||||
|
Texture = texture;
|
||||||
|
TexCoordIndex = texCoordIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object.");
|
||||||
|
|
||||||
|
if (element.TryGetProperty("index", out JsonElement indexProp))
|
||||||
|
{
|
||||||
|
_texture.Id = indexProp.GetInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("texCoord", out JsonElement texCoordProp))
|
||||||
|
{
|
||||||
|
TexCoordIndex = texCoordProp.GetInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfMetallicRoughness : GtlfMiscObject
|
||||||
|
{
|
||||||
|
public Vector4 BaseColorFactor { get; set; } = new Vector4(1,1,1,1);
|
||||||
|
public GltfMaterialTextureInfo? BaseColorTexture { get; set; } = null;
|
||||||
|
public float Metallic { get; set; } = 1;
|
||||||
|
public float Roughness { get; set; } = 1;
|
||||||
|
public GltfMaterialTextureInfo? MetallicRoughnessTexture { get; set; } = null;
|
||||||
|
|
||||||
|
internal GltfMetallicRoughness(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object.");
|
||||||
|
|
||||||
|
JsonElement child;
|
||||||
|
if (element.TryGetProperty("baseColorFactor", out child))
|
||||||
|
{
|
||||||
|
if (child.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
throw new Exception("Expected a value array.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector4 color = Vector4.One;
|
||||||
|
int i = 0;
|
||||||
|
foreach (JsonElement item in child.EnumerateArray())
|
||||||
|
{
|
||||||
|
float value = item.GetSingle();
|
||||||
|
switch (i++)
|
||||||
|
{
|
||||||
|
case 0: color.X = value; break;
|
||||||
|
case 1: color.Y = value; break;
|
||||||
|
case 2: color.Z = value; break;
|
||||||
|
case 3: color.W = value; break;
|
||||||
|
default:
|
||||||
|
goto break_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
break_for:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BaseColorFactor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("baseColorTexture", out child))
|
||||||
|
{
|
||||||
|
BaseColorTexture = new GltfMaterialTextureInfo(Document);
|
||||||
|
BaseColorTexture.Deserialize(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("metallicFactor", out child))
|
||||||
|
{
|
||||||
|
Metallic = child.GetSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("roughnessFactor", out child))
|
||||||
|
{
|
||||||
|
Roughness = child.GetSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("metallicRoughnessTexture", out child))
|
||||||
|
{
|
||||||
|
MetallicRoughnessTexture = new GltfMaterialTextureInfo(Document);
|
||||||
|
MetallicRoughnessTexture.Deserialize(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class GltfNormalTexture : GltfMaterialTextureInfo
|
||||||
|
{
|
||||||
|
public float Scale { get; set; } = 1.0f;
|
||||||
|
|
||||||
|
internal GltfNormalTexture(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
base.Deserialize(element);
|
||||||
|
|
||||||
|
if (element.TryGetProperty("scale", out JsonElement scale))
|
||||||
|
{
|
||||||
|
Scale = scale.GetSingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfOcclusionTexture : GltfMaterialTextureInfo
|
||||||
|
{
|
||||||
|
public float Strength { get; set; } = 1;
|
||||||
|
|
||||||
|
internal GltfOcclusionTexture(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
base.Deserialize(element);
|
||||||
|
|
||||||
|
if (element.TryGetProperty("strength", out JsonElement strength))
|
||||||
|
{
|
||||||
|
Strength = strength.GetSingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfMaterial : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Material;
|
||||||
|
private GltfMaterialAlphaMode _alphaMode = GltfMaterialAlphaMode.Opaque;
|
||||||
|
private string _alphaModeString = "OPAQUE";
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public string AlphaModeString
|
||||||
|
{
|
||||||
|
get => _alphaModeString;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_alphaModeString = value;
|
||||||
|
_alphaMode = GltfMaterialAlphaModeFromString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfMaterialAlphaMode AlphaMode
|
||||||
|
{
|
||||||
|
get => _alphaMode;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_alphaMode = value;
|
||||||
|
_alphaModeString = GltfMaterialAlphaModeToString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float AlphaCutoff { get; set; } = 0.5f;
|
||||||
|
|
||||||
|
public bool DoubleSided { get; set; } = false;
|
||||||
|
|
||||||
|
public GltfMetallicRoughness? MetallicRoughness { get; set; } = null;
|
||||||
|
public GltfNormalTexture? NormalTexture { get; set; } = null;
|
||||||
|
public GltfOcclusionTexture? OcclusionTexture { get; set; } = null;
|
||||||
|
public GltfMaterialTextureInfo? EmissiveTexture { get; set; } = null;
|
||||||
|
public Vector3 EmissiveFactor { get; set; } = Vector3.Zero;
|
||||||
|
|
||||||
|
internal GltfMaterial(GltfDocument document) : base(document) { }
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object.");
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
|
||||||
|
foreach (JsonProperty property in element.EnumerateObject())
|
||||||
|
{
|
||||||
|
switch (property.Name)
|
||||||
|
{
|
||||||
|
case "name":
|
||||||
|
Name = property.Value.GetString();
|
||||||
|
break;
|
||||||
|
case "pbrMetallicRoughness":
|
||||||
|
MetallicRoughness = new GltfMetallicRoughness(Document);
|
||||||
|
MetallicRoughness.Deserialize(property.Value);
|
||||||
|
break;
|
||||||
|
case "normalTexture":
|
||||||
|
NormalTexture = new GltfNormalTexture(Document);
|
||||||
|
NormalTexture.Deserialize(property.Value);
|
||||||
|
break;
|
||||||
|
case "occlusionTexture":
|
||||||
|
OcclusionTexture = new GltfOcclusionTexture(Document);
|
||||||
|
OcclusionTexture.Deserialize(property.Value);
|
||||||
|
break;
|
||||||
|
case "emissiveTexture":
|
||||||
|
EmissiveTexture = new GltfMaterialTextureInfo(Document);
|
||||||
|
EmissiveTexture.Deserialize(property.Value);
|
||||||
|
break;
|
||||||
|
case "emissiveFactor":
|
||||||
|
if (property.Value.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
throw new Exception("Expected a value array as emissiveFactor");
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 factor = default;
|
||||||
|
int i = 0;
|
||||||
|
foreach (JsonElement item in property.Value.EnumerateArray())
|
||||||
|
{
|
||||||
|
float value = item.GetSingle();
|
||||||
|
switch (i++)
|
||||||
|
{
|
||||||
|
case 0: factor.X = value; break;
|
||||||
|
case 1: factor.Y = value; break;
|
||||||
|
case 2: factor.Z = value; break;
|
||||||
|
default:
|
||||||
|
goto break_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
break_for:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "alphaMode":
|
||||||
|
AlphaModeString = property.Value.GetString() ?? "OPAQUE";
|
||||||
|
break;
|
||||||
|
case "alphaCutoff":
|
||||||
|
AlphaCutoff = property.Value.GetSingle();
|
||||||
|
break;
|
||||||
|
case "doubleSided":
|
||||||
|
DoubleSided = property.Value.GetBoolean();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GltfMaterialAlphaModeToString(GltfMaterialAlphaMode mode) => mode switch {
|
||||||
|
GltfMaterialAlphaMode.Opaque => "OPAQUE",
|
||||||
|
GltfMaterialAlphaMode.Mask => "MASK",
|
||||||
|
GltfMaterialAlphaMode.Blend => "BLEND",
|
||||||
|
_ => throw new ArgumentException("GltfMaterialAlphaMode.Other is not a valid target.", nameof(mode)),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static GltfMaterialAlphaMode GltfMaterialAlphaModeFromString(string str) => str switch {
|
||||||
|
"OPAQUE" => GltfMaterialAlphaMode.Opaque,
|
||||||
|
"MASK" => GltfMaterialAlphaMode.Mask,
|
||||||
|
"BLEND" => GltfMaterialAlphaMode.Blend,
|
||||||
|
_ => GltfMaterialAlphaMode.Other
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
250
ReFuel.Gltf/GltfMesh.cs
Normal file
250
ReFuel.Gltf/GltfMesh.cs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public record struct GltfMeshAttribute
|
||||||
|
{
|
||||||
|
private GltfSmartId<GltfAccessor> accessor;
|
||||||
|
|
||||||
|
public GltfAccessor Accessor
|
||||||
|
{
|
||||||
|
get => accessor.Value;
|
||||||
|
set => accessor.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
public int Index { get; }
|
||||||
|
public GltfAttributeType Type { get; }
|
||||||
|
|
||||||
|
public GltfMeshAttribute(GltfDocument document, string name, int accessorIndex)
|
||||||
|
{
|
||||||
|
accessor = new GltfSmartId<GltfAccessor>(document.Accessors)
|
||||||
|
{
|
||||||
|
Id = accessorIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
ReadOnlySpan<char> full = name.AsSpan();
|
||||||
|
ReadOnlySpan<char> head;
|
||||||
|
ReadOnlySpan<char> tail;
|
||||||
|
|
||||||
|
int indexOfUnderscore = name.IndexOf('_');
|
||||||
|
if (indexOfUnderscore == -1)
|
||||||
|
{
|
||||||
|
head = full;
|
||||||
|
tail = ReadOnlySpan<char>.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
head = full.Slice(0, indexOfUnderscore);
|
||||||
|
tail = full.Slice(indexOfUnderscore+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (head.ToString())
|
||||||
|
{
|
||||||
|
case "POSITION":
|
||||||
|
Name = "POSITION";
|
||||||
|
Index = 0;
|
||||||
|
Type = GltfAttributeType.Position;
|
||||||
|
break;
|
||||||
|
case "NORMAL":
|
||||||
|
Name = "NORMAL";
|
||||||
|
Index = 0;
|
||||||
|
Type = GltfAttributeType.Normal;
|
||||||
|
break;
|
||||||
|
case "TANGENT":
|
||||||
|
Name = "TANGENT";
|
||||||
|
Index = 0;
|
||||||
|
Type = GltfAttributeType.Tangent;
|
||||||
|
break;
|
||||||
|
case "TEXCOORD":
|
||||||
|
Name = "TEXCOORD";
|
||||||
|
Index = int.Parse(tail);
|
||||||
|
Type = GltfAttributeType.TexCoord;
|
||||||
|
break;
|
||||||
|
case "COLOR":
|
||||||
|
Name = "COLOR";
|
||||||
|
Index = int.Parse(tail);
|
||||||
|
Type = GltfAttributeType.Color;
|
||||||
|
break;
|
||||||
|
case "JOINTS":
|
||||||
|
Name = "JOINTS";
|
||||||
|
Index = int.Parse(tail);
|
||||||
|
Type = GltfAttributeType.Joints;
|
||||||
|
break;
|
||||||
|
case "WEIGHTS":
|
||||||
|
Name = "WEIGHTS";
|
||||||
|
Index = int.Parse(tail);
|
||||||
|
Type = GltfAttributeType.Weights;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Name = tail.ToString();
|
||||||
|
Index = 0;
|
||||||
|
Type = GltfAttributeType.Custom;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfMeshPrimitive : GltfObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.MeshPrimitive;
|
||||||
|
private GltfSmartId<GltfAccessor> index;
|
||||||
|
private GltfSmartId<GltfMaterial> material;
|
||||||
|
|
||||||
|
public List<GltfMeshAttribute> Attributes { get; } = new List<GltfMeshAttribute>();
|
||||||
|
|
||||||
|
public GltfAccessor? Index
|
||||||
|
{
|
||||||
|
get => index.IsSet ? index : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
index.Reset();
|
||||||
|
else
|
||||||
|
index.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfMaterial? Material
|
||||||
|
{
|
||||||
|
get => material.IsSet ? material : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
material.Reset();
|
||||||
|
else
|
||||||
|
material.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfMeshMode Mode { get; set; } = GltfMeshMode.Triangles;
|
||||||
|
|
||||||
|
public GltfRefList<GltfIdObject> Targets { get; private set; }
|
||||||
|
|
||||||
|
internal GltfMeshPrimitive(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
Targets = new GltfRefList<GltfIdObject>(null!);
|
||||||
|
index = new GltfSmartId<GltfAccessor>(document.Accessors);
|
||||||
|
material = new GltfSmartId<GltfMaterial>(document.Materials);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
AssertObject(element);
|
||||||
|
|
||||||
|
if (!element.TryGetProperty("attributes", out JsonElement attributesElement))
|
||||||
|
RequiredProperty("attributes");
|
||||||
|
else if (attributesElement.ValueKind != JsonValueKind.Object)
|
||||||
|
PropertyFormatFail("attributes", "expected a dictionary.");
|
||||||
|
|
||||||
|
foreach (JsonProperty property in attributesElement.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (!property.Value.TryGetInt32(out int i))
|
||||||
|
{
|
||||||
|
PropertyFormatFail("attributes", "expected dictionary values to be integers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes.Add(new GltfMeshAttribute(Document, property.Name, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("indices", out JsonElement indicesElement))
|
||||||
|
{
|
||||||
|
if (!indicesElement.TryGetInt32(out int index))
|
||||||
|
PropertyFormatFail("indices", "expected an integer value.");
|
||||||
|
|
||||||
|
this.index.Id = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("material", out JsonElement materialElement))
|
||||||
|
{
|
||||||
|
if (!materialElement.TryGetInt32(out int index))
|
||||||
|
PropertyFormatFail("material", "expected an integer value.");
|
||||||
|
|
||||||
|
this.material.Id = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: materials.
|
||||||
|
// TODO: morph targets.
|
||||||
|
|
||||||
|
if (element.TryGetProperty("mode", out JsonElement modeElement))
|
||||||
|
{
|
||||||
|
if (!modeElement.TryGetInt32(out int mode))
|
||||||
|
PropertyFormatFail("mode", "expected integer value.");
|
||||||
|
|
||||||
|
Mode = (GltfMeshMode)mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfMesh : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Mesh;
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public float[]? Weights { get; set; }
|
||||||
|
public List<GltfMeshPrimitive> Primitives { get; } = new List<GltfMeshPrimitive>();
|
||||||
|
|
||||||
|
internal GltfMesh(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object.");
|
||||||
|
|
||||||
|
if (element.TryGetProperty("name", out JsonElement nameElement))
|
||||||
|
{
|
||||||
|
Name = nameElement.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("weights", out JsonElement weightsArray))
|
||||||
|
{
|
||||||
|
if (weightsArray.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected an array.");
|
||||||
|
|
||||||
|
List<float> values = new List<float>();
|
||||||
|
foreach (JsonElement item in weightsArray.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (!item.TryGetSingle(out float v))
|
||||||
|
throw new Exception("expected a number");
|
||||||
|
|
||||||
|
values.Add(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Weights = values.ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Weights = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("primitives", out JsonElement primitivesElement))
|
||||||
|
{
|
||||||
|
if (primitivesElement.ValueKind != JsonValueKind.Array)
|
||||||
|
PropertyFormatFail("primitives", "expected an array.");
|
||||||
|
|
||||||
|
foreach (JsonElement child in primitivesElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
GltfMeshPrimitive primitive = new GltfMeshPrimitive(Document);
|
||||||
|
primitive.Deserialize(child);
|
||||||
|
Primitives.Add(primitive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
ReFuel.Gltf/GltfMeshMode.cs
Executable file
13
ReFuel.Gltf/GltfMeshMode.cs
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfMeshMode
|
||||||
|
{
|
||||||
|
Points = 0,
|
||||||
|
Lines = 1,
|
||||||
|
LineLoop = 2,
|
||||||
|
LineStrip = 3,
|
||||||
|
Triangles = 4,
|
||||||
|
TriangleStrip = 5,
|
||||||
|
TriangleFan = 6
|
||||||
|
}
|
||||||
|
}
|
11
ReFuel.Gltf/GltfMiscObject.cs
Normal file
11
ReFuel.Gltf/GltfMiscObject.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public abstract class GtlfMiscObject : GltfObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Misc;
|
||||||
|
|
||||||
|
protected GtlfMiscObject(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
407
ReFuel.Gltf/GltfNode.cs
Executable file
407
ReFuel.Gltf/GltfNode.cs
Executable file
@ -0,0 +1,407 @@
|
|||||||
|
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfNode : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Node;
|
||||||
|
|
||||||
|
private GltfSmartId<GltfIdObject> camera;
|
||||||
|
private GltfSmartId<GltfIdObject> skin;
|
||||||
|
private GltfSmartId<GltfMesh> mesh;
|
||||||
|
private float[]? weights = null;
|
||||||
|
private TransformMode transformMode = TransformMode.Matrix;
|
||||||
|
private TransformUnion transform = new TransformUnion() { Matrix = Matrix4x4.Identity };
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public object? Camera { get; set; }
|
||||||
|
public GltfRefList<GltfNode> Children { get; }
|
||||||
|
public object? Skin { get; set; }
|
||||||
|
|
||||||
|
public Matrix4x4 Matrix
|
||||||
|
{
|
||||||
|
get => transformMode == TransformMode.Matrix ? transform.Matrix : transform.ToMatrix().Matrix;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
transformMode = TransformMode.Matrix;
|
||||||
|
transform.Matrix = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Quaternion Rotation
|
||||||
|
{
|
||||||
|
get => transformMode == TransformMode.SRT ? transform.Rotation : transform.ToSRT().Rotation;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (transformMode != TransformMode.SRT)
|
||||||
|
{
|
||||||
|
transformMode = TransformMode.SRT;
|
||||||
|
transform = transform.ToSRT();
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.Rotation = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Scale
|
||||||
|
{
|
||||||
|
get => transformMode == TransformMode.SRT ?
|
||||||
|
transform.Scale :
|
||||||
|
transform.ToSRT().Scale;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (transformMode != TransformMode.SRT)
|
||||||
|
{
|
||||||
|
transformMode = TransformMode.SRT;
|
||||||
|
transform = transform.ToSRT();
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.Scale = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Translation
|
||||||
|
{
|
||||||
|
get => transformMode == TransformMode.SRT ?
|
||||||
|
transform.Translation :
|
||||||
|
transform.ToSRT().Translation;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (transformMode != TransformMode.SRT)
|
||||||
|
{
|
||||||
|
transformMode = TransformMode.SRT;
|
||||||
|
transform = transform.ToSRT();
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.Translation = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfMesh? Mesh
|
||||||
|
{
|
||||||
|
get => mesh.IsSet ? mesh.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
mesh.Reset();
|
||||||
|
else
|
||||||
|
mesh.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public float[]? Weights => weights;
|
||||||
|
|
||||||
|
internal GltfNode(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
Children = new GltfRefList<GltfNode>(document.Nodes);
|
||||||
|
mesh = new GltfSmartId<GltfMesh>(document.Meshes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optimize this node for matrix mode transforms.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Self</returns>
|
||||||
|
public GltfNode MatrixMode()
|
||||||
|
{
|
||||||
|
if (transformMode == TransformMode.Matrix)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
transformMode = TransformMode.Matrix;
|
||||||
|
transform = transform.ToMatrix();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optimize this node for scale rotation translation transforms.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Self</returns>
|
||||||
|
public GltfNode SrtMode()
|
||||||
|
{
|
||||||
|
if (transformMode == TransformMode.SRT)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
transformMode = TransformMode.SRT;
|
||||||
|
transform = transform.ToSRT();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected node to be an object.");
|
||||||
|
|
||||||
|
if (element.TryGetProperty("name", out JsonElement nameProperty))
|
||||||
|
Name = nameProperty.ToString();
|
||||||
|
|
||||||
|
if (element.TryGetProperty("camera", out JsonElement cameraProperty))
|
||||||
|
{
|
||||||
|
if (!cameraProperty.TryGetInt32(out int cameraId))
|
||||||
|
throw new Exception("Expected camera id to be a number.");
|
||||||
|
|
||||||
|
camera.Id = cameraId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("skin", out JsonElement skinElement))
|
||||||
|
{
|
||||||
|
if (!skinElement.TryGetInt32(out int skinId))
|
||||||
|
throw new Exception("Expected skin id to be a number.");
|
||||||
|
|
||||||
|
skin.Id = skinId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("mesh", out JsonElement meshElement))
|
||||||
|
{
|
||||||
|
if (!meshElement.TryGetInt32(out int meshId))
|
||||||
|
throw new Exception("Expected mesh id to be a number.");
|
||||||
|
|
||||||
|
mesh.Id = meshId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("children", out JsonElement childrenElement))
|
||||||
|
{
|
||||||
|
if (childrenElement.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected a JSON array for children.");
|
||||||
|
|
||||||
|
foreach (JsonElement child in childrenElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (!child.TryGetInt32(out int id))
|
||||||
|
throw new Exception("Expected a number for child id.");
|
||||||
|
|
||||||
|
Children.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("matrix", out JsonElement matrixElement))
|
||||||
|
{
|
||||||
|
if (matrixElement.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected matrix to be an array.");
|
||||||
|
|
||||||
|
var enumerator = matrixElement.EnumerateArray();
|
||||||
|
|
||||||
|
transformMode = TransformMode.Matrix;
|
||||||
|
transform.Matrix = Matrix4x4.Identity;
|
||||||
|
|
||||||
|
for (int x = 0; x < 4; x++)
|
||||||
|
for (int y = 0; y < 4 && enumerator.MoveNext(); y++)
|
||||||
|
{
|
||||||
|
if (!enumerator.Current.TryGetSingle(out float v))
|
||||||
|
throw new Exception("Expected a floating point number as matrix elements.");
|
||||||
|
StoreMat(ref transform.Matrix, x, y, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (element.TryGetProperty("scale", out JsonElement scaleElement))
|
||||||
|
{
|
||||||
|
if (scaleElement.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected scale to be an array.");
|
||||||
|
|
||||||
|
transformMode = TransformMode.SRT;
|
||||||
|
Vector3 scale = default;
|
||||||
|
var enumerator = scaleElement.EnumerateArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < 3 && enumerator.MoveNext(); i++)
|
||||||
|
{
|
||||||
|
if (!enumerator.Current.TryGetSingle(out float v))
|
||||||
|
throw new Exception("Expected a floating point number as scale elements.");
|
||||||
|
StoreVec(ref scale, i, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("rotation", out JsonElement rotationElement))
|
||||||
|
{
|
||||||
|
if (scaleElement.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected rotation to be an array.");
|
||||||
|
|
||||||
|
transformMode = TransformMode.SRT;
|
||||||
|
Quaternion rotation = Quaternion.Identity;
|
||||||
|
var enumerator = scaleElement.EnumerateArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 && enumerator.MoveNext(); i++)
|
||||||
|
{
|
||||||
|
if (!enumerator.Current.TryGetSingle(out float v))
|
||||||
|
throw new Exception("Expected a floating point number as rotation elements.");
|
||||||
|
StoreQuat(ref rotation, i, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.TryGetProperty("translation", out JsonElement translationElement))
|
||||||
|
{
|
||||||
|
if (scaleElement.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected translation to be an array.");
|
||||||
|
|
||||||
|
transformMode = TransformMode.SRT;
|
||||||
|
Vector3 translation = default;
|
||||||
|
var enumerator = scaleElement.EnumerateArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < 3 && enumerator.MoveNext(); i++)
|
||||||
|
{
|
||||||
|
if (!enumerator.Current.TryGetSingle(out float v))
|
||||||
|
throw new Exception("Expected a floating point number as rotation elements.");
|
||||||
|
StoreVec(ref translation, i, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Translation = translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
|
||||||
|
static void StoreMat(ref Matrix4x4 m, int r, int c, float x)
|
||||||
|
{
|
||||||
|
// I love System.Numerics...
|
||||||
|
switch (r, c)
|
||||||
|
{
|
||||||
|
case (0, 0): m.M11 = x; break;
|
||||||
|
case (0, 1): m.M12 = x; break;
|
||||||
|
case (0, 2): m.M13 = x; break;
|
||||||
|
case (0, 3): m.M14 = x; break;
|
||||||
|
|
||||||
|
case (1, 0): m.M21 = x; break;
|
||||||
|
case (1, 1): m.M22 = x; break;
|
||||||
|
case (1, 2): m.M23 = x; break;
|
||||||
|
case (1, 3): m.M24 = x; break;
|
||||||
|
|
||||||
|
case (2, 0): m.M31 = x; break;
|
||||||
|
case (2, 1): m.M32 = x; break;
|
||||||
|
case (2, 2): m.M33 = x; break;
|
||||||
|
case (2, 3): m.M34 = x; break;
|
||||||
|
|
||||||
|
case (3, 0): m.M41 = x; break;
|
||||||
|
case (3, 1): m.M42 = x; break;
|
||||||
|
case (3, 2): m.M43 = x; break;
|
||||||
|
case (3, 3): m.M44 = x; break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StoreVec(ref Vector3 v, int r, float x)
|
||||||
|
{
|
||||||
|
switch (r)
|
||||||
|
{
|
||||||
|
case 0: v.X = x; break;
|
||||||
|
case 1: v.Y = x; break;
|
||||||
|
case 2: v.Z = x; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StoreQuat(ref Quaternion q, int r, float x)
|
||||||
|
{
|
||||||
|
switch (r)
|
||||||
|
{
|
||||||
|
case 0: q.X = x; break;
|
||||||
|
case 1: q.Y = x; break;
|
||||||
|
case 2: q.Z = x; break;
|
||||||
|
case 3: q.W = x; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
private struct TransformUnion
|
||||||
|
{
|
||||||
|
[FieldOffset(0 * sizeof(float))] public Matrix4x4 Matrix;
|
||||||
|
|
||||||
|
[FieldOffset(0 * sizeof(float))] public Vector3 Scale;
|
||||||
|
[FieldOffset(4 * sizeof(float))] public Quaternion Rotation;
|
||||||
|
[FieldOffset(8 * sizeof(float))] public Vector3 Translation;
|
||||||
|
|
||||||
|
public TransformUnion ToSRT()
|
||||||
|
{
|
||||||
|
Vector3 r0 = new Vector3(Matrix.M11, Matrix.M12, Matrix.M13);
|
||||||
|
Vector3 r1 = new Vector3(Matrix.M11, Matrix.M12, Matrix.M13);
|
||||||
|
Vector3 r2 = new Vector3(Matrix.M11, Matrix.M12, Matrix.M13);
|
||||||
|
|
||||||
|
float r0l = r0.Length();
|
||||||
|
float r1l = r1.Length();
|
||||||
|
float r2l = r2.Length();
|
||||||
|
|
||||||
|
Vector3 scale = new Vector3(r0l, r1l, r2l);
|
||||||
|
Vector3 translation = new Vector3(Matrix.M41, Matrix.M42, Matrix.M43);
|
||||||
|
|
||||||
|
// The following code segment has been adapted from OpenTK.Mathematics (MIT), which adapted from
|
||||||
|
// Blender (GPLv2 or more permissive).
|
||||||
|
// However, it is probably not copyrighted considering it is math.
|
||||||
|
|
||||||
|
r0 /= r0l;
|
||||||
|
r1 /= r1l;
|
||||||
|
r2 /= r2l;
|
||||||
|
|
||||||
|
Quaternion rotation = default;
|
||||||
|
float trace = 0.25f * (r0.X + r1.Y + r2.Z + 1f);
|
||||||
|
|
||||||
|
if (trace > 0)
|
||||||
|
{
|
||||||
|
float sq = MathF.Sqrt(trace);
|
||||||
|
|
||||||
|
rotation.W = sq;
|
||||||
|
sq = 1.0f / (4.0f * sq);
|
||||||
|
rotation.X = (r1.Z - r2.Y) * sq;
|
||||||
|
rotation.Y = (r2.X - r0.Z) * sq;
|
||||||
|
rotation.Z = (r0.Y - r1.X) * sq;
|
||||||
|
}
|
||||||
|
else if (r0.X > r1.Y && r0.X > r2.Z)
|
||||||
|
{
|
||||||
|
float sq = 2.0f * MathF.Sqrt(1.0f + r0.X - r1.Y - r2.Z);
|
||||||
|
|
||||||
|
rotation.X = 0.25f * sq;
|
||||||
|
sq = 1.0f / sq;
|
||||||
|
rotation.W = (r2.Y - r1.Z) * sq;
|
||||||
|
rotation.Y = (r1.X + r0.Y) * sq;
|
||||||
|
rotation.Z = (r2.X + r0.Z) * sq;
|
||||||
|
}
|
||||||
|
else if (r1.Y > r2.Z)
|
||||||
|
{
|
||||||
|
float sq = 2.0f * MathF.Sqrt(1.0f + r1.Y - r0.X - r2.Z);
|
||||||
|
|
||||||
|
rotation.Y = 0.25f * sq;
|
||||||
|
sq = 1.0f / sq;
|
||||||
|
rotation.W = (r2.X - r0.Z) * sq;
|
||||||
|
rotation.X = (r1.X + r0.Y) * sq;
|
||||||
|
rotation.Z = (r2.Y + r1.Z) * sq;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float sq = 2.0f * MathF.Sqrt(1.0f + r2.Z - r0.X - r1.Y);
|
||||||
|
|
||||||
|
rotation.Z = (float)(0.25 * sq);
|
||||||
|
sq = 1.0f / sq;
|
||||||
|
rotation.W = (float)((r1.X - r0.Y) * sq);
|
||||||
|
rotation.X = (float)((r2.X + r0.Z) * sq);
|
||||||
|
rotation.Y = (float)((r2.Y + r1.Z) * sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
rotation = Quaternion.Normalize(rotation);
|
||||||
|
|
||||||
|
return new TransformUnion() { Scale = scale, Rotation = rotation, Translation = translation };
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransformUnion ToMatrix()
|
||||||
|
{
|
||||||
|
Matrix4x4 matrix = Matrix4x4.CreateScale(Scale) *
|
||||||
|
Matrix4x4.CreateFromQuaternion(Rotation) *
|
||||||
|
Matrix4x4.CreateTranslation(Translation);
|
||||||
|
|
||||||
|
return new TransformUnion() { Matrix = matrix };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TransformMode
|
||||||
|
{
|
||||||
|
Matrix,
|
||||||
|
SRT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
ReFuel.Gltf/GltfObject.cs
Executable file
86
ReFuel.Gltf/GltfObject.cs
Executable file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfObjectKind
|
||||||
|
{
|
||||||
|
Document,
|
||||||
|
Asset,
|
||||||
|
Scene,
|
||||||
|
Node,
|
||||||
|
Camera,
|
||||||
|
Mesh,
|
||||||
|
Buffer,
|
||||||
|
BufferView,
|
||||||
|
Accessor,
|
||||||
|
Material,
|
||||||
|
Texture,
|
||||||
|
Image,
|
||||||
|
Sampler,
|
||||||
|
Skin,
|
||||||
|
Animation,
|
||||||
|
MeshPrimitive,
|
||||||
|
Misc,
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class GltfObject
|
||||||
|
{
|
||||||
|
public abstract GltfObjectKind Kind { get; }
|
||||||
|
public GltfDocument Document { get; protected set; }
|
||||||
|
public GltfCollection Extension { get; protected set; } = new GltfCollection();
|
||||||
|
public GltfCollection Extras { get; protected set; } = new GltfCollection();
|
||||||
|
|
||||||
|
protected GltfObject()
|
||||||
|
{
|
||||||
|
Unsafe.SkipInit(out GltfDocument doc);
|
||||||
|
Document = doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GltfObject(GltfDocument document) : this()
|
||||||
|
{
|
||||||
|
Document = document;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void DeserializeCommon(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.TryGetProperty("extras", out JsonElement extras))
|
||||||
|
{
|
||||||
|
Extras.Parse(extras);
|
||||||
|
}
|
||||||
|
if (element.TryGetProperty("extension", out JsonElement extension))
|
||||||
|
{
|
||||||
|
Extension.Parse(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract void Deserialize(JsonElement element);
|
||||||
|
internal abstract void Serialize(Utf8JsonWriter writer);
|
||||||
|
|
||||||
|
protected void AssertObject(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception($"Gltf document node '{Kind}' expects an object in the JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AssertArray(JsonElement element, string property)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception($"Gltf document '{property}' expects an array in the JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RequiredProperty(string name)
|
||||||
|
{
|
||||||
|
throw new Exception($"Gltf document node '{Kind}' requries property '{name}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void PropertyFormatFail(string name, string? message)
|
||||||
|
{
|
||||||
|
if (message != null)
|
||||||
|
throw new Exception($"Gltf document node '{Kind}', property '{name}' has invalid format: {message}");
|
||||||
|
else
|
||||||
|
throw new Exception($"Gltf document node '{Kind}', property '{name}' has invalid format.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
ReFuel.Gltf/GltfPrimitiveTopology.cs
Executable file
13
ReFuel.Gltf/GltfPrimitiveTopology.cs
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfPrimitiveTopology
|
||||||
|
{
|
||||||
|
Points = 0,
|
||||||
|
Lines = 1,
|
||||||
|
LineLoop = 2,
|
||||||
|
LineStrip = 3,
|
||||||
|
Triangles = 4,
|
||||||
|
TriangleStrip = 5,
|
||||||
|
TriangleFan = 6
|
||||||
|
}
|
||||||
|
}
|
93
ReFuel.Gltf/GltfSampler.cs
Normal file
93
ReFuel.Gltf/GltfSampler.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
internal enum GltfSamplerValue : int
|
||||||
|
{
|
||||||
|
Nearest = 9728,
|
||||||
|
Linear = 9729,
|
||||||
|
NearestMipmapNearest = 9984,
|
||||||
|
LinearMipmapNearest = 9985,
|
||||||
|
NearestMipmapLinear = 9986,
|
||||||
|
LinearMipmapLinear = 9987,
|
||||||
|
ClampToEdge = 33071,
|
||||||
|
MirroredRepeat = 33648,
|
||||||
|
Repeat = 10497,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GltfSamplerMagFilter
|
||||||
|
{
|
||||||
|
Unset = 0,
|
||||||
|
Nearest = GltfSamplerValue.Nearest,
|
||||||
|
Linear = GltfSamplerValue.Linear
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GltfSamplerMinFilter
|
||||||
|
{
|
||||||
|
Unset = 0,
|
||||||
|
Nearest = GltfSamplerValue.Nearest,
|
||||||
|
Linear = GltfSamplerValue.Linear,
|
||||||
|
NearestMipmapNearest = GltfSamplerValue.NearestMipmapNearest,
|
||||||
|
LinearMipmapNearest = GltfSamplerValue.LinearMipmapNearest,
|
||||||
|
NearestMipmapLinear = GltfSamplerValue.NearestMipmapLinear,
|
||||||
|
LinearMipmapLinear = GltfSamplerValue.LinearMipmapLinear,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GltfSamplerWrapMode
|
||||||
|
{
|
||||||
|
ClampToEdge = GltfSamplerValue.ClampToEdge,
|
||||||
|
MirroredRepeat = GltfSamplerValue.MirroredRepeat,
|
||||||
|
Repeat = GltfSamplerValue.Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GltfSampler : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Sampler;
|
||||||
|
|
||||||
|
public string? Name { get; set; } = null;
|
||||||
|
public GltfSamplerMinFilter MinFilter { get; set; } = GltfSamplerMinFilter.Unset;
|
||||||
|
public GltfSamplerMagFilter MagFilter { get; set; } = GltfSamplerMagFilter.Unset;
|
||||||
|
public GltfSamplerWrapMode WrapS { get; set; } = GltfSamplerWrapMode.Repeat;
|
||||||
|
public GltfSamplerWrapMode WrapT { get; set; } = GltfSamplerWrapMode.Repeat;
|
||||||
|
|
||||||
|
internal GltfSampler(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object as a sampler.");
|
||||||
|
|
||||||
|
foreach (JsonProperty property in element.EnumerateObject())
|
||||||
|
{
|
||||||
|
switch (property.Name)
|
||||||
|
{
|
||||||
|
case "name":
|
||||||
|
Name = property.Value.GetString();
|
||||||
|
break;
|
||||||
|
case "magFilter":
|
||||||
|
MagFilter = (GltfSamplerMagFilter)property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
case "minFilter":
|
||||||
|
MinFilter = (GltfSamplerMinFilter)property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
case "wrapS":
|
||||||
|
WrapS = (GltfSamplerWrapMode)property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
case "wrapT":
|
||||||
|
WrapT = (GltfSamplerWrapMode)property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
ReFuel.Gltf/GltfScene.cs
Executable file
49
ReFuel.Gltf/GltfScene.cs
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfScene : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Scene;
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public GltfRefList<GltfNode> Nodes { get; }
|
||||||
|
|
||||||
|
internal GltfScene(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
Nodes = new GltfRefList<GltfNode>(document.Nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object as an asset.");
|
||||||
|
|
||||||
|
JsonElement value;
|
||||||
|
|
||||||
|
if (element.TryGetProperty("name", out value))
|
||||||
|
Name = value.GetString();
|
||||||
|
|
||||||
|
if (element.TryGetProperty("nodes", out value))
|
||||||
|
{
|
||||||
|
if (value.ValueKind != JsonValueKind.Array)
|
||||||
|
throw new Exception("Expected nodes to be an integer array.");
|
||||||
|
|
||||||
|
foreach (JsonElement node in value.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (!node.TryGetInt32(out int id))
|
||||||
|
throw new Exception("Expected a node ID in nodes array.");
|
||||||
|
|
||||||
|
Nodes.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
ReFuel.Gltf/GltfSerializedExtensionProvider.cs
Executable file
45
ReFuel.Gltf/GltfSerializedExtensionProvider.cs
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfSerializedExtensionProvider<T> : IGltfExtensionProvider<T>
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public Type? DataType { get; } = typeof(T);
|
||||||
|
|
||||||
|
public GltfSerializedExtensionProvider(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
if (typeof(T).GetCustomAttribute<JsonSerializableAttribute>() is null)
|
||||||
|
throw new Exception("This type is not JSON serializable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public T ParseExtension(JsonElement element)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<T>(element) ?? throw new Exception("Could not deserialize extension object.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ParseExtensionObject(JsonElement element)
|
||||||
|
{
|
||||||
|
return ParseExtension(element)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteExtension(T value, Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteExtensionObject(object obj, Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
if (obj is T extensionType)
|
||||||
|
{
|
||||||
|
WriteExtension(extensionType, writer);
|
||||||
|
}
|
||||||
|
else throw new Exception($"Unexpected type of object {obj.GetType()}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
ReFuel.Gltf/GltfSkin.cs
Normal file
81
ReFuel.Gltf/GltfSkin.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfSkin : GltfIdObject
|
||||||
|
{
|
||||||
|
private GltfSmartId<GltfAccessor> _inverseBindMatrices = new GltfSmartId<GltfAccessor>();
|
||||||
|
private GltfSmartId<GltfNode> _skeleton = new GltfSmartId<GltfNode>();
|
||||||
|
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Skin;
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public GltfAccessor? InverseBindMatrices
|
||||||
|
{
|
||||||
|
get => _inverseBindMatrices.IsSet ? _inverseBindMatrices.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
_inverseBindMatrices.Reset();
|
||||||
|
else
|
||||||
|
_inverseBindMatrices.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public GltfNode? Skeleton
|
||||||
|
{
|
||||||
|
get => _skeleton.IsSet ? _skeleton.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
_skeleton.Reset();
|
||||||
|
else
|
||||||
|
_skeleton.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfRefList<GltfNode> Joints { get; }
|
||||||
|
|
||||||
|
internal GltfSkin(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
Joints = new GltfRefList<GltfNode>(document.Nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
AssertObject(element);
|
||||||
|
|
||||||
|
if (!element.TryGetProperty("joints", out JsonElement jointsElement))
|
||||||
|
RequiredProperty("joints");
|
||||||
|
AssertArray(jointsElement, "joints");
|
||||||
|
|
||||||
|
foreach (JsonElement joint in jointsElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
Joints.Add(joint.GetInt32());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (JsonProperty property in element.EnumerateObject())
|
||||||
|
{
|
||||||
|
switch (property.Name)
|
||||||
|
{
|
||||||
|
case "inverseBindMatrices":
|
||||||
|
_inverseBindMatrices.Id = property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
case "skeleton":
|
||||||
|
_skeleton.Id = property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
case "name":
|
||||||
|
Name = property.Value.GetString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
ReFuel.Gltf/GltfTargetHint.cs
Executable file
9
ReFuel.Gltf/GltfTargetHint.cs
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public enum GltfTargetHint
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
ArrayBuffer = 34962,
|
||||||
|
ElementArrayBuffer = 3463
|
||||||
|
}
|
||||||
|
}
|
74
ReFuel.Gltf/GltfTexture.cs
Normal file
74
ReFuel.Gltf/GltfTexture.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
public class GltfTexture : GltfIdObject
|
||||||
|
{
|
||||||
|
public override GltfObjectKind Kind => GltfObjectKind.Texture;
|
||||||
|
private GltfSmartId<GltfSampler> _sampler;
|
||||||
|
private GltfSmartId<GltfImage> _source;
|
||||||
|
|
||||||
|
public string? Name { get; set; } = null;
|
||||||
|
|
||||||
|
public GltfSampler? Sampler
|
||||||
|
{
|
||||||
|
get => _sampler.IsSet ? _sampler.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
_sampler.Reset();
|
||||||
|
else
|
||||||
|
_sampler.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GltfImage? Source
|
||||||
|
{
|
||||||
|
get => _source.IsSet ? _source.Value : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
_source.Reset();
|
||||||
|
else
|
||||||
|
_source.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal GltfTexture(GltfDocument document) : base(document)
|
||||||
|
{
|
||||||
|
_sampler = new GltfSmartId<GltfSampler>(document.Samplers);
|
||||||
|
_source = new GltfSmartId<GltfImage>(document.Images);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Deserialize(JsonElement element)
|
||||||
|
{
|
||||||
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
throw new Exception("Expected an object.");
|
||||||
|
|
||||||
|
foreach (JsonProperty property in element.EnumerateObject())
|
||||||
|
{
|
||||||
|
switch (property.Name)
|
||||||
|
{
|
||||||
|
case "name":
|
||||||
|
Name = property.Value.GetString();
|
||||||
|
break;
|
||||||
|
case "source":
|
||||||
|
_source.Id = property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
case "sampler":
|
||||||
|
_sampler.Id = property.Value.GetInt32();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializeCommon(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Serialize(Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
ReFuel.Gltf/IGltfExtensionProvider.cs
Executable file
20
ReFuel.Gltf/IGltfExtensionProvider.cs
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
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<T> : IGltfExtensionProvider
|
||||||
|
{
|
||||||
|
T ParseExtension(JsonElement element);
|
||||||
|
void WriteExtension(T value, Utf8JsonWriter writer);
|
||||||
|
}
|
||||||
|
}
|
18
ReFuel.Gltf/IGltfStreamProvider.cs
Normal file
18
ReFuel.Gltf/IGltfStreamProvider.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides users with special stream opening logic.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGltfStreamProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Open the file at the given URI.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pwd">The present working directory of the file.</param>
|
||||||
|
/// <param name="uri">The URI of the file to open</param>
|
||||||
|
public Stream? OpenFile(string? pwd, Uri uri);
|
||||||
|
}
|
||||||
|
}
|
62
ReFuel.Gltf/IO/GltfUriReader.cs
Executable file
62
ReFuel.Gltf/IO/GltfUriReader.cs
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
namespace ReFuel.Gltf.IO
|
||||||
|
{
|
||||||
|
public class GltfUriReader : Stream
|
||||||
|
{
|
||||||
|
public Uri Uri { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
public string Encoding { get; }
|
||||||
|
|
||||||
|
private string data;
|
||||||
|
private int index = 0;
|
||||||
|
|
||||||
|
public GltfUriReader(Uri uri)
|
||||||
|
{
|
||||||
|
if (uri.Scheme != "data")
|
||||||
|
throw new Exception("Expected a URI with 'data:' scheme.");
|
||||||
|
|
||||||
|
Uri = uri;
|
||||||
|
data = uri.AbsolutePath;
|
||||||
|
index = data.IndexOf(',') + 1;
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("No data string.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead => true;
|
||||||
|
|
||||||
|
public override bool CanSeek => true;
|
||||||
|
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
public override long Length => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
ReFuel.Gltf/IO/StreamWrapper.cs
Normal file
98
ReFuel.Gltf/IO/StreamWrapper.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
namespace ReFuel.Gltf.IO
|
||||||
|
{
|
||||||
|
public class StreamWrapper : Stream
|
||||||
|
{
|
||||||
|
private readonly Stream _proxy;
|
||||||
|
|
||||||
|
public Stream BaseStream { get; }
|
||||||
|
public bool KeepOpen { get; }
|
||||||
|
public long Offset { get; private init; } = 0;
|
||||||
|
public long Limit { get; private init; } = -1;
|
||||||
|
|
||||||
|
public override bool CanRead => _proxy.CanRead;
|
||||||
|
public override bool CanSeek => _proxy.CanSeek;
|
||||||
|
public override bool CanWrite => _proxy.CanWrite;
|
||||||
|
public override long Length => Limit < 0 ? _proxy.Length : Math.Min(Limit, Length - Offset);
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => ToRelOffset(_proxy.Position);
|
||||||
|
set => _proxy.Position = ToAbsOffset(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamWrapper(Stream baseStream, bool keepOpen = false)
|
||||||
|
{
|
||||||
|
BaseStream = baseStream;
|
||||||
|
KeepOpen = keepOpen;
|
||||||
|
|
||||||
|
_proxy = Synchronized(baseStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush() => _proxy.Flush();
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count) => _proxy.Read(buffer, offset, count);
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin) => _proxy.Seek(offset, origin);
|
||||||
|
|
||||||
|
public override void SetLength(long value) => _proxy.SetLength(value);
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count) => _proxy.Write(buffer, offset, count);
|
||||||
|
|
||||||
|
public StreamWrapper Fork(long offset = 0, long limit = -1, bool keepOpen = false)
|
||||||
|
{
|
||||||
|
if (limit < 0)
|
||||||
|
limit = Limit;
|
||||||
|
|
||||||
|
if (offset < 0 || offset > Length)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "Offset out of range.");
|
||||||
|
|
||||||
|
if (limit >= 0 && offset + limit > Length)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(limit), "Limit too large for the given offset.");
|
||||||
|
|
||||||
|
return ForkAbs(ToAbsOffset(offset), limit, keepOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamWrapper ForkAbs(long offset = 0, long limit = -1, bool keepOpen = false)
|
||||||
|
{
|
||||||
|
if (offset < 0 || offset > BaseStream.Length)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "Offset out of range.");
|
||||||
|
|
||||||
|
if (limit >= 0 && offset + limit > BaseStream.Length)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(limit), "Limit too large for the given offset.");
|
||||||
|
|
||||||
|
return new StreamWrapper(BaseStream, keepOpen)
|
||||||
|
{
|
||||||
|
Offset = offset,
|
||||||
|
Limit = limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private long ToAbsOffset(long offset)
|
||||||
|
{
|
||||||
|
if (offset < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), offset, "The relative offset cannot be less than zero.");
|
||||||
|
|
||||||
|
if (Limit >= 0 && offset > Limit)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), offset, "The relative offset cannot be greater than the limit.");
|
||||||
|
|
||||||
|
return offset + Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long ToRelOffset(long offset)
|
||||||
|
{
|
||||||
|
return offset - Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (!disposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_proxy.Dispose();
|
||||||
|
if (!KeepOpen)
|
||||||
|
BaseStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
ReFuel.Gltf/ReFuel.Gltf.csproj
Normal file
10
ReFuel.Gltf/ReFuel.Gltf.csproj
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<Version>0.0.1</Version>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
Loading…
x
Reference in New Issue
Block a user