// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using UnityEngine.Assertions;
using UnityEngine.Profiling;
namespace GLTFast.Schema
{
///
[Serializable]
public class Root : RootBase<
Accessor,
Animation,
Asset,
Buffer,
BufferView,
Camera,
RootExtensions,
Image,
Material,
Mesh,
Node,
Sampler,
Scene,
Skin,
Texture
>
{ }
///
/// Accessor type
/// Animation type
/// Asset type
/// Buffer type
/// BufferView type
/// Camera type
/// Extensions type
/// Image type
/// Material type
/// Mesh type
/// Node type
/// Sampler type
/// Scene type
/// Skin type
/// Texture type
[Serializable]
public abstract class RootBase<
TAccessor,
TAnimation,
TAsset,
TBuffer,
TBufferView,
TCamera,
TExtensions,
TImage,
TMaterial,
TMesh,
TNode,
TSampler,
TScene,
TSkin,
TTexture
> : RootBase
where TAccessor : AccessorBase
where TAnimation : AnimationBase
where TAsset : Asset
where TBuffer : Buffer
where TBufferView : BufferViewBase
where TCamera : CameraBase
where TExtensions : RootExtensions
where TImage : Image
where TMaterial : MaterialBase
where TMesh : MeshBase
where TNode : NodeBase
where TSampler : Sampler
where TScene : Scene
where TSkin : Skin
where TTexture : TextureBase
{
///
public TAccessor[] accessors;
#if UNITY_ANIMATION
///
public TAnimation[] animations;
#endif
///
public TAsset asset;
///
public TBuffer[] buffers;
///
public TBufferView[] bufferViews;
///
public TCamera[] cameras;
///
public TImage[] images;
///
public TMaterial[] materials;
///
public TNode[] nodes;
///
public TSampler[] samplers;
///
public TScene[] scenes;
///
public TSkin[] skins;
///
public TTexture[] textures;
///
public TExtensions extensions;
///
public TMesh[] meshes;
///
public override IReadOnlyList Accessors => accessors;
#if UNITY_ANIMATION
///
public override IReadOnlyList Animations => animations;
#endif
///
public override Asset Asset => asset;
///
public override IReadOnlyList Buffers => buffers;
///
public override IReadOnlyList BufferViews => bufferViews;
///
public override IReadOnlyList Cameras => cameras;
///
public override IReadOnlyList Images => images;
///
public override IReadOnlyList Materials => materials;
///
public override IReadOnlyList Nodes => nodes;
///
public override IReadOnlyList Samplers => samplers;
///
public override IReadOnlyList Scenes => scenes;
///
public override IReadOnlyList Skins => skins;
///
public override IReadOnlyList Textures => textures;
///
public override RootExtensions Extensions => extensions;
///
internal override void UnsetExtensions()
{
extensions = null;
}
///
public override IReadOnlyList Meshes => meshes;
}
///
/// The root object for a glTF asset.
///
///
[Serializable]
public abstract class RootBase
{
///
/// Names of glTF extensions used somewhere in this asset.
///
public string[] extensionsUsed;
///
/// Names of glTF extensions required to properly load this asset.
///
public string[] extensionsRequired;
///
/// An array of accessors. An accessor is a typed view into a bufferView.
///
public abstract IReadOnlyList Accessors { get; }
#if UNITY_ANIMATION
///
/// An array of keyframe animations.
///
public abstract IReadOnlyList Animations { get; }
#endif
///
/// Metadata about the glTF asset.
///
public abstract Asset Asset { get; }
///
/// An array of buffers. A buffer points to binary geometry, animation, or skins.
///
public abstract IReadOnlyList Buffers { get; }
///
/// An array of bufferViews.
/// A bufferView is a view into a buffer generally representing a subset of the buffer.
///
public abstract IReadOnlyList BufferViews { get; }
///
/// An array of cameras. A camera defines a projection matrix.
///
public abstract IReadOnlyList Cameras { get; }
///
/// An array of images. An image defines data used to create a texture.
///
public abstract IReadOnlyList Images { get; }
///
/// An array of materials. A material defines the appearance of a primitive.
///
public abstract IReadOnlyList Materials { get; }
///
/// An array of meshes. A mesh is a set of primitives to be rendered.
///
public abstract IReadOnlyList Meshes { get; }
///
/// An array of nodes.
///
public abstract IReadOnlyList Nodes { get; }
///
/// An array of samplers. A sampler contains properties for texture filtering and wrapping modes.
///
public abstract IReadOnlyList Samplers { get; }
///
/// The index of the default scene.
///
public int scene = -1;
///
/// An array of scenes.
///
public abstract IReadOnlyList Scenes { get; }
///
/// An array of skins. A skin is defined by joints and matrices.
///
public abstract IReadOnlyList Skins { get; }
///
/// An array of textures.
///
public abstract IReadOnlyList Textures { get; }
///
public abstract RootExtensions Extensions { get; }
///
/// Sets to null.
///
internal abstract void UnsetExtensions();
#if UNITY_ANIMATION
public bool HasAnimation => Animations != null && Animations.Count > 0;
#endif // UNITY_ANIMATION
///
/// Looks up if a certain accessor points to interleaved data.
///
/// Accessor index
/// True if accessor is interleaved, false if its data is
/// continuous.
public bool IsAccessorInterleaved(int accessorIndex)
{
var accessor = Accessors[accessorIndex];
var bufferView = BufferViews[accessor.bufferView];
if (bufferView.byteStride < 0) return false;
return bufferView.byteStride > accessor.ElementByteSize;
}
///
/// Serialization to JSON
///
/// Stream the JSON string is being written to.
public void GltfSerialize(StreamWriter stream)
{
var writer = new JsonWriter(stream);
if (Asset != null)
{
writer.AddProperty("asset");
Asset.GltfSerialize(writer);
}
if (Nodes != null)
{
writer.AddArray("nodes");
foreach (var node in Nodes)
{
node.GltfSerialize(writer);
}
writer.CloseArray();
}
if (extensionsRequired != null)
{
writer.AddArrayProperty("extensionsRequired", extensionsRequired);
}
if (extensionsUsed != null)
{
writer.AddArrayProperty("extensionsUsed", extensionsUsed);
}
#if UNITY_ANIMATION
if (Animations!=null) {
writer.AddArray("animations");
foreach( var animation in Animations) {
animation.GltfSerialize(writer);
}
writer.CloseArray();
}
#endif
if (Buffers != null)
{
writer.AddArray("buffers");
foreach (var buffer in Buffers)
{
buffer.GltfSerialize(writer);
}
writer.CloseArray();
}
if (BufferViews != null)
{
writer.AddArray("bufferViews");
foreach (var bufferView in BufferViews)
{
bufferView.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Accessors != null)
{
writer.AddArray("accessors");
foreach (var accessor in Accessors)
{
accessor.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Cameras != null)
{
writer.AddArray("cameras");
foreach (var camera in Cameras)
{
camera.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Images != null)
{
writer.AddArray("images");
foreach (var image in Images)
{
image?.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Materials != null)
{
writer.AddArray("materials");
foreach (var material in Materials)
{
material.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Meshes != null)
{
writer.AddArray("meshes");
foreach (var mesh in Meshes)
{
mesh.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Samplers != null)
{
writer.AddArray("samplers");
foreach (var sampler in Samplers)
{
sampler.GltfSerialize(writer);
}
writer.CloseArray();
}
if (scene >= 0)
{
writer.AddProperty("scene", scene);
}
if (Scenes != null)
{
writer.AddArray("scenes");
foreach (var sceneToSerialize in Scenes)
{
sceneToSerialize.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Skins != null)
{
writer.AddArray("skins");
foreach (var skin in Skins)
{
skin.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Textures != null)
{
writer.AddArray("textures");
foreach (var texture in Textures)
{
texture.GltfSerialize(writer);
}
writer.CloseArray();
}
if (Extensions != null)
{
writer.AddProperty("extensions");
Extensions.GltfSerialize(writer);
}
writer.Close();
}
///
/// Detects if a secondary null-check is necessary.
///
/// True if a secondary parse against the FakeSchema is required. False otherwise
internal bool JsonUtilitySecondParseRequired()
{
Profiler.BeginSample("JsonUtilitySecondParseRequired");
var check = false;
if (Materials != null)
{
foreach (var mat in Materials)
{
// mat.extension is always set (not null), because JsonUtility constructs a default
// if any of mat.extension's members is not null, it is because there was
// a legit extensions node in JSON => we have to check which ones
if (mat.Extensions.KHR_materials_unlit != null)
{
check = true;
}
else
{
// otherwise dump the wrongfully constructed MaterialExtension
mat.UnsetExtensions();
}
}
}
if (Accessors != null)
{
foreach (var accessor in Accessors)
{
if (accessor.Sparse.Indices == null || accessor.Sparse.Values == null)
{
// If indices and values members are null, `sparse` is likely
// an auto-instance by the JsonUtility and not present in JSON.
// Therefore we remove it:
accessor.UnsetSparse();
}
#if GLTFAST_SAFE
else {
// This is very likely a valid sparse accessor.
// However, an empty sparse property ( "sparse": {} ) would break
// glTFast, so better do a thorough follow-up check
check = true;
}
#endif // GLTFAST_SAFE
}
}
#if DRACO_IS_INSTALLED
if(!check && Meshes!=null) {
foreach (var mesh in Meshes) {
if (mesh.Primitives != null) {
foreach (var primitive in mesh.Primitives) {
if (primitive.Extensions?.KHR_draco_mesh_compression != null) {
check = true;
break;
}
}
}
}
}
#endif
Profiler.EndSample();
return check;
}
internal void JsonUtilityCleanupAgainstSecondParse(FakeSchema.Root fakeRoot)
{
Profiler.BeginSample("JsonUtilityCleanup");
if (Materials != null)
{
for (var i = 0; i < Materials.Count; i++)
{
var mat = Materials[i];
if (mat.Extensions == null) continue;
Assert.AreEqual(mat.name, fakeRoot.materials[i].name);
var fake = fakeRoot.materials[i].extensions;
if (fake.KHR_materials_unlit == null)
{
mat.Extensions.KHR_materials_unlit = null;
}
if (fake.KHR_materials_pbrSpecularGlossiness == null)
{
mat.Extensions.KHR_materials_pbrSpecularGlossiness = null;
}
if (fake.KHR_materials_transmission == null)
{
mat.Extensions.KHR_materials_transmission = null;
}
if (fake.KHR_materials_clearcoat == null)
{
mat.Extensions.KHR_materials_clearcoat = null;
}
if (fake.KHR_materials_sheen == null)
{
mat.Extensions.KHR_materials_sheen = null;
}
if (fake.KHR_materials_ior == null)
{
mat.Extensions.KHR_materials_ior = null;
}
if (fake.KHR_materials_specular == null)
{
mat.Extensions.KHR_materials_specular = null;
}
}
}
#if GLTFAST_SAFE
if (Accessors != null) {
for (var i = 0; i < Accessors.Count; i++) {
var sparse = fakeRoot.accessors[i].sparse;
if (sparse?.indices == null || sparse.values == null) {
Accessors[i].UnsetSparse();
}
}
}
#endif
if (Meshes != null)
{
for (var i = 0; i < Meshes.Count; i++)
{
var mesh = Meshes[i];
Assert.AreEqual(mesh.name, fakeRoot.meshes[i].name);
for (var j = 0; j < mesh.Primitives.Count; j++)
{
var primitive = mesh.Primitives[j];
if (primitive.Extensions == null) continue;
var fake = fakeRoot.meshes[i].primitives[j];
#if DRACO_IS_INSTALLED
if (fake.extensions.KHR_draco_mesh_compression == null) {
primitive.Extensions.KHR_draco_mesh_compression = null;
}
#endif
if (fake.extensions.KHR_materials_variants == null)
{
primitive.Extensions.KHR_materials_variants = null;
}
}
}
}
Profiler.EndSample();
}
///
/// Cleans up invalid parsing artifacts created by .
/// If you inherit a custom Root class (for use with
///
/// ) you can override this method to perform sanity checks on the deserialized, custom properties.
///
public virtual void JsonUtilityCleanup()
{
if (Nodes != null)
{
foreach (var t in Nodes)
{
t.JsonUtilityCleanup();
}
}
if (Extensions != null && !Extensions.JsonUtilityCleanup())
{
UnsetExtensions();
}
if (Textures != null)
{
foreach (var texture in Textures)
{
texture.JsonUtilityCleanup();
}
}
}
///
/// Number of materials variants.
///
///
public int MaterialsVariantsCount => Extensions?.KHR_materials_variants?.variants?.Count ?? 0;
///
/// Gets the name of a specific materials variant.
///
/// Materials variant index.
/// Name of a materials variant.
///
public string GetMaterialsVariantName(int index)
{
var variants = Extensions?.KHR_materials_variants?.variants;
if (variants != null && index >= 0 && index < variants.Count)
{
return variants[index].name;
}
return null;
}
}
}