Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement KHR_materials_variants gltf extension #8

Merged
merged 2 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 298 additions & 0 deletions src/ThreeDModels/Format/Gltf/Extensions/KHR_materials_variants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
using System.Text.Json;
using ThreeDModels.Format.Gltf.Elements;
using ThreeDModels.Format.Gltf.IO;
using static ThreeDModels.Format.Gltf.IO.Utf8JsonReaderHelpers;

namespace ThreeDModels.Format.Gltf.Extensions;

public class KHR_materials_variants : IGltfProperty
{
/// <summary>
/// A list of objects defining a valid material variant.
/// </summary>
public required List<KhrMaterialsVariantsVariant> Variants { get; set; }
public Dictionary<string, object?>? Extensions { get; set; }
public object? Extras { get; set; }
}

/// <summary>
/// Represents an object defining a valid material variant.
/// </summary>
public class KhrMaterialsVariantsVariant : IGltfRootProperty
{
/// <summary>
/// The name of the material variant.
/// </summary>
public string? Name { get; set; }
public Dictionary<string, object?>? Extensions { get; set; }
public object? Extras { get; set; }
}

public class KHR_materials_variants_mesh_primitive : IGltfProperty
{
/// <summary>
/// A list of material to variant mappings.
/// </summary>
public required List<KhrMaterialsVariantsMapping>? Mappings { get; set; }
public Dictionary<string, object?>? Extensions { get; set; }
public object? Extras { get; set; }
}

/// <summary>
/// Represents a mapping of an indexed material to a set of variants.
/// </summary>
public class KhrMaterialsVariantsMapping : IGltfProperty
{
/// <summary>
/// A list of variant index values.
/// </summary>
public required List<int> Variants { get; set; }
/// <summary>
/// The material associated with the set of variants.
/// </summary>
public required int Material { get; set; }
/// <summary>
/// The user-defined name of this variant material mapping.
/// </summary>
public string? Name { get; set; }
public Dictionary<string, object?>? Extensions { get; set; }
public object? Extras { get; set; }
}

public class KhrMaterialsVariantsExtension : IGltfExtension
{
public string Name => nameof(KHR_materials_variants);

public object? Read(ref Utf8JsonReader jsonReader, GltfReaderContext context, Type parentType)
{
if (parentType != typeof(Gltf))
{
List<KhrMaterialsVariantsVariant>? variants = null;
Dictionary<string, object?>? extensions = null;
object? extras = null;
if (jsonReader.TokenType == JsonTokenType.PropertyName && jsonReader.Read())
{
}
if (jsonReader.TokenType == JsonTokenType.Null)
{
return null;
}
else if (jsonReader.TokenType != JsonTokenType.StartObject)
{
throw new InvalidDataException("Failed to find start of property.");
}
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonTokenType.EndObject)
{
break;
}
var propertyName = jsonReader.GetString();
if (propertyName == nameof(variants))
{
variants = ReadObjectList(ref jsonReader, context, (ref Utf8JsonReader reader, GltfReaderContext ctx) => KhrMaterialsVariantsVariantSerialization.Read(ref reader, ctx)!);
}
else if (propertyName == nameof(extensions))
{
extensions = ExtensionsSerialization.Read<KHR_draco_mesh_compression>(ref jsonReader, context);
}
else if (propertyName == nameof(extras))
{
extras = ExtrasSerialization.Read(ref jsonReader, context);
}
else
{
throw new InvalidDataException($"Unknown property: {propertyName}");
}
}
if (variants == null || variants.Count < 1)
{
throw new InvalidDataException("KHR_materials_variants.variants must have at least one variant.");
}
return new KHR_materials_variants()
{
Variants = variants!,
Extensions = extensions,
Extras = extras,
};
}
else if (parentType == typeof(MeshPrimitive))
{
List<KhrMaterialsVariantsMapping>? mappings = null;
Dictionary<string, object?>? extensions = null;
object? extras = null;
if (jsonReader.TokenType == JsonTokenType.PropertyName && jsonReader.Read())
{
}
if (jsonReader.TokenType == JsonTokenType.Null)
{
return null;
}
else if (jsonReader.TokenType != JsonTokenType.StartObject)
{
throw new InvalidDataException("Failed to find start of property.");
}
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonTokenType.EndObject)
{
break;
}
var propertyName = jsonReader.GetString();
if (propertyName == nameof(mappings))
{
mappings = ReadObjectList(ref jsonReader, context, (ref Utf8JsonReader reader, GltfReaderContext ctx) => KhrMaterialsVariantsMappingSerialization.Read(ref reader, ctx)!);
}
else if (propertyName == nameof(extensions))
{
extensions = ExtensionsSerialization.Read<KHR_draco_mesh_compression>(ref jsonReader, context);
}
else if (propertyName == nameof(extras))
{
extras = ExtrasSerialization.Read(ref jsonReader, context);
}
else
{
throw new InvalidDataException($"Unknown property: {propertyName}");
}
}
if (mappings == null || mappings.Count < 1)
{
throw new InvalidDataException("KHR_materials_variants.mappings must have at least 1 mapping.");
}
return new KHR_materials_variants_mesh_primitive()
{
Mappings = mappings!,
Extensions = extensions,
Extras = extras,
};
}
throw new InvalidDataException("KHR_materials_variants must be used in a either the Gltf root or a MeshPrimitive.");
}
}

