Files
AR-Menu/Library/PackageCache/com.unity.cloud.gltfast@db5a82ec0b47/Runtime/Scripts/Schema/Root.cs
2025-11-30 08:35:03 +02:00

672 lines
22 KiB
C#

// 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
{
/// <inheritdoc />
[Serializable]
public class Root : RootBase<
Accessor,
Animation,
Asset,
Buffer,
BufferView,
Camera,
RootExtensions,
Image,
Material,
Mesh,
Node,
Sampler,
Scene,
Skin,
Texture
>
{ }
/// <inheritdoc />
/// <typeparam name="TAccessor">Accessor type</typeparam>
/// <typeparam name="TAnimation">Animation type</typeparam>
/// <typeparam name="TAsset">Asset type</typeparam>
/// <typeparam name="TBuffer">Buffer type</typeparam>
/// <typeparam name="TBufferView">BufferView type</typeparam>
/// <typeparam name="TCamera">Camera type</typeparam>
/// <typeparam name="TExtensions">Extensions type</typeparam>
/// <typeparam name="TImage">Image type</typeparam>
/// <typeparam name="TMaterial">Material type</typeparam>
/// <typeparam name="TMesh">Mesh type</typeparam>
/// <typeparam name="TNode">Node type</typeparam>
/// <typeparam name="TSampler">Sampler type</typeparam>
/// <typeparam name="TScene">Scene type</typeparam>
/// <typeparam name="TSkin">Skin type</typeparam>
/// <typeparam name="TTexture">Texture type</typeparam>
[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
{
/// <inheritdoc cref="Accessors"/>
public TAccessor[] accessors;
#if UNITY_ANIMATION
/// <inheritdoc cref="Animations"/>
public TAnimation[] animations;
#endif
/// <inheritdoc cref="Asset"/>
public TAsset asset;
/// <inheritdoc cref="Buffer"/>
public TBuffer[] buffers;
/// <inheritdoc cref="BufferView"/>
public TBufferView[] bufferViews;
/// <inheritdoc cref="Camera"/>
public TCamera[] cameras;
/// <inheritdoc cref="Image"/>
public TImage[] images;
/// <inheritdoc cref="Material"/>
public TMaterial[] materials;
/// <inheritdoc cref="Node"/>
public TNode[] nodes;
/// <inheritdoc cref="Sampler"/>
public TSampler[] samplers;
/// <inheritdoc cref="Scene"/>
public TScene[] scenes;
/// <inheritdoc cref="Skin"/>
public TSkin[] skins;
/// <inheritdoc cref="Texture"/>
public TTexture[] textures;
/// <inheritdoc cref="RootExtensions"/>
public TExtensions extensions;
/// <inheritdoc cref="Meshes"/>
public TMesh[] meshes;
/// <inheritdoc />
public override IReadOnlyList<AccessorBase> Accessors => accessors;
#if UNITY_ANIMATION
/// <inheritdoc />
public override IReadOnlyList<AnimationBase> Animations => animations;
#endif
/// <inheritdoc />
public override Asset Asset => asset;
/// <inheritdoc />
public override IReadOnlyList<Buffer> Buffers => buffers;
/// <inheritdoc />
public override IReadOnlyList<BufferViewBase> BufferViews => bufferViews;
/// <inheritdoc />
public override IReadOnlyList<CameraBase> Cameras => cameras;
/// <inheritdoc />
public override IReadOnlyList<Image> Images => images;
/// <inheritdoc />
public override IReadOnlyList<MaterialBase> Materials => materials;
/// <inheritdoc />
public override IReadOnlyList<NodeBase> Nodes => nodes;
/// <inheritdoc />
public override IReadOnlyList<Sampler> Samplers => samplers;
/// <inheritdoc />
public override IReadOnlyList<Scene> Scenes => scenes;
/// <inheritdoc />
public override IReadOnlyList<Skin> Skins => skins;
/// <inheritdoc />
public override IReadOnlyList<TextureBase> Textures => textures;
/// <inheritdoc />
public override RootExtensions Extensions => extensions;
/// <inheritdoc />
internal override void UnsetExtensions()
{
extensions = null;
}
/// <inheritdoc />
public override IReadOnlyList<MeshBase> Meshes => meshes;
}
/// <summary>
/// The root object for a glTF asset.
/// </summary>
/// <seealso href="https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-gltf"/>
[Serializable]
public abstract class RootBase
{
/// <summary>
/// Names of glTF extensions used somewhere in this asset.
/// </summary>
public string[] extensionsUsed;
/// <summary>
/// Names of glTF extensions required to properly load this asset.
/// </summary>
public string[] extensionsRequired;
/// <summary>
/// An array of accessors. An accessor is a typed view into a bufferView.
/// </summary>
public abstract IReadOnlyList<AccessorBase> Accessors { get; }
#if UNITY_ANIMATION
/// <summary>
/// An array of keyframe animations.
/// </summary>
public abstract IReadOnlyList<AnimationBase> Animations { get; }
#endif
/// <summary>
/// Metadata about the glTF asset.
/// </summary>
public abstract Asset Asset { get; }
/// <summary>
/// An array of buffers. A buffer points to binary geometry, animation, or skins.
/// </summary>
public abstract IReadOnlyList<Buffer> Buffers { get; }
/// <summary>
/// An array of bufferViews.
/// A bufferView is a view into a buffer generally representing a subset of the buffer.
/// </summary>
public abstract IReadOnlyList<BufferViewBase> BufferViews { get; }
/// <summary>
/// An array of cameras. A camera defines a projection matrix.
/// </summary>
public abstract IReadOnlyList<CameraBase> Cameras { get; }
/// <summary>
/// An array of images. An image defines data used to create a texture.
/// </summary>
public abstract IReadOnlyList<Image> Images { get; }
/// <summary>
/// An array of materials. A material defines the appearance of a primitive.
/// </summary>
public abstract IReadOnlyList<MaterialBase> Materials { get; }
/// <summary>
/// An array of meshes. A mesh is a set of primitives to be rendered.
/// </summary>
public abstract IReadOnlyList<MeshBase> Meshes { get; }
/// <summary>
/// An array of nodes.
/// </summary>
public abstract IReadOnlyList<NodeBase> Nodes { get; }
/// <summary>
/// An array of samplers. A sampler contains properties for texture filtering and wrapping modes.
/// </summary>
public abstract IReadOnlyList<Sampler> Samplers { get; }
/// <summary>
/// The index of the default scene.
/// </summary>
public int scene = -1;
/// <summary>
/// An array of scenes.
/// </summary>
public abstract IReadOnlyList<Scene> Scenes { get; }
/// <summary>
/// An array of skins. A skin is defined by joints and matrices.
/// </summary>
public abstract IReadOnlyList<Skin> Skins { get; }
/// <summary>
/// An array of textures.
/// </summary>
public abstract IReadOnlyList<TextureBase> Textures { get; }
/// <inheritdoc cref="RootExtensions"/>
public abstract RootExtensions Extensions { get; }
/// <summary>
/// Sets <see cref="Extensions"/> to null.
/// </summary>
internal abstract void UnsetExtensions();
#if UNITY_ANIMATION
public bool HasAnimation => Animations != null && Animations.Count > 0;
#endif // UNITY_ANIMATION
/// <summary>
/// Looks up if a certain accessor points to interleaved data.
/// </summary>
/// <param name="accessorIndex">Accessor index</param>
/// <returns>True if accessor is interleaved, false if its data is
/// continuous.</returns>
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;
}
/// <summary>
/// Serialization to JSON
/// </summary>
/// <param name="stream">Stream the JSON string is being written to.</param>
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();
}
/// <summary>
/// Detects if a secondary null-check is necessary.
/// </summary>
/// <returns>True if a secondary parse against the FakeSchema is required. False otherwise</returns>
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();
}
/// <summary>
/// Cleans up invalid parsing artifacts created by <see cref="GltfJsonUtilityParser"/>.
/// If you inherit a custom Root class (for use with
/// <see cref="GltfImport.LoadWithCustomSchema&lt;T&gt;(string,ImportSettings,System.Threading.CancellationToken)"/>
/// ) you can override this method to perform sanity checks on the deserialized, custom properties.
/// </summary>
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();
}
}
}
/// <summary>
/// Number of materials variants.
/// </summary>
/// <seealso href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_variants"/>
public int MaterialsVariantsCount => Extensions?.KHR_materials_variants?.variants?.Count ?? 0;
/// <summary>
/// Gets the name of a specific materials variant.
/// </summary>
/// <param name="index">Materials variant index.</param>
/// <returns>Name of a materials variant.</returns>
/// <seealso href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_variants"/>
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;
}
}
}