public class KhrMaterialsVariantsVariantSerialization
{
public static KhrMaterialsVariantsVariant? Read(ref Utf8JsonReader jsonReader, GltfReaderContext context)
{
string? name = null;
Dictionary<string, object?>? extensions = null;
object? extras = null;
if (jsonReader.TokenType == JsonTokenType.PropertyName && jsonReader.Read())
{
}
if (jsonReader.TokenType == JsonTokenType.Null)
{
return null;
}
else if (jsonReader.TokenType != JsonTokenType.StartObject)
{
throw new InvalidDataException("Failed to find start of property.");
}
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonTokenType.EndObject)
{
break;
}
var propertyName = jsonReader.GetString();
if (propertyName == nameof(name))
{
name = ReadString(ref jsonReader);
}
else if (propertyName == nameof(extensions))
{
extensions = ExtensionsSerialization.Read<AccessorSparse>(ref jsonReader, context);
}
else if (propertyName == nameof(extras))
{
extras = ExtrasSerialization.Read(ref jsonReader, context);
}
else
{
throw new InvalidDataException($"Unknown property: {propertyName}");
}
}
if (name == null)
{
throw new InvalidDataException("KhrMaterialsVariantsVariant.name is a required property.");
}
return new()
{
Name = name!,
Extensions = extensions,
Extras = extras,
};
}
}

public class KhrMaterialsVariantsMappingSerialization
{
public static KhrMaterialsVariantsMapping? Read(ref Utf8JsonReader jsonReader, GltfReaderContext context)
{
List<int>? variants = null;
int? material = null;
string? name = null;
Dictionary<string, object?>? extensions = null;
object? extras = null;
if (jsonReader.TokenType == JsonTokenType.PropertyName && jsonReader.Read())
{
}
if (jsonReader.TokenType == JsonTokenType.Null)
{
return null;
}
else if (jsonReader.TokenType != JsonTokenType.StartObject)
{
throw new InvalidDataException("Failed to find start of property.");
}
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonTokenType.EndObject)
{
break;
}
var propertyName = jsonReader.GetString();
if (propertyName == nameof(variants))
{
variants = ReadIntegerList(ref jsonReader, context);
}
else if (propertyName == nameof(material))
{
material = ReadInteger(ref jsonReader);
}
else if (propertyName == nameof(name))
{
name = ReadString(ref jsonReader);
}
else if (propertyName == nameof(extensions))
{
extensions = ExtensionsSerialization.Read<AccessorSparse>(ref jsonReader, context);
}
else if (propertyName == nameof(extras))
{
extras = ExtrasSerialization.Read(ref jsonReader, context);
}
else
{
throw new InvalidDataException($"Unknown property: {propertyName}");
}
}
if (variants == null || variants.Count < 1)
{
throw new InvalidDataException("KHR_materials_variants.mappings[i].variants must have at least one variant.");
}
if (material == null)
{
throw new InvalidDataException("KHR_materials_variants.mappings[i].material is a required property.");
}
return new()
{
Variants = variants!,
Material = (int)material!,
Name = name,
Extensions = extensions,
Extras = extras,
};
}
}
5 changes: 5 additions & 0 deletions src/ThreeDModels/Format/Gltf/IO/Utf8JsonReaderHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,9 @@ public static class Utf8JsonReaderHelpers
{
return ReadList(ref jsonReader, context, JsonTokenType.Number, (ref Utf8JsonReader reader, GltfReaderContext _) => (float)ReadFloat(ref reader)!);
}

public static List<T>? ReadObjectList<T>(ref Utf8JsonReader jsonReader, GltfReaderContext context, ArrayElementReader<T> elementReader)
{
return ReadList(ref jsonReader, context, JsonTokenType.StartObject, elementReader);
}
}
Loading