This commit is contained in:
2023-11-28 11:38:59 +05:30
commit ce059d4bf6
2742 changed files with 618089 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,122 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using UnityEngine;
// This is a modified version of the script from: https://jacksondunstan.com/articles/3568
/// <summary> Much faster than BinaryReader </summary>
public class BufferedBinaryReader : IDisposable {
private readonly Stream stream;
private readonly byte[] buffer;
private readonly int bufferSize;
private int bufferOffset;
private int bufferedBytes;
private int byteStride;
private Bit2Converter bit2Converter;
private Bit4Converter bit4Converter;
public long Position { get { return stream.Position + bufferOffset; } set { stream.Position = value; bufferedBytes = 0; bufferOffset = 0; } }
public BufferedBinaryReader(Stream stream, int bufferSize) {
this.stream = stream;
this.bufferSize = bufferSize;
buffer = new byte[bufferSize];
bufferOffset = 0;
bufferedBytes = 0;
}
private void FillBuffer(int byteCount) {
int unreadBytes = bufferedBytes - bufferOffset;
if (unreadBytes < byteCount) {
// If not enough bytes left in buffer
if (unreadBytes != 0) {
// If buffer still has unread bytes, move them to start of buffer
Buffer.BlockCopy(buffer, bufferOffset, buffer, 0, unreadBytes);
}
bufferedBytes = stream.Read(buffer, unreadBytes, bufferSize - unreadBytes) + unreadBytes;
bufferOffset = 0;
}
}
public byte ReadByte() {
FillBuffer(1);
return buffer[bufferOffset++];
}
public sbyte ReadSByte() {
FillBuffer(1);
return (sbyte) buffer[bufferOffset++];
}
public ushort ReadUInt16() {
FillBuffer(sizeof(ushort));
return bit2Converter.Read(buffer, ref bufferOffset).@ushort;
}
public short ReadInt16() {
FillBuffer(sizeof(short));
return bit2Converter.Read(buffer, ref bufferOffset).@short;
}
public uint ReadUInt32() {
FillBuffer(sizeof(uint));
return bit4Converter.Read(buffer, ref bufferOffset).@uint;
}
public int ReadInt32() {
FillBuffer(sizeof(int));
return bit4Converter.Read(buffer, ref bufferOffset).@int;
}
public float ReadSingle() {
FillBuffer(sizeof(float));
return bit4Converter.Read(buffer, ref bufferOffset).@float;
}
public void Skip(int bytes) {
FillBuffer(bytes);
bufferOffset += bytes;
}
[StructLayout(LayoutKind.Explicit)]
public struct Bit2Converter {
[FieldOffset(0)] public byte b0;
[FieldOffset(1)] public byte b1;
[FieldOffset(0)] public short @short;
[FieldOffset(0)] public ushort @ushort;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Bit2Converter Read(byte[] buffer, ref int bufferOffset) {
b0 = buffer[bufferOffset++];
b1 = buffer[bufferOffset++];
return this;
}
}
[StructLayout(LayoutKind.Explicit)]
public struct Bit4Converter {
[FieldOffset(0)] public byte b0;
[FieldOffset(1)] public byte b1;
[FieldOffset(2)] public byte b2;
[FieldOffset(3)] public byte b3;
[FieldOffset(0)] public float @float;
[FieldOffset(0)] public int @int;
[FieldOffset(0)] public uint @uint;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Bit4Converter Read(byte[] buffer, ref int bufferOffset) {
b0 = buffer[bufferOffset++];
b1 = buffer[bufferOffset++];
b2 = buffer[bufferOffset++];
b3 = buffer[bufferOffset++];
return this;
}
}
public void Dispose() {
stream.Close();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c0ec98d1cbd7324cbe265d9819a7b11
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6f0a1eaa625f4834c98391d4025b2fa2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility.Converters {
/// <summary> Converts from float array to Color during deserialization, and back </summary>
[Preserve] public class ColorRGBConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
Color c = (Color) value;
writer.WriteStartArray();
writer.WriteValue(c.r);
writer.WriteValue(c.g);
writer.WriteValue(c.b);
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
float[] a = serializer.Deserialize<float[]>(reader);
return new Color(a[0], a[1], a[2]);
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Color);
}
}
[Preserve] public class ColorRGBAConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
Color c = (Color) value;
writer.WriteStartArray();
writer.WriteValue(c.r);
writer.WriteValue(c.g);
writer.WriteValue(c.b);
writer.WriteValue(c.a);
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
float[] a = serializer.Deserialize<float[]>(reader);
return new Color(a[0], a[1], a[2], a[3]);
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Color);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6532df4837460e147922cbb51088800d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using System;
using Newtonsoft.Json;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility.Converters {
/// <summary> Converts from string to enum during deserialization, and back </summary>
[Preserve] public class EnumConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
string value = serializer.Deserialize<string>(reader);
if (Enum.IsDefined(objectType, value)) return Enum.Parse(objectType, value);
else return existingValue;
}
public override bool CanConvert(Type objectType) {
return typeof(Enum).IsAssignableFrom(objectType);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2d1df10b03ea5f43acd4f34d6150bf9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility.Converters {
/// <summary> Converts from float array to Matrix4x4 during deserialization, and back </summary>
[Preserve] public class Matrix4x4Converter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
Matrix4x4 m = (Matrix4x4) value;
writer.WriteStartArray();
for (int i = 0; i < 16; i++) {
writer.WriteValue(m[i]);
}
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
float[] floatArray = serializer.Deserialize<float[]>(reader);
return new Matrix4x4(
new Vector4(floatArray[0], floatArray[1], floatArray[2], floatArray[3]),
new Vector4(floatArray[4], floatArray[5], floatArray[6], floatArray[7]),
new Vector4(floatArray[8], floatArray[9], floatArray[10], floatArray[11]),
new Vector4(floatArray[12], floatArray[13], floatArray[14], floatArray[15])
);;
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Matrix4x4);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 948a6430ddecdb3409e998ba6e9a3704
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility.Converters {
/// <summary>
/// Converts from float array to Quaternion during deserialization, and back.
/// Compensates for differing coordinate systems as well.
/// </summary>
[Preserve] public class QuaternionConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
Quaternion q = (Quaternion) value;
writer.WriteStartArray();
writer.WriteValue(q.x);
writer.WriteValue(-q.y);
writer.WriteValue(-q.z);
writer.WriteValue(q.w);
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
float[] floatArray = serializer.Deserialize<float[]>(reader);
return new Quaternion(floatArray[0], -floatArray[1], -floatArray[2], floatArray[3]);
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Quaternion);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2cbe765fb9857a44696eb441e0e0bb25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility.Converters {
/// <summary>
/// Converts from float array to Vector3 during deserialization, and back.
/// Compensates for differing coordinate systems as well.
/// </summary>
[Preserve] public class TranslationConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
Vector3 pos = (Vector3) value;
writer.WriteStartArray();
writer.WriteValue(-pos.x);
writer.WriteValue(pos.y);
writer.WriteValue(pos.z);
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
float[] floatArray = serializer.Deserialize<float[]>(reader);
return new Vector3(-floatArray[0], floatArray[1], floatArray[2]);
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Vector3);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7c0b75c128b76e2488f9284096ada72d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility.Converters {
/// <summary> Converts from float array to Vector2 during deserialization, and back </summary>
[Preserve] public class Vector2Converter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
Vector2 pos = (Vector2) value;
writer.WriteStartArray();
writer.WriteValue(pos.x);
writer.WriteValue(pos.y);
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
float[] floatArray = null;
try {
floatArray = serializer.Deserialize<float[]>(reader);
} catch (System.Exception) {
floatArray = new float[] { serializer.Deserialize<float>(reader) };
}
switch (floatArray.Length) {
case 1:
return new Vector2(floatArray[0], floatArray[0]); // just copy float
case 2:
return new Vector2(floatArray[0], floatArray[1]);
case 3:
return new Vector2(floatArray[0], floatArray[1]); // we dont need third float
default:
return new Vector2(1, 1);
}
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Vector2);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3270bccdd3fef254d9cb10fd0fc8bf51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility.Converters {
/// <summary> Converts from float array to Vector3 during deserialization, and back </summary>
[Preserve] public class Vector3Converter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
Vector3 pos = (Vector3) value;
writer.WriteStartArray();
writer.WriteValue(pos.x);
writer.WriteValue(pos.y);
writer.WriteValue(pos.z);
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
float[] floatArray = serializer.Deserialize<float[]>(reader);
return new Vector3(floatArray[0], floatArray[1], floatArray[2]);
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(Vector3);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27048007fcb96f94f9c0af0877a49845
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 46478e359a7c2694db7532f3a07945f0
folderAsset: yes
timeCreated: 1538919615
licenseType: Free
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
#if HAVE_GLTFAST || HAVE_UNITYGLTF
#define ANOTHER_IMPORTER_HAS_HIGHER_PRIORITY
#endif
#if !ANOTHER_IMPORTER_HAS_HIGHER_PRIORITY && !GLTFUTILITY_FORCE_DEFAULT_IMPORTER_OFF
#define ENABLE_DEFAULT_GLB_IMPORTER
#endif
#if GLTFUTILITY_FORCE_DEFAULT_IMPORTER_ON
#define ENABLE_DEFAULT_GLB_IMPORTER
#endif
using UnityEngine;
#if !UNITY_2020_2_OR_NEWER
using UnityEditor.Experimental.AssetImporters;
#else
using UnityEditor.AssetImporters;
#endif
namespace Siccity.GLTFUtility {
#if ENABLE_DEFAULT_GLB_IMPORTER
[ScriptedImporter(1, "glb")]
#else
[ScriptedImporter(2, null, overrideExts: new[] { "glb" })]
#endif
public class GLBImporter : GLTFImporter {
public override void OnImportAsset(AssetImportContext ctx) {
// Load asset
AnimationClip[] animations;
if (importSettings == null) importSettings = new ImportSettings();
GameObject root = Importer.LoadFromFile(ctx.assetPath, importSettings, out animations, Format.GLB);
// Save asset
GLTFAssetUtility.SaveToAsset(root, animations, ctx, importSettings);
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 768a5a18fd6eb914f980bf85dd7b34ec
timeCreated: 1538919678
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,133 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
#if !UNITY_2020_2_OR_NEWER
using UnityEditor.Experimental.AssetImporters;
#else
using UnityEditor.AssetImporters;
#endif
using UnityEngine;
namespace Siccity.GLTFUtility {
/// <summary> Contains methods for saving a gameobject as an asset </summary>
public static class GLTFAssetUtility {
public static void SaveToAsset(GameObject root, AnimationClip[] animations, AssetImportContext ctx, ImportSettings settings) {
#if UNITY_2018_2_OR_NEWER
ctx.AddObjectToAsset("main", root);
ctx.SetMainObject(root);
#else
ctx.SetMainAsset("main obj", root);
#endif
UnwrapParam? unwrapParams = new UnwrapParam()
{
angleError = settings.angleError,
areaError = settings.areaError,
hardAngle = settings.hardAngle,
packMargin = settings.packMargin
};
MeshRenderer[] renderers = root.GetComponentsInChildren<MeshRenderer>(true);
SkinnedMeshRenderer[] skinnedRenderers = root.GetComponentsInChildren<SkinnedMeshRenderer>(true);
MeshFilter[] filters = root.GetComponentsInChildren<MeshFilter>(true);
AddMeshes(filters, skinnedRenderers, ctx, settings.generateLightmapUVs ? unwrapParams : null);
AddMaterials(renderers, skinnedRenderers, ctx);
AddAnimations(animations, ctx, settings.animationSettings);
}
public static void AddMeshes(MeshFilter[] filters, SkinnedMeshRenderer[] skinnedRenderers, AssetImportContext ctx, UnwrapParam? lightmapUnwrapInfo) {
HashSet<Mesh> visitedMeshes = new HashSet<Mesh>();
for (int i = 0; i < filters.Length; i++) {
Mesh mesh = filters[i].sharedMesh;
if (lightmapUnwrapInfo.HasValue) Unwrapping.GenerateSecondaryUVSet(mesh, lightmapUnwrapInfo.Value);
if (visitedMeshes.Contains(mesh)) continue;
ctx.AddAsset(mesh.name, mesh);
visitedMeshes.Add(mesh);
}
for (int i = 0; i < skinnedRenderers.Length; i++) {
Mesh mesh = skinnedRenderers[i].sharedMesh;
if (visitedMeshes.Contains(mesh)) continue;
ctx.AddAsset(mesh.name, mesh);
visitedMeshes.Add(mesh);
}
}
public static void AddAnimations(AnimationClip[] animations, AssetImportContext ctx, AnimationSettings settings) {
if (animations == null) return;
// Editor-only animation settings
foreach (AnimationClip clip in animations) {
AnimationClipSettings clipSettings = AnimationUtility.GetAnimationClipSettings(clip);
clipSettings.loopTime = settings.looping;
AnimationUtility.SetAnimationClipSettings(clip, clipSettings);
}
HashSet<AnimationClip> visitedAnimations = new HashSet<AnimationClip>();
for (int i = 0; i < animations.Length; i++) {
AnimationClip clip = animations[i];
if (visitedAnimations.Contains(clip)) continue;
ctx.AddAsset(clip.name, clip);
visitedAnimations.Add(clip);
}
}
public static void AddMaterials(MeshRenderer[] renderers, SkinnedMeshRenderer[] skinnedRenderers, AssetImportContext ctx) {
HashSet<Material> visitedMaterials = new HashSet<Material>();
HashSet<Texture2D> visitedTextures = new HashSet<Texture2D>();
for (int i = 0; i < renderers.Length; i++) {
foreach (Material mat in renderers[i].sharedMaterials) {
if (mat == GLTFMaterial.defaultMaterial) continue;
if (visitedMaterials.Contains(mat)) continue;
if (string.IsNullOrEmpty(mat.name)) mat.name = "material" + visitedMaterials.Count;
ctx.AddAsset(mat.name, mat);
visitedMaterials.Add(mat);
// Add textures
foreach (Texture2D tex in mat.AllTextures()) {
// Dont add asset textures
//if (images[i].isAsset) continue;
if (visitedTextures.Contains(tex)) continue;
if (AssetDatabase.Contains(tex)) continue;
if (string.IsNullOrEmpty(tex.name)) tex.name = "texture" + visitedTextures.Count;
ctx.AddAsset(tex.name, tex);
visitedTextures.Add(tex);
}
}
}
for (int i = 0; i < skinnedRenderers.Length; i++) {
foreach (Material mat in skinnedRenderers[i].sharedMaterials) {
if (visitedMaterials.Contains(mat)) continue;
if (string.IsNullOrEmpty(mat.name)) mat.name = "material" + visitedMaterials.Count;
ctx.AddAsset(mat.name, mat);
visitedMaterials.Add(mat);
// Add textures
foreach (Texture2D tex in mat.AllTextures()) {
// Dont add asset textures
//if (images[i].isAsset) continue;
if (visitedTextures.Contains(tex)) continue;
if (AssetDatabase.Contains(tex)) continue;
if (string.IsNullOrEmpty(tex.name)) tex.name = "texture" + visitedTextures.Count;
ctx.AddAsset(tex.name, tex);
visitedTextures.Add(tex);
}
}
}
}
public static void AddAsset(this AssetImportContext ctx, string identifier, Object obj) {
#if UNITY_2018_2_OR_NEWER
ctx.AddObjectToAsset(identifier, obj);
#else
ctx.AddSubAsset(identifier, obj);
#endif
}
public static IEnumerable<Texture2D> AllTextures(this Material mat) {
int[] ids = mat.GetTexturePropertyNameIDs();
for (int i = 0; i < ids.Length; i++) {
Texture2D tex = mat.GetTexture(ids[i]) as Texture2D;
if (tex != null) yield return tex;
}
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: abda7048a5d68e74281dc3833f5a7a22
timeCreated: 1538919678
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
#if HAVE_GLTFAST || HAVE_UNITYGLTF
#define ANOTHER_IMPORTER_HAS_HIGHER_PRIORITY
#endif
#if !ANOTHER_IMPORTER_HAS_HIGHER_PRIORITY && !GLTFUTILITY_FORCE_DEFAULT_IMPORTER_OFF
#define ENABLE_DEFAULT_GLB_IMPORTER
#endif
#if GLTFUTILITY_FORCE_DEFAULT_IMPORTER_ON
#define ENABLE_DEFAULT_GLB_IMPORTER
#endif
#if !UNITY_2020_2_OR_NEWER
using UnityEditor.Experimental.AssetImporters;
#else
using UnityEditor.AssetImporters;
#endif
using UnityEngine;
namespace Siccity.GLTFUtility {
#if ENABLE_DEFAULT_GLB_IMPORTER
[ScriptedImporter(1, "gltf")]
#else
[ScriptedImporter(1, null, overrideExts: new[] { "gltf" })]
#endif
public class GLTFImporter : ScriptedImporter {
public ImportSettings importSettings;
public override void OnImportAsset(AssetImportContext ctx) {
// Load asset
AnimationClip[] animations;
if (importSettings == null) importSettings = new ImportSettings();
GameObject root = Importer.LoadFromFile(ctx.assetPath, importSettings, out animations, Format.GLTF);
// Save asset
GLTFAssetUtility.SaveToAsset(root, animations, ctx, importSettings);
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1fa4dafe23771ae498589f7f7c22c974
timeCreated: 1538919678
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
{
"name": "Siccity.GLTFUtility.Editor",
"references": [
"Siccity.GLTFUtility"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"versionDefines": [
{
"name": "com.atteneder.gltfast",
"expression": "3.0.0",
"define": "HAVE_GLTFAST"
},
{
"name": "org.khronos.unitygltf",
"expression": "0.0.0",
"define": "HAVE_UNITYGLTF"
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 56f0af5eabd0f8244a313552ef7d963e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,63 @@
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
namespace Siccity.GLTFUtility {
[CustomPropertyDrawer(typeof(ShaderSettings))]
public class ShaderSettingsDrawer : PropertyDrawer {
private static SerializedObject serializedGraphicsSettings;
private bool hasShaders;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
// Get shader properties
SerializedProperty metalicProp = property.FindPropertyRelative("metallic");
SerializedProperty metallicBlendProp = property.FindPropertyRelative("metallicBlend");
SerializedProperty specularProp = property.FindPropertyRelative("specular");
SerializedProperty specularBlendProp = property.FindPropertyRelative("specularBlend");
hasShaders = HasShaders(metalicProp, metallicBlendProp, specularProp, specularBlendProp);
EditorGUI.PropertyField(position, property, true);
// Show shader warning
if (!hasShaders && property.isExpanded) {
GUIContent content = new GUIContent("Shaders not found in 'GraphicSettings/Always Included Shaders'.\nShaders might not be available for runtime import.");
float baseHeight = EditorGUI.GetPropertyHeight(property, true);
Rect warningRect = new Rect(position.x, position.y + baseHeight + 2, position.width, 50);
EditorGUI.HelpBox(warningRect, content.text, MessageType.Warning);
/* Rect buttonRect = new Rect(warningRect.xMax - 150, warningRect.yMax + 2, 150, 18);
if (GUI.Button(buttonRect, "Open GraphicsSettings")) {
} */
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
float height = EditorGUI.GetPropertyHeight(property, true);
if (!hasShaders && property.isExpanded) height += 40;
return height;
}
public bool HasShaders(params SerializedProperty[] shaders) {
if (serializedGraphicsSettings == null) {
GraphicsSettings graphicsSettings = AssetDatabase.LoadAssetAtPath<GraphicsSettings>("ProjectSettings/GraphicsSettings.asset");
serializedGraphicsSettings = new SerializedObject(graphicsSettings);
}
SerializedProperty arrayProp = serializedGraphicsSettings.FindProperty("m_AlwaysIncludedShaders");
bool[] hasShaders = new bool[shaders.Length];
for (int i = 0; i < arrayProp.arraySize; ++i) {
SerializedProperty arrayElem = arrayProp.GetArrayElementAtIndex(i);
for (int k = 0; k < shaders.Length; k++) {
if (shaders[k].objectReferenceValue == arrayElem.objectReferenceValue) hasShaders[k] = true;
}
}
return hasShaders.All(x => x);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7fc255616f65554bad19a4eae4938bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
using UnityEngine;
namespace Siccity.GLTFUtility {
public enum AlphaMode { OPAQUE, MASK, BLEND }
public enum AccessorType { SCALAR, VEC2, VEC3, VEC4, MAT2, MAT3, MAT4 }
public enum RenderingMode { POINTS = 0, LINES = 1, LINE_LOOP = 2, LINE_STRIP = 3, TRIANGLES = 4, TRIANGLE_STRIP = 5, TRIANGLE_FAN = 6 }
public enum GLType { UNSET = -1, BYTE = 5120, UNSIGNED_BYTE = 5121, SHORT = 5122, UNSIGNED_SHORT = 5123, UNSIGNED_INT = 5125, FLOAT = 5126 }
public enum Format { AUTO, GLTF, GLB }
public enum CameraType { perspective, orthographic }
public enum InterpolationMode { ImportFromFile = -1, LINEAR = 0, STEP = 1, CUBICSPLINE = 2 }
public static class EnumExtensions {
public static int ByteSize(this GLType gltype) {
switch (gltype) {
case GLType.BYTE:
return sizeof(sbyte);
case GLType.UNSIGNED_BYTE:
return sizeof(byte);
case GLType.SHORT:
return sizeof(short);
case GLType.UNSIGNED_SHORT:
return sizeof(ushort);
case GLType.FLOAT:
return sizeof(float);
case GLType.UNSIGNED_INT:
return sizeof(uint);
default:
Debug.LogError("GLType " + (int) gltype + " not supported!");
return 0;
}
}
public static int ComponentCount(this AccessorType accessorType) {
switch (accessorType) {
case AccessorType.SCALAR:
return 1;
case AccessorType.VEC2:
return 2;
case AccessorType.VEC3:
return 3;
case AccessorType.VEC4:
return 4;
case AccessorType.MAT2:
return 4;
case AccessorType.MAT3:
return 9;
case AccessorType.MAT4:
return 16;
default:
Debug.LogError("AccessorType " + accessorType + " not supported!");
return 0;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da9e406a98bd2df42b00ca699cf01c6b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using UnityEngine;
namespace Siccity.GLTFUtility {
/// <summary> API used for exporting .gltf and .glb files </summary>
public static class Exporter {
#if UNITY_EDITOR
[UnityEditor.MenuItem("File/Export Selection/.glb")]
public static void ExportSelectedGLB() {
ExportGLB(UnityEditor.Selection.activeGameObject);
}
#endif
public static void ExportGLB(GameObject root) {
GLTFObject gltfObject = CreateGLTFObject(root.transform);
Debug.Log(JsonConvert.SerializeObject(gltfObject, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }));
}
public static void ExportGLTF(GameObject root) {
Debug.Log("Not implemented yet");
}
public static GLTFObject CreateGLTFObject(Transform root) {
GLTFObject gltfObject = new GLTFObject();
GLTFAsset asset = new GLTFAsset() {
generator = "GLTFUtility by Siccity https: //github.com/Siccity/GLTFUtility",
version = "2.0"
};
//List<GLTFAccessor.ExportResult> accessors
List<GLTFNode.ExportResult> nodes = GLTFNode.Export(root);
List<GLTFMesh.ExportResult> meshes = GLTFMesh.Export(nodes);
gltfObject.scene = 0;
gltfObject.asset = asset;
gltfObject.nodes = nodes.Cast<GLTFNode>().ToList();
gltfObject.meshes = meshes.Cast<GLTFMesh>().ToList();
return gltfObject;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f44abb798dea27c4eaa48bd050b1471f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections;
using UnityEngine;
namespace Siccity.GLTFUtility {
public static class Extensions {
public class CoroutineRunner : MonoBehaviour { }
private static CoroutineRunner coroutineRunner;
public static Coroutine RunCoroutine(this IEnumerator ienum) {
if (coroutineRunner == null) {
coroutineRunner = new GameObject("[CoroutineRunner]").AddComponent<CoroutineRunner>();
coroutineRunner.hideFlags = HideFlags.DontSaveInEditor | HideFlags.HideInHierarchy | HideFlags.HideInInspector | HideFlags.NotEditable | HideFlags.DontSaveInBuild;
coroutineRunner.gameObject.hideFlags = HideFlags.DontSaveInEditor | HideFlags.HideInHierarchy | HideFlags.HideInInspector | HideFlags.NotEditable | HideFlags.DontSaveInBuild;
}
return coroutineRunner.StartCoroutine(ienum);
}
public static T[] SubArray<T>(this T[] data, int index, int length) {
T[] result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
public static void UnpackTRS(this Matrix4x4 trs, ref Vector3 position, ref Quaternion rotation, ref Vector3 scale) {
position = trs.GetColumn(3);
position.x = -position.x;
rotation = trs.rotation;
rotation = new Quaternion(rotation.x, -rotation.y, -rotation.z, rotation.w);
scale = trs.lossyScale;
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b28e9070c77e9aa458e0629479371587
timeCreated: 1539200073
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7d54e7c3f2ea6bd42a274cd36d5017f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using Newtonsoft.Json;
using Siccity.GLTFUtility.Converters;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
/// <summary>
/// KHR_texture_transform textureInfo extension
/// glTF extension that enables shifting and scaling UV coordinates on a per-texture basis
/// see: https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform/schema/KHR_texture_transform.textureInfo.schema.json
/// </summary>
[Preserve] public class KHR_texture_transform : GLTFMaterial.TextureInfo.IExtension {
/// <summary>
/// The offset of the UV coordinate origin as a factor of the texture dimensions.
/// </summary>
[JsonConverter(typeof(Vector2Converter))] public Vector2 offset = Vector2.zero;
/// <summary>
/// Rotate the UVs by this many radians counter-clockwise around the origin.
/// </summary>
public float rotation = 0.0f;
/// <summary>
/// The scale factor applied to the components of the UV coordinates.
/// </summary>
[JsonConverter(typeof(Vector2Converter))] public Vector2 scale = Vector2.one;
/// <summary>
/// Overrides the textureInfo texCoord value if supplied, and if this extension is supported.
/// </summary>
public int texCoord = 0;
public void Apply(GLTFMaterial.TextureInfo texInfo, Material material, string textureSamplerName) {
material.SetTextureOffset(textureSamplerName, offset);
// TODO rotation
material.SetTextureScale(textureSamplerName, scale);
// TODO texCoord
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 430c613bbb3e69145bf8e34015468fd6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,380 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
namespace Siccity.GLTFUtility {
/// <summary> API used for importing .gltf and .glb files </summary>
public static class Importer {
public static GameObject LoadFromFile(string filepath, Format format = Format.AUTO) {
AnimationClip[] animations;
return LoadFromFile(filepath, new ImportSettings(), out animations, format);
}
public static GameObject LoadFromFile(string filepath, ImportSettings importSettings, Format format = Format.AUTO) {
AnimationClip[] animations;
return LoadFromFile(filepath, importSettings, out animations, format);
}
public static GameObject LoadFromFile(string filepath, ImportSettings importSettings, out AnimationClip[] animations, Format format = Format.AUTO) {
if (format == Format.GLB) {
return ImportGLB(filepath, importSettings, out animations);
} else if (format == Format.GLTF) {
return ImportGLTF(filepath, importSettings, out animations);
} else {
string extension = Path.GetExtension(filepath).ToLower();
if (extension == ".glb") return ImportGLB(filepath, importSettings, out animations);
else if (extension == ".gltf") return ImportGLTF(filepath, importSettings, out animations);
else {
Debug.Log("Extension '" + extension + "' not recognized in " + filepath);
animations = null;
return null;
}
}
}
/// <param name="bytes">GLB file is supported</param>
public static GameObject LoadFromBytes(byte[] bytes, ImportSettings importSettings = null) {
AnimationClip[] animations;
if (importSettings == null) importSettings = new ImportSettings();
return ImportGLB(bytes, importSettings, out animations);
}
/// <param name="bytes">GLB file is supported</param>
public static GameObject LoadFromBytes(byte[] bytes, ImportSettings importSettings, out AnimationClip[] animations) {
return ImportGLB(bytes, importSettings, out animations);
}
public static void LoadFromFileAsync(string filepath, ImportSettings importSettings, Action<GameObject, AnimationClip[]> onFinished, Action<float> onProgress = null) {
string extension = Path.GetExtension(filepath).ToLower();
if (extension == ".glb") ImportGLBAsync(filepath, importSettings, onFinished, onProgress);
else if (extension == ".gltf") ImportGLTFAsync(filepath, importSettings, onFinished, onProgress);
else {
Debug.Log("Extension '" + extension + "' not recognized in " + filepath);
onFinished(null, null);
}
}
#region GLB
private static GameObject ImportGLB(string filepath, ImportSettings importSettings, out AnimationClip[] animations) {
FileStream stream = File.OpenRead(filepath);
long binChunkStart;
string json = GetGLBJson(stream, out binChunkStart);
GLTFObject gltfObject = JsonConvert.DeserializeObject<GLTFObject>(json);
return gltfObject.LoadInternal(filepath, null, binChunkStart, importSettings, out animations);
}
private static GameObject ImportGLB(byte[] bytes, ImportSettings importSettings, out AnimationClip[] animations) {
Stream stream = new MemoryStream(bytes);
long binChunkStart;
string json = GetGLBJson(stream, out binChunkStart);
GLTFObject gltfObject = JsonConvert.DeserializeObject<GLTFObject>(json);
return gltfObject.LoadInternal(null, bytes, binChunkStart, importSettings, out animations);
}
public static void ImportGLBAsync(string filepath, ImportSettings importSettings, Action<GameObject, AnimationClip[]> onFinished, Action<float> onProgress = null) {
FileStream stream = File.OpenRead(filepath);
long binChunkStart;
string json = GetGLBJson(stream, out binChunkStart);
LoadAsync(json, filepath, null, binChunkStart, importSettings, onFinished, onProgress).RunCoroutine();
}
public static void ImportGLBAsync(byte[] bytes, ImportSettings importSettings, Action<GameObject, AnimationClip[]> onFinished, Action<float> onProgress = null) {
Stream stream = new MemoryStream(bytes);
long binChunkStart;
string json = GetGLBJson(stream, out binChunkStart);
LoadAsync(json, null, bytes, binChunkStart, importSettings, onFinished, onProgress).RunCoroutine();
}
private static string GetGLBJson(Stream stream, out long binChunkStart) {
byte[] buffer = new byte[12];
stream.Read(buffer, 0, 12);
// 12 byte header
// 0-4 - magic = "glTF"
// 4-8 - version = 2
// 8-12 - length = total length of glb, including Header and all Chunks, in bytes.
string magic = Encoding.Default.GetString(buffer, 0, 4);
if (magic != "glTF") {
Debug.LogWarning("Input does not look like a .glb file");
binChunkStart = 0;
return null;
}
uint version = System.BitConverter.ToUInt32(buffer, 4);
if (version != 2) {
Debug.LogWarning("Importer does not support gltf version " + version);
binChunkStart = 0;
return null;
}
// What do we even need the length for.
//uint length = System.BitConverter.ToUInt32(bytes, 8);
// Chunk 0 (json)
// 0-4 - chunkLength = total length of the chunkData
// 4-8 - chunkType = "JSON"
// 8-[chunkLength+8] - chunkData = json data.
stream.Read(buffer, 0, 8);
uint chunkLength = System.BitConverter.ToUInt32(buffer, 0);
TextReader reader = new StreamReader(stream);
char[] jsonChars = new char[chunkLength];
reader.Read(jsonChars, 0, (int) chunkLength);
string json = new string(jsonChars);
// Chunk
binChunkStart = chunkLength + 20;
stream.Close();
// Return json
return json;
}
#endregion
private static GameObject ImportGLTF(string filepath, ImportSettings importSettings, out AnimationClip[] animations) {
string json = File.ReadAllText(filepath);
// Parse json
GLTFObject gltfObject = JsonConvert.DeserializeObject<GLTFObject>(json);
return gltfObject.LoadInternal(filepath, null, 0, importSettings, out animations);
}
public static void ImportGLTFAsync(string filepath, ImportSettings importSettings, Action<GameObject, AnimationClip[]> onFinished, Action<float> onProgress = null) {
string json = File.ReadAllText(filepath);
// Parse json
LoadAsync(json, filepath, null, 0, importSettings, onFinished, onProgress).RunCoroutine();
}
public abstract class ImportTask<TReturn> : ImportTask {
public TReturn Result;
/// <summary> Constructor. Sets waitFor which ensures ImportTasks are completed before running. </summary>
public ImportTask(params ImportTask[] waitFor) : base(waitFor) { }
/// <summary> Runs task followed by OnCompleted </summary>
public TReturn RunSynchronously() {
if(task != null)
{
task.RunSynchronously();
}
IEnumerator en = OnCoroutine();
while (en.MoveNext()) { };
return Result;
}
}
public abstract class ImportTask {
public Task task;
public readonly ImportTask[] waitFor;
public bool IsReady { get { return waitFor.All(x => x.IsCompleted); } }
public bool IsCompleted { get; protected set; }
/// <summary> Constructor. Sets waitFor which ensures ImportTasks are completed before running. </summary>
public ImportTask(params ImportTask[] waitFor) {
IsCompleted = false;
this.waitFor = waitFor;
}
public virtual IEnumerator OnCoroutine(Action<float> onProgress = null) {
IsCompleted = true;
yield break;
}
}
#region Sync
private static GameObject LoadInternal(this GLTFObject gltfObject, string filepath, byte[] bytefile, long binChunkStart, ImportSettings importSettings, out AnimationClip[] animations) {
CheckExtensions(gltfObject);
// directory root is sometimes used for loading buffers from containing file, or local images
string directoryRoot = filepath != null ? Directory.GetParent(filepath).ToString() + "/" : null;
importSettings.shaderOverrides.CacheDefaultShaders();
// Import tasks synchronously
GLTFBuffer.ImportTask bufferTask = new GLTFBuffer.ImportTask(gltfObject.buffers, filepath, bytefile, binChunkStart);
bufferTask.RunSynchronously();
GLTFBufferView.ImportTask bufferViewTask = new GLTFBufferView.ImportTask(gltfObject.bufferViews, bufferTask);
bufferViewTask.RunSynchronously();
GLTFAccessor.ImportTask accessorTask = new GLTFAccessor.ImportTask(gltfObject.accessors, bufferViewTask);
accessorTask.RunSynchronously();
GLTFImage.ImportTask imageTask = new GLTFImage.ImportTask(gltfObject.images, directoryRoot, bufferViewTask);
imageTask.RunSynchronously();
GLTFTexture.ImportTask textureTask = new GLTFTexture.ImportTask(gltfObject.textures, imageTask);
textureTask.RunSynchronously();
GLTFMaterial.ImportTask materialTask = new GLTFMaterial.ImportTask(gltfObject.materials, textureTask, importSettings);
materialTask.RunSynchronously();
GLTFMesh.ImportTask meshTask = new GLTFMesh.ImportTask(gltfObject.meshes, accessorTask, bufferViewTask, materialTask, importSettings);
meshTask.RunSynchronously();
GLTFSkin.ImportTask skinTask = new GLTFSkin.ImportTask(gltfObject.skins, accessorTask);
skinTask.RunSynchronously();
GLTFNode.ImportTask nodeTask = new GLTFNode.ImportTask(gltfObject.nodes, meshTask, skinTask, gltfObject.cameras);
nodeTask.RunSynchronously();
GLTFAnimation.ImportResult[] animationResult = gltfObject.animations.Import(accessorTask.Result, nodeTask.Result, importSettings);
if (animationResult != null) animations = animationResult.Select(x => x.clip).ToArray();
else animations = new AnimationClip[0];
foreach (var item in bufferTask.Result) {
item.Dispose();
}
GameObject gameObject = nodeTask.Result.GetRoot();
if (importSettings.extrasProcessor != null)
{
if(gltfObject.extras == null)
{
gltfObject.extras = new JObject();
}
if(gltfObject.materials != null)
{
JArray materialExtras = new JArray();
bool hasMaterialExtraData = false;
foreach (GLTFMaterial material in gltfObject.materials)
{
if (material.extras != null)
{
materialExtras.Add(material.extras);
hasMaterialExtraData = true;
}
else
{
materialExtras.Add(new JObject());
}
}
if (hasMaterialExtraData)
{
gltfObject.extras.Add("material", materialExtras);
}
}
if (gltfObject.animations != null)
{
JArray animationExtras = new JArray();
bool hasAnimationExtraData = false;
foreach (GLTFAnimation animation in gltfObject.animations)
{
if (animation.extras != null)
{
hasAnimationExtraData = true;
animationExtras.Add(animation.extras);
}
else
{
animationExtras.Add(new JObject());
}
}
if (hasAnimationExtraData)
{
gltfObject.extras.Add("animation", animationExtras);
}
}
importSettings.extrasProcessor.ProcessExtras(gameObject, animations, gltfObject.extras);
}
return gameObject;
}
#endregion
#region Async
private static IEnumerator LoadAsync(string json, string filepath, byte[] bytefile, long binChunkStart, ImportSettings importSettings, Action<GameObject, AnimationClip[]> onFinished, Action<float> onProgress = null) {
// Threaded deserialization
Task<GLTFObject> deserializeTask = new Task<GLTFObject>(() => JsonConvert.DeserializeObject<GLTFObject>(json));
deserializeTask.Start();
while (!deserializeTask.IsCompleted) yield return null;
GLTFObject gltfObject = deserializeTask.Result;
CheckExtensions(gltfObject);
// directory root is sometimes used for loading buffers from containing file, or local images
string directoryRoot = filepath != null ? Directory.GetParent(filepath).ToString() + "/" : null;
importSettings.shaderOverrides.CacheDefaultShaders();
// Setup import tasks
List<ImportTask> importTasks = new List<ImportTask>();
GLTFBuffer.ImportTask bufferTask = new GLTFBuffer.ImportTask(gltfObject.buffers, filepath, bytefile, binChunkStart);
importTasks.Add(bufferTask);
GLTFBufferView.ImportTask bufferViewTask = new GLTFBufferView.ImportTask(gltfObject.bufferViews, bufferTask);
importTasks.Add(bufferViewTask);
GLTFAccessor.ImportTask accessorTask = new GLTFAccessor.ImportTask(gltfObject.accessors, bufferViewTask);
importTasks.Add(accessorTask);
GLTFImage.ImportTask imageTask = new GLTFImage.ImportTask(gltfObject.images, directoryRoot, bufferViewTask);
importTasks.Add(imageTask);
GLTFTexture.ImportTask textureTask = new GLTFTexture.ImportTask(gltfObject.textures, imageTask);
importTasks.Add(textureTask);
GLTFMaterial.ImportTask materialTask = new GLTFMaterial.ImportTask(gltfObject.materials, textureTask, importSettings);
importTasks.Add(materialTask);
GLTFMesh.ImportTask meshTask = new GLTFMesh.ImportTask(gltfObject.meshes, accessorTask, bufferViewTask, materialTask, importSettings);
importTasks.Add(meshTask);
GLTFSkin.ImportTask skinTask = new GLTFSkin.ImportTask(gltfObject.skins, accessorTask);
importTasks.Add(skinTask);
GLTFNode.ImportTask nodeTask = new GLTFNode.ImportTask(gltfObject.nodes, meshTask, skinTask, gltfObject.cameras);
importTasks.Add(nodeTask);
// Ignite
for (int i = 0; i < importTasks.Count; i++) {
TaskSupervisor(importTasks[i], onProgress).RunCoroutine();
}
// Wait for all tasks to finish
while (!importTasks.All(x => x.IsCompleted)) yield return null;
// Fire onFinished when all tasks have completed
GameObject root = nodeTask.Result.GetRoot();
GLTFAnimation.ImportResult[] animationResult = gltfObject.animations.Import(accessorTask.Result, nodeTask.Result, importSettings);
AnimationClip[] animations = new AnimationClip[0];
if (animationResult != null) animations = animationResult.Select(x => x.clip).ToArray();
if (onFinished != null) onFinished(nodeTask.Result.GetRoot(), animations);
// Close file streams
foreach (var item in bufferTask.Result) {
item.Dispose();
}
}
/// <summary> Keeps track of which threads to start when </summary>
private static IEnumerator TaskSupervisor(ImportTask importTask, Action<float> onProgress = null) {
// Wait for required results to complete before starting
while (!importTask.IsReady) yield return null;
// Prevent asynchronous data disorder
yield return null;
if(importTask.task != null)
{
// Start threaded task
importTask.task.Start();
// Wait for task to complete
while (!importTask.task.IsCompleted) yield return null;
// Prevent asynchronous data disorder
yield return new WaitForSeconds(0.1f);
}
// Run additional unity code on main thread
importTask.OnCoroutine(onProgress).RunCoroutine();
//Wait for additional coroutines to complete
while (!importTask.IsCompleted) { yield return null; }
// Prevent asynchronous data disorder
yield return new WaitForSeconds(0.1f);
}
#endregion
private static void CheckExtensions(GLTFObject gLTFObject) {
if (gLTFObject.extensionsRequired != null) {
for (int i = 0; i < gLTFObject.extensionsRequired.Count; i++) {
switch (gLTFObject.extensionsRequired[i]) {
case "KHR_materials_pbrSpecularGlossiness":
break;
case "KHR_draco_mesh_compression":
break;
default:
Debug.LogWarning($"GLTFUtility: Required extension '{gLTFObject.extensionsRequired[i]}' not supported. Import process will proceed but results may vary.");
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3d8a47f9eb0b1c48b13e959fc075ada
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 124029330cc82b14aa51a2808ff4511c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace Siccity.GLTFUtility {
/// <summary> Defines how animations are imported </summary>
[Serializable]
public class AnimationSettings {
public bool looping;
[Tooltip("Sample rate set on all imported animation clips.")]
public float frameRate = 24;
[Tooltip("Interpolation mode applied to all keyframe tangents. Use Import From File when mixing modes within an animation.")]
public InterpolationMode interpolationMode = InterpolationMode.ImportFromFile;
[Tooltip("When true, remove redundant keyframes from blend shape animations.")]
public bool compressBlendShapeKeyFrames = true;
[Tooltip("Load animations as legacy AnimationClips.")]
public bool useLegacyClips;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 977131d8fdcfd7449b4c86946f77be9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.EventSystems;
using UnityEngine.Events;
namespace Siccity.GLTFUtility {
[Serializable]
public class ImportSettings {
public bool materials = true;
[FormerlySerializedAs("shaders")]
public ShaderSettings shaderOverrides = new ShaderSettings();
public AnimationSettings animationSettings = new AnimationSettings();
public bool generateLightmapUVs;
[Range(0, 180)]
public float hardAngle = 88;
[Range(1, 75)]
public float angleError = 8;
[Range(1, 75)]
public float areaError = 15;
[Range(1, 64)]
public float packMargin = 4;
[Tooltip("Script used to process extra data.")]
public GLTFExtrasProcessor extrasProcessor;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b276c1b22c950af4583e0d432d9f11ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,61 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace Siccity.GLTFUtility {
/// <summary> Defines which shaders to use in the gltf import process </summary>
[Serializable]
public class ShaderSettings {
[SerializeField] private Shader metallic;
public Shader Metallic { get { return metallic != null ? metallic : GetDefaultMetallic(); } }
[SerializeField] private Shader metallicBlend;
public Shader MetallicBlend { get { return metallicBlend != null ? metallicBlend : GetDefaultMetallicBlend(); } }
[SerializeField] private Shader specular;
public Shader Specular { get { return specular != null ? specular : GetDefaultSpecular(); } }
[SerializeField] private Shader specularBlend;
public Shader SpecularBlend { get { return specularBlend != null ? specularBlend : GetDefaultSpecularBlend(); } }
/// <summary> Caches default shaders so that async import won't try to search for them while on a separate thread </summary>
public void CacheDefaultShaders() {
metallic = Metallic;
metallicBlend = MetallicBlend;
specular = Specular;
specularBlend = SpecularBlend;
}
public Shader GetDefaultMetallic() {
#if UNITY_2019_1_OR_NEWER
if (GraphicsSettings.renderPipelineAsset) return Shader.Find("GLTFUtility/URP/Standard (Metallic)");
else
#endif
return Shader.Find("GLTFUtility/Standard (Metallic)");
}
public Shader GetDefaultMetallicBlend() {
#if UNITY_2019_1_OR_NEWER
if (GraphicsSettings.renderPipelineAsset) return Shader.Find("GLTFUtility/URP/Standard Transparent (Metallic)");
else
#endif
return Shader.Find("GLTFUtility/Standard Transparent (Metallic)");
}
public Shader GetDefaultSpecular() {
#if UNITY_2019_1_OR_NEWER
if (GraphicsSettings.renderPipelineAsset) return Shader.Find("GLTFUtility/URP/Standard (Specular)");
else
#endif
return Shader.Find("GLTFUtility/Standard (Specular)");
}
public Shader GetDefaultSpecularBlend() {
#if UNITY_2019_1_OR_NEWER
if (GraphicsSettings.renderPipelineAsset) return Shader.Find("GLTFUtility/URP/Standard Transparent (Specular)");
else
#endif
return Shader.Find("GLTFUtility/Standard Transparent (Specular)");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc38d0f9a32cd0443ac479afd967ab81
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 893e3cbb80103e040aceb5dc3c05f135
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,508 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Siccity.GLTFUtility.Converters;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessor
/// <summary> Reads data from BufferViews </summary>
[Preserve] public class GLTFAccessor {
#region Serialized fields
public int? bufferView;
public int byteOffset = 0;
[JsonProperty(Required = Required.Always), JsonConverter(typeof(EnumConverter))] public AccessorType type;
[JsonProperty(Required = Required.Always)] public GLType componentType;
[JsonProperty(Required = Required.Always)] public int count;
public float[] min;
public float[] max;
public Sparse sparse;
#endregion
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse
[Preserve] public class Sparse {
[JsonProperty(Required = Required.Always)] public int count;
[JsonProperty(Required = Required.Always)] public Indices indices;
[JsonProperty(Required = Required.Always)] public Values values;
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#values
[Preserve] public class Values {
[JsonProperty(Required = Required.Always)] public int bufferView;
public int byteOffset = 0;
}
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#indices
[Preserve] public class Indices {
[JsonProperty(Required = Required.Always)] public int bufferView;
[JsonProperty(Required = Required.Always)] public GLType componentType;
public int byteOffset = 0;
}
}
#region Import
public class ImportResult {
private const float byteNormalize = 1f / byte.MaxValue;
private const float shortNormalize = 1f / short.MaxValue;
private const float ushortNormalize = 1f / ushort.MaxValue;
private const float intNormalize = 1f / int.MaxValue;
private const float uintNormalize = 1f / uint.MaxValue;
public GLTFBufferView.ImportResult bufferView;
public int? byteStride;
public int count;
public GLType componentType;
public AccessorType type;
public int byteOffset;
public Sparse sparse;
public class Sparse {
public int count;
public Indices indices;
public Values values;
public class Values {
public GLTFBufferView.ImportResult bufferView;
public int byteOffset = 0;
}
public class Indices {
public GLTFBufferView.ImportResult bufferView;
public GLType componentType;
public int byteOffset = 0;
}
}
public Matrix4x4[] ReadMatrix4x4() {
if (!ValidateAccessorType(type, AccessorType.MAT4)) return new Matrix4x4[count];
Func<BufferedBinaryReader, float> floatReader = GetFloatReader(componentType);
Matrix4x4[] m = new Matrix4x4[count];
if (bufferView != null) {
BufferedBinaryReader reader = new BufferedBinaryReader(bufferView.stream, 1024);
reader.Position = bufferView.byteOffset + byteOffset;
int byteSkip = byteStride.HasValue ? byteStride.Value - GetComponentSize() : 0;
for (int i = 0; i < count; i++) {
m[i].m00 = floatReader(reader);
m[i].m01 = floatReader(reader);
m[i].m02 = floatReader(reader);
m[i].m03 = floatReader(reader);
m[i].m10 = floatReader(reader);
m[i].m11 = floatReader(reader);
m[i].m12 = floatReader(reader);
m[i].m13 = floatReader(reader);
m[i].m20 = floatReader(reader);
m[i].m21 = floatReader(reader);
m[i].m22 = floatReader(reader);
m[i].m23 = floatReader(reader);
m[i].m30 = floatReader(reader);
m[i].m31 = floatReader(reader);
m[i].m32 = floatReader(reader);
m[i].m33 = floatReader(reader);
reader.Skip(byteSkip);
}
}
if (sparse != null) {
Func<BufferedBinaryReader, int> indexIntReader = GetIntReader(sparse.indices.componentType);
BufferedBinaryReader indexReader = new BufferedBinaryReader(sparse.indices.bufferView.stream, 1024);
indexReader.Position = sparse.indices.bufferView.byteOffset + sparse.indices.byteOffset;
int[] indices = new int[sparse.count];
for (int i = 0; i < sparse.count; i++) {
indices[i] = indexIntReader(indexReader);
}
BufferedBinaryReader valueReader = new BufferedBinaryReader(sparse.values.bufferView.stream, 1024);
indexReader.Position = sparse.values.bufferView.byteOffset + sparse.values.byteOffset;
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
m[index].m00 = floatReader(valueReader);
m[index].m01 = floatReader(valueReader);
m[index].m02 = floatReader(valueReader);
m[index].m03 = floatReader(valueReader);
m[index].m10 = floatReader(valueReader);
m[index].m11 = floatReader(valueReader);
m[index].m12 = floatReader(valueReader);
m[index].m13 = floatReader(valueReader);
m[index].m20 = floatReader(valueReader);
m[index].m21 = floatReader(valueReader);
m[index].m22 = floatReader(valueReader);
m[index].m23 = floatReader(valueReader);
m[index].m30 = floatReader(valueReader);
m[index].m31 = floatReader(valueReader);
m[index].m32 = floatReader(valueReader);
m[index].m33 = floatReader(valueReader);
}
}
return m;
}
public Vector4[] ReadVec4(bool normalize = false) {
if (!ValidateAccessorType(type, AccessorType.VEC4)) return new Vector4[count];
Func<BufferedBinaryReader, float> floatReader = normalize ? GetNormalizedFloatReader(componentType) : GetFloatReader(componentType);
Vector4[] v = new Vector4[count];
if (bufferView != null) {
BufferedBinaryReader reader = new BufferedBinaryReader(bufferView.stream, 1024);
reader.Position = bufferView.byteOffset + byteOffset;
int byteSkip = byteStride.HasValue ? byteStride.Value - GetComponentSize() : 0;
for (int i = 0; i < count; i++) {
v[i].x = floatReader(reader);
v[i].y = floatReader(reader);
v[i].z = floatReader(reader);
v[i].w = floatReader(reader);
reader.Skip(byteSkip);
}
}
if (sparse != null) {
Func<BufferedBinaryReader, int> indexIntReader = GetIntReader(sparse.indices.componentType);
BufferedBinaryReader indexReader = new BufferedBinaryReader(sparse.indices.bufferView.stream, 1024);
indexReader.Position = sparse.indices.bufferView.byteOffset + sparse.indices.byteOffset;
int[] indices = new int[sparse.count];
for (int i = 0; i < sparse.count; i++) {
indices[i] = indexIntReader(indexReader);
}
BufferedBinaryReader valueReader = new BufferedBinaryReader(sparse.values.bufferView.stream, 1024);
indexReader.Position = sparse.values.bufferView.byteOffset + sparse.values.byteOffset;
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
v[index].x = floatReader(valueReader);
v[index].y = floatReader(valueReader);
v[index].z = floatReader(valueReader);
v[index].w = floatReader(valueReader);
}
}
return v;
}
public Color[] ReadColor() {
if (!ValidateAccessorTypeAny(type, AccessorType.VEC3, AccessorType.VEC4)) return new Color[count];
Func<BufferedBinaryReader, float> floatReader = GetNormalizedFloatReader(componentType);
Color[] c = new Color[count];
if (bufferView != null) {
BufferedBinaryReader reader = new BufferedBinaryReader(bufferView.stream, 1024);
reader.Position = bufferView.byteOffset + byteOffset;
int byteSkip = byteStride.HasValue ? byteStride.Value - GetComponentSize() : 0;
if (type == AccessorType.VEC3) {
for (int i = 0; i < count; i++) {
c[i].r = floatReader(reader);
c[i].g = floatReader(reader);
c[i].b = floatReader(reader);
reader.Skip(byteSkip);
}
} else if (type == AccessorType.VEC4) {
for (int i = 0; i < count; i++) {
c[i].r = floatReader(reader);
c[i].g = floatReader(reader);
c[i].b = floatReader(reader);
c[i].a = floatReader(reader);
reader.Skip(byteSkip);
}
}
}
if (sparse != null) {
Func<BufferedBinaryReader, int> indexIntReader = GetIntReader(sparse.indices.componentType);
BufferedBinaryReader indexReader = new BufferedBinaryReader(sparse.indices.bufferView.stream, 1024);
indexReader.Position = sparse.indices.bufferView.byteOffset + sparse.indices.byteOffset;
int[] indices = new int[sparse.count];
for (int i = 0; i < sparse.count; i++) {
indices[i] = indexIntReader(indexReader);
}
BufferedBinaryReader valueReader = new BufferedBinaryReader(sparse.values.bufferView.stream, 1024);
indexReader.Position = sparse.values.bufferView.byteOffset + sparse.values.byteOffset;
if (type == AccessorType.VEC3) {
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
c[index].r = floatReader(valueReader);
c[index].g = floatReader(valueReader);
c[index].b = floatReader(valueReader);
}
} else if (type == AccessorType.VEC4) {
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
c[index].r = floatReader(valueReader);
c[index].g = floatReader(valueReader);
c[index].b = floatReader(valueReader);
c[index].a = floatReader(valueReader);
}
}
}
return c;
}
public Vector3[] ReadVec3(bool normalize = false) {
if (!ValidateAccessorType(type, AccessorType.VEC3)) return new Vector3[count];
Func<BufferedBinaryReader, float> floatReader = normalize ? GetNormalizedFloatReader(componentType) : GetFloatReader(componentType);
Vector3[] v = new Vector3[count];
if (bufferView != null) {
BufferedBinaryReader reader = new BufferedBinaryReader(bufferView.stream, 1024);
reader.Position = bufferView.byteOffset + byteOffset;
int byteSkip = byteStride.HasValue ? byteStride.Value - GetComponentSize() : 0;
for (int i = 0; i < count; i++) {
v[i].x = floatReader(reader);
v[i].y = floatReader(reader);
v[i].z = floatReader(reader);
reader.Skip(byteSkip);
}
}
if (sparse != null) {
Func<BufferedBinaryReader, int> indexIntReader = GetIntReader(sparse.indices.componentType);
BufferedBinaryReader indexReader = new BufferedBinaryReader(sparse.indices.bufferView.stream, 1024);
indexReader.Position = sparse.indices.bufferView.byteOffset + sparse.indices.byteOffset;
int[] indices = new int[sparse.count];
for (int i = 0; i < sparse.count; i++) {
indices[i] = indexIntReader(indexReader);
}
BufferedBinaryReader valueReader = new BufferedBinaryReader(sparse.values.bufferView.stream, 1024);
valueReader.Position = sparse.values.bufferView.byteOffset + sparse.values.byteOffset;
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
v[index].x = floatReader(valueReader);
v[index].y = floatReader(valueReader);
v[index].z = floatReader(valueReader);
}
}
return v;
}
public Vector2[] ReadVec2(bool normalize = false) {
if (!ValidateAccessorType(type, AccessorType.VEC2)) return new Vector2[count];
Func<BufferedBinaryReader, float> floatReader = normalize ? GetNormalizedFloatReader(componentType) : GetFloatReader(componentType);
Vector2[] v = new Vector2[count];
if (bufferView != null) {
BufferedBinaryReader reader = new BufferedBinaryReader(bufferView.stream, 1024);
reader.Position = bufferView.byteOffset + byteOffset;
int byteSkip = byteStride.HasValue ? byteStride.Value - GetComponentSize() : 0;
for (int i = 0; i < count; i++) {
v[i].x = floatReader(reader);
v[i].y = floatReader(reader);
reader.Skip(byteSkip);
}
}
if (sparse != null) {
Func<BufferedBinaryReader, int> indexIntReader = GetIntReader(sparse.indices.componentType);
BufferedBinaryReader indexReader = new BufferedBinaryReader(sparse.indices.bufferView.stream, 1024);
indexReader.Position = sparse.indices.bufferView.byteOffset + sparse.indices.byteOffset;
int[] indices = new int[sparse.count];
for (int i = 0; i < sparse.count; i++) {
indices[i] = indexIntReader(indexReader);
}
BufferedBinaryReader valueReader = new BufferedBinaryReader(sparse.values.bufferView.stream, 1024);
indexReader.Position = sparse.values.bufferView.byteOffset + sparse.values.byteOffset;
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
v[index].x = floatReader(valueReader);
v[index].y = floatReader(valueReader);
}
}
return v;
}
public float[] ReadFloat() {
if (!ValidateAccessorType(type, AccessorType.SCALAR)) return new float[count];
Func<BufferedBinaryReader, float> floatReader = GetFloatReader(componentType);
float[] f = new float[count];
if (bufferView != null) {
BufferedBinaryReader reader = new BufferedBinaryReader(bufferView.stream, 1024);
reader.Position = bufferView.byteOffset + byteOffset;
int byteSkip = byteStride.HasValue ? byteStride.Value - GetComponentSize() : 0;
for (int i = 0; i < count; i++) {
f[i] = floatReader(reader);
reader.Skip(byteSkip);
}
}
if (sparse != null) {
Func<BufferedBinaryReader, int> indexIntReader = GetIntReader(sparse.indices.componentType);
BufferedBinaryReader indexReader = new BufferedBinaryReader(sparse.indices.bufferView.stream, 1024);
indexReader.Position = sparse.indices.bufferView.byteOffset + sparse.indices.byteOffset;
int[] indices = new int[sparse.count];
for (int i = 0; i < sparse.count; i++) {
indices[i] = indexIntReader(indexReader);
}
BufferedBinaryReader valueReader = new BufferedBinaryReader(sparse.values.bufferView.stream, 1024);
indexReader.Position = sparse.values.bufferView.byteOffset + sparse.values.byteOffset;
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
f[index] = floatReader(valueReader);
}
}
return f;
}
public int[] ReadInt() {
if (!ValidateAccessorType(type, AccessorType.SCALAR)) return new int[count];
Func<BufferedBinaryReader, int> intReader = GetIntReader(componentType);
int[] v = new int[count];
if (bufferView != null) {
BufferedBinaryReader reader = new BufferedBinaryReader(bufferView.stream, 1024);
reader.Position = bufferView.byteOffset + byteOffset;
int byteSkip = byteStride.HasValue ? byteStride.Value - GetComponentSize() : 0;
for (int i = 0; i < count; i++) {
v[i] = intReader(reader);
reader.Skip(byteSkip);
}
}
if (sparse != null) {
Func<BufferedBinaryReader, int> indexIntReader = GetIntReader(sparse.indices.componentType);
BufferedBinaryReader indexReader = new BufferedBinaryReader(sparse.indices.bufferView.stream, 1024);
indexReader.Position = sparse.indices.bufferView.byteOffset + sparse.indices.byteOffset;
int[] indices = new int[sparse.count];
for (int i = 0; i < sparse.count; i++) {
indices[i] = indexIntReader(indexReader);
}
BufferedBinaryReader valueReader = new BufferedBinaryReader(sparse.values.bufferView.stream, 1024);
indexReader.Position = sparse.values.bufferView.byteOffset + sparse.values.byteOffset;
for (int i = 0; i < sparse.count; i++) {
int index = indices[i];
v[index] = intReader(valueReader);
}
}
return v;
}
public Func<BufferedBinaryReader, int> GetIntReader(GLType componentType) {
Func<BufferedBinaryReader, int> readMethod;
switch (componentType) {
case GLType.BYTE:
return x => x.ReadSByte();
case GLType.UNSIGNED_BYTE:
return readMethod = x => x.ReadByte();
case GLType.FLOAT:
return readMethod = x => (int) x.ReadSingle();
case GLType.SHORT:
return readMethod = x => x.ReadInt16();
case GLType.UNSIGNED_SHORT:
return readMethod = x => x.ReadUInt16();
case GLType.UNSIGNED_INT:
return readMethod = x => (int) x.ReadUInt32();
default:
Debug.LogWarning("No componentType defined");
return readMethod = x => x.ReadInt32();
}
}
public Func<BufferedBinaryReader, float> GetFloatReader(GLType componentType) {
Func<BufferedBinaryReader, float> readMethod;
switch (componentType) {
case GLType.BYTE:
return x => x.ReadSByte();
case GLType.UNSIGNED_BYTE:
return readMethod = x => x.ReadByte();
case GLType.FLOAT:
return readMethod = x => x.ReadSingle();
case GLType.SHORT:
return readMethod = x => x.ReadInt16();
case GLType.UNSIGNED_SHORT:
return readMethod = x => x.ReadUInt16();
case GLType.UNSIGNED_INT:
return readMethod = x => x.ReadUInt32();
default:
Debug.LogWarning("No componentType defined");
return readMethod = x => x.ReadSingle();
}
}
public Func<BufferedBinaryReader, float> GetNormalizedFloatReader(GLType componentType)
{
Func<BufferedBinaryReader, float> readMethod;
switch(componentType)
{
case GLType.BYTE:
return x => x.ReadSByte();
case GLType.UNSIGNED_BYTE:
return readMethod = x => x.ReadByte() * byteNormalize;
case GLType.FLOAT:
return readMethod = x => x.ReadSingle();
case GLType.SHORT:
return readMethod = x => x.ReadInt16() * shortNormalize;
case GLType.UNSIGNED_SHORT:
return readMethod = x => x.ReadUInt16() * ushortNormalize;
case GLType.UNSIGNED_INT:
return readMethod = x => x.ReadUInt32() / uintNormalize;
default:
Debug.LogWarning("No componentType defined");
return readMethod = x => x.ReadSingle();
}
}
/// <summary> Get the size of the attribute type, in bytes </summary>
public int GetComponentSize() {
return type.ComponentCount() * componentType.ByteSize();
}
public static bool ValidateByteStride(int byteStride) {
if (byteStride >= 4 && byteStride <= 252 && byteStride % 4 == 0) return true;
Debug.Log("ByteStride of " + byteStride + " is invalid. Ignoring.");
return false;
}
private static bool ValidateAccessorType(AccessorType type, AccessorType expected) {
if (type == expected) return true;
else {
Debug.LogError("Type mismatch! Expected " + expected + " got " + type);
return false;
}
}
public static bool ValidateAccessorTypeAny(AccessorType type, params AccessorType[] expected) {
for (int i = 0; i < expected.Length; i++) {
if (type == expected[i]) return true;
}
Debug.Log("Type mismatch! Expected " + string.Join("or ", expected) + ", got " + type);
return false;
}
}
public ImportResult Import(GLTFBufferView.ImportResult[] bufferViews) {
ImportResult result = new ImportResult();
result.bufferView = bufferView.HasValue ? bufferViews[bufferView.Value] : null;
result.componentType = componentType;
result.type = type;
result.count = count;
result.byteOffset = byteOffset;
result.byteStride = result.bufferView != null ? result.bufferView.byteStride : null;
// Sparse accessor works by overwriting specified indices instead of defining a full data set. This can save space, especially for morph targets
if (sparse != null) {
result.sparse = new ImportResult.Sparse() {
count = sparse.count,
indices = new ImportResult.Sparse.Indices() {
bufferView = bufferViews[sparse.indices.bufferView],
componentType = sparse.indices.componentType,
byteOffset = sparse.indices.byteOffset
},
values = new ImportResult.Sparse.Values() {
bufferView = bufferViews[sparse.values.bufferView],
byteOffset = sparse.values.byteOffset
}
};
}
return result;
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
public ImportTask(List<GLTFAccessor> accessors, GLTFBufferView.ImportTask bufferViewTask) : base(bufferViewTask) {
task = new Task(() => {
Result = new ImportResult[accessors.Count];
for (int i = 0; i < Result.Length; i++) {
Result[i] = accessors[i].Import(bufferViewTask.Result);
}
});
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 2f27a48d4b05948419cd94312065cc96
timeCreated: 1539029690
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Siccity.GLTFUtility.Converters;
using UnityEngine;
using UnityEngine.Scripting;
using Newtonsoft.Json.Linq;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#animation
/// <summary> Contains info for a single animation clip </summary>
[Preserve] public class GLTFAnimation {
/// <summary> Connects the output values of the key frame animation to a specific node in the hierarchy </summary>
[JsonProperty(Required = Required.Always)] public Channel[] channels;
[JsonProperty(Required = Required.Always)] public Sampler[] samplers;
public string name;
public JObject extras;
#region Classes
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#animation-sampler
[Preserve] public class Sampler {
/// <summary> The index of an accessor containing keyframe input values, e.g., time. </summary>
[JsonProperty(Required = Required.Always)] public int input;
/// <summary> The index of an accessor containing keyframe output values. </summary>
[JsonProperty(Required = Required.Always)] public int output;
/// <summary> Valid names include: "LINEAR", "STEP", "CUBICSPLINE" </summary>
[JsonConverter(typeof(EnumConverter))] public InterpolationMode interpolation = InterpolationMode.LINEAR;
}
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#channel
/// <summary> Connects the output values of the key frame animation to a specific node in the hierarchy </summary>
[Preserve] public class Channel {
/// <summary> Target sampler index </summary>
[JsonProperty(Required = Required.Always)] public int sampler;
/// <summary> Target sampler index </summary>
[JsonProperty(Required = Required.Always)] public Target target;
}
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#target
/// <summary> Identifies which node and property to animate </summary>
[Preserve] public class Target {
/// <summary> Target node index.</summary>
public int? node;
/// <summary> Which property to animate. Valid names are: "translation", "rotation", "scale", "weights" </summary>
[JsonProperty(Required = Required.Always)] public string path;
}
[Preserve] public class ImportResult {
public AnimationClip clip;
}
#endregion
public ImportResult Import(GLTFAccessor.ImportResult[] accessors, GLTFNode.ImportResult[] nodes, ImportSettings importSettings) {
bool multiRoots = nodes.Where(x => x.IsRoot).Count() > 1;
ImportResult result = new ImportResult();
result.clip = new AnimationClip();
result.clip.name = name;
result.clip.frameRate = importSettings.animationSettings.frameRate;
result.clip.legacy = importSettings.animationSettings.useLegacyClips;
if (result.clip.legacy && importSettings.animationSettings.looping)
{
result.clip.wrapMode = WrapMode.Loop;
}
for (int i = 0; i < channels.Length; i++) {
Channel channel = channels[i];
if (samplers.Length <= channel.sampler) {
Debug.LogWarning($"GLTFUtility: Animation channel points to sampler at index {channel.sampler} which doesn't exist. Skipping animation clip.");
continue;
}
Sampler sampler = samplers[channel.sampler];
// Get interpolation mode
InterpolationMode interpolationMode = importSettings.animationSettings.interpolationMode;
if (interpolationMode == InterpolationMode.ImportFromFile) {
interpolationMode = sampler.interpolation;
}
if (interpolationMode == InterpolationMode.CUBICSPLINE) Debug.LogWarning("Animation interpolation mode CUBICSPLINE not fully supported, result might look different.");
string relativePath = "";
GLTFNode.ImportResult node = nodes[channel.target.node.Value];
while (node != null && !node.IsRoot) {
if (string.IsNullOrEmpty(relativePath)) relativePath = node.transform.name;
else relativePath = node.transform.name + "/" + relativePath;
if (node.parent.HasValue) node = nodes[node.parent.Value];
else node = null;
}
// If file has multiple root nodes, a new parent will be created for them as a final step of the import process. This parent fucks up the curve relative paths.
// Add node.transform.name to path if there are multiple roots. This is not the most elegant fix but it works.
// See GLTFNodeExtensions.GetRoot
if (multiRoots) relativePath = node.transform.name + "/" + relativePath;
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
float[] keyframeInput = accessors[sampler.input].ReadFloat().ToArray();
switch (channel.target.path) {
case "translation":
Vector3[] pos = accessors[sampler.output].ReadVec3().ToArray();
AnimationCurve posX = new AnimationCurve();
AnimationCurve posY = new AnimationCurve();
AnimationCurve posZ = new AnimationCurve();
for (int k = 0; k < keyframeInput.Length; k++) {
posX.AddKey(CreateKeyframe(k, keyframeInput, pos, x => -x.x, interpolationMode));
posY.AddKey(CreateKeyframe(k, keyframeInput, pos, x => x.y, interpolationMode));
posZ.AddKey(CreateKeyframe(k, keyframeInput, pos, x => x.z, interpolationMode));
}
result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.x", posX);
result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.y", posY);
result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.z", posZ);
break;
case "rotation":
Vector4[] rot = accessors[sampler.output].ReadVec4().ToArray();
AnimationCurve rotX = new AnimationCurve();
AnimationCurve rotY = new AnimationCurve();
AnimationCurve rotZ = new AnimationCurve();
AnimationCurve rotW = new AnimationCurve();
for (int k = 0; k < keyframeInput.Length; k++) {
// The Animation window in Unity shows keyframes incorrectly converted to euler. This is only to deceive you. The quaternions underneath work correctly
rotX.AddKey(CreateKeyframe(k, keyframeInput, rot, x => x.x, interpolationMode));
rotY.AddKey(CreateKeyframe(k, keyframeInput, rot, x => -x.y, interpolationMode));
rotZ.AddKey(CreateKeyframe(k, keyframeInput, rot, x => -x.z, interpolationMode));
rotW.AddKey(CreateKeyframe(k, keyframeInput, rot, x => x.w, interpolationMode));
}
result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.x", rotX);
result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.y", rotY);
result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.z", rotZ);
result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.w", rotW);
break;
case "scale":
Vector3[] scale = accessors[sampler.output].ReadVec3().ToArray();
AnimationCurve scaleX = new AnimationCurve();
AnimationCurve scaleY = new AnimationCurve();
AnimationCurve scaleZ = new AnimationCurve();
for (int k = 0; k < keyframeInput.Length; k++) {
scaleX.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.x, interpolationMode));
scaleY.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.y, interpolationMode));
scaleZ.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.z, interpolationMode));
}
result.clip.SetCurve(relativePath, typeof(Transform), "localScale.x", scaleX);
result.clip.SetCurve(relativePath, typeof(Transform), "localScale.y", scaleY);
result.clip.SetCurve(relativePath, typeof(Transform), "localScale.z", scaleZ);
break;
case "weights":
GLTFNode.ImportResult skinnedMeshNode = nodes[channel.target.node.Value];
SkinnedMeshRenderer skinnedMeshRenderer = skinnedMeshNode.transform.GetComponent<SkinnedMeshRenderer>();
int numberOfBlendShapes = skinnedMeshRenderer.sharedMesh.blendShapeCount;
AnimationCurve[] blendShapeCurves = new AnimationCurve[numberOfBlendShapes];
for(int j = 0; j < numberOfBlendShapes; ++j) {
blendShapeCurves[j] = new AnimationCurve();
}
float[] weights = accessors[sampler.output].ReadFloat().ToArray();
float[] weightValues = new float[keyframeInput.Length];
float[] previouslyKeyedValues = new float[numberOfBlendShapes];
// Reference for my future self:
// keyframeInput.Length = number of keyframes
// keyframeInput[ k ] = timestamp of keyframe
// weights.Length = number of keyframes * number of blendshapes
// weights[ j ] = actual animated weight of a specific blend shape
// (index into weights[] array accounts for keyframe index and blend shape index)
for(int k = 0; k < keyframeInput.Length; ++k) {
for(int j = 0; j < numberOfBlendShapes; ++j) {
int weightIndex = (k * numberOfBlendShapes) + j;
weightValues[k] = weights[weightIndex];
bool addKey = true;
if(importSettings.animationSettings.compressBlendShapeKeyFrames) {
if(k == 0 || !Mathf.Approximately(weightValues[k], previouslyKeyedValues[j])) {
if(k > 0) {
weightValues[k-1] = previouslyKeyedValues[j];
blendShapeCurves[j].AddKey(CreateKeyframe(k-1, keyframeInput, weightValues, x => x, interpolationMode));
}
addKey = true;
previouslyKeyedValues[j] = weightValues[k];
} else {
addKey = false;
}
}
if(addKey) {
blendShapeCurves[j].AddKey(CreateKeyframe(k, keyframeInput, weightValues, x => x, interpolationMode));
}
}
}
for(int j = 0; j < numberOfBlendShapes; ++j) {
string propertyName = "blendShape." + skinnedMeshRenderer.sharedMesh.GetBlendShapeName(j);
result.clip.SetCurve(relativePath, typeof(SkinnedMeshRenderer), propertyName, blendShapeCurves[j]);
}
break;
}
}
return result;
}
public static Keyframe CreateKeyframe<T>(int index, float[] timeArray, T[] valueArray, Func<T, float> getValue, InterpolationMode interpolationMode) {
float time = timeArray[index];
Keyframe keyframe;
#pragma warning disable CS0618
if (interpolationMode == InterpolationMode.STEP) {
keyframe = new Keyframe(time, getValue(valueArray[index]), float.PositiveInfinity, float.PositiveInfinity, 1, 1);
} else if (interpolationMode == InterpolationMode.CUBICSPLINE) {
// @TODO: Find out what the right math is to calculate the tangent/weight values.
float inTangent = getValue(valueArray[index * 3]);
float outTangent = getValue(valueArray[(index * 3) + 2]);
keyframe = new Keyframe(time, getValue(valueArray[(index * 3) + 1]), inTangent, outTangent, 1, 1);
} else { // LINEAR
keyframe = new Keyframe(time, getValue(valueArray[index]), 0, 0, 0, 0);
}
#pragma warning restore CS0618
return keyframe;
}
}
public static class GLTFAnimationExtensions {
public static GLTFAnimation.ImportResult[] Import(this List<GLTFAnimation> animations, GLTFAccessor.ImportResult[] accessors, GLTFNode.ImportResult[] nodes, ImportSettings importSettings) {
if (animations == null) return null;
GLTFAnimation.ImportResult[] results = new GLTFAnimation.ImportResult[animations.Count];
for (int i = 0; i < results.Length; i++) {
results[i] = animations[i].Import(accessors, nodes, importSettings);
if (string.IsNullOrEmpty(results[i].clip.name)) results[i].clip.name = "animation" + i;
}
return results;
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e5a3f1b564768444a879a11c4ae57c8c
timeCreated: 1539024463
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#asset-1
/// <summary> Metadata about the glTF asset </summary>
[Preserve] public class GLTFAsset {
/// <summary> A copyright message suitable for display to credit the content creator. </summary>
public string copyright;
/// <summary> Tool that generated this glTF model. Useful for debugging. </summary>
public string generator;
/// <summary> The glTF version that this asset targets. </summary>
[JsonProperty(Required = Required.Always)] public string version;
/// <summary> The minimum glTF version that this asset targets. </summary>
public string minVersion;
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 5a649aef7d3e3c74aaa7be7d80ccda51
timeCreated: 1539024463
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffer
/// <summary> Contains raw binary data </summary>
[Preserve] public class GLTFBuffer {
[JsonProperty(Required = Required.Always)] public int byteLength;
public string uri;
public string name;
[JsonIgnore] private const string embeddedPrefix = "data:application/octet-stream;base64,";
[JsonIgnore] private const string embeddedPrefix2 = "data:application/gltf-buffer;base64,";
public class ImportResult {
public Stream stream;
public long startOffset;
public void Dispose() {
stream.Dispose();
}
}
#region Import
/// <param name="filepath">Filepath if loading from a file</param>
/// <param name="bytefile">bytes if loading from raw bytes</param>
public ImportResult Import(string filepath, byte[] bytefile, long binChunkStart) {
ImportResult result = new ImportResult();
if (uri == null) {
// Load entire file
if (string.IsNullOrEmpty(filepath)) result.stream = new MemoryStream(bytefile);
else result.stream = File.OpenRead(filepath);
result.startOffset = binChunkStart + 8;
result.stream.Position = result.startOffset;
} else if (uri.StartsWith(embeddedPrefix)) {
// Load embedded
string b64 = uri.Substring(embeddedPrefix.Length, uri.Length - embeddedPrefix.Length);
byte[] bytes = Convert.FromBase64String(b64);
result.stream = new MemoryStream(bytes);
} else if (uri.StartsWith(embeddedPrefix2)) {
// Load embedded
string b64 = uri.Substring(embeddedPrefix2.Length, uri.Length - embeddedPrefix2.Length);
byte[] bytes = Convert.FromBase64String(b64);
result.stream = new MemoryStream(bytes);
} else {
// Load URI
string directoryRoot = Directory.GetParent(filepath).ToString() + "/";
result.stream = File.OpenRead(directoryRoot + uri);
result.startOffset = result.stream.Length - byteLength;
}
return result;
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
/// <param name="filepath">Filepath if loading from a file</param>
/// <param name="bytefile">bytes if loading from raw bytes</param>
public ImportTask(List<GLTFBuffer> buffers, string filepath, byte[] bytefile, long binChunkStart) : base() {
task = new Task(() => {
Result = new ImportResult[buffers.Count];
for (int i = 0; i < Result.Length; i++) {
Result[i] = buffers[i].Import(filepath, bytefile, binChunkStart);
}
});
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 8a0e8a023d948d340989746154fbe314
timeCreated: 1539024463
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#bufferview
/// <summary> Defines sections within the Buffer </summary>
[Preserve] public class GLTFBufferView {
[JsonProperty(Required = Required.Always)] public int buffer;
[JsonProperty(Required = Required.Always)] public int byteLength;
public int byteOffset = 0;
public int? byteStride;
/// <summary> OpenGL buffer target </summary>
public int? target;
public string name;
public class ImportResult {
public Stream stream;
public int byteOffset;
public int byteLength;
public int? byteStride;
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
public ImportTask(List<GLTFBufferView> bufferViews, GLTFBuffer.ImportTask bufferTask) : base(bufferTask) {
task = new Task(() => {
Result = new ImportResult[bufferViews.Count];
for (int i = 0; i < Result.Length; i++) {
GLTFBuffer.ImportResult buffer = bufferTask.Result[bufferViews[i].buffer];
ImportResult result = new ImportResult();
result.stream = buffer.stream;
result.byteOffset = bufferViews[i].byteOffset;
result.byteOffset += (int)buffer.startOffset;
result.byteLength = bufferViews[i].byteLength;
result.byteStride = bufferViews[i].byteStride;
Result[i] = result;
}
});
}
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f77c029d61e83644b812d926bc8a7706
timeCreated: 1539029690
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Siccity.GLTFUtility.Converters;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#camera
[Preserve] public class GLTFCamera {
#region Serialization
public Orthographic orthographic;
public Perspective perspective;
[JsonProperty(Required = Required.Always), JsonConverter(typeof(EnumConverter))] public CameraType type;
public string name;
[Preserve] public class Orthographic {
[JsonProperty(Required = Required.Always)] public float xmag;
[JsonProperty(Required = Required.Always)] public float ymag;
[JsonProperty(Required = Required.Always)] public float zfar;
[JsonProperty(Required = Required.Always)] public float znear;
}
[Preserve] public class Perspective {
public float? aspectRatio;
[JsonProperty(Required = Required.Always)] public float yfov;
public float? zfar;
[JsonProperty(Required = Required.Always)] public float znear;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ecd6a04a0989ee34f9f188e57c464ac3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.Events;
namespace Siccity.GLTFUtility {
[Serializable]
public class GLTFExtrasProcessor
{
public virtual void ProcessExtras(GameObject importedObject, AnimationClip[] animations, JObject extras)
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1e414d9d11bb09448a2424d6ad278db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#image
[Preserve] public class GLTFImage {
/// <summary>
/// The uri of the image.
/// Relative paths are relative to the .gltf file.
/// Instead of referencing an external file, the uri can also be a data-uri.
/// The image format must be jpg or png.
/// </summary>
public string uri;
/// <summary> Either "image/jpeg" or "image/png" </summary>
public string mimeType;
public int? bufferView;
public string name;
public class ImportResult {
public byte[] bytes;
public string path;
public ImportResult(byte[] bytes, string path = null) {
this.bytes = bytes;
this.path = path;
}
public IEnumerator CreateTextureAsync(bool linear, Action<Texture2D> onFinish, Action<float> onProgress = null) {
if (!string.IsNullOrEmpty(path)) {
#if UNITY_EDITOR
// Load textures from asset database if we can
Texture2D assetTexture = UnityEditor.AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D)) as Texture2D;
if (assetTexture != null) {
onFinish(assetTexture);
if (onProgress != null) onProgress(1f);
yield break;
}
#endif
#if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
path = "File://" + path;
#endif
// TODO: Support linear/sRGB textures
using(UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(path, true)) {
UnityWebRequestAsyncOperation operation = uwr.SendWebRequest();
float progress = 0;
while (!operation.isDone) {
if (progress != uwr.downloadProgress) {
if (onProgress != null) onProgress(uwr.downloadProgress);
}
yield return null;
}
if (onProgress != null) onProgress(1f);
#if UNITY_2020_2_OR_NEWER
if(uwr.result == UnityWebRequest.Result.ConnectionError ||
uwr.result == UnityWebRequest.Result.ProtocolError)
#else
if(uwr.isNetworkError || uwr.isHttpError)
#endif
{
Debug.LogError("GLTFImage.cs ToTexture2D() ERROR: " + uwr.error);
} else {
Texture2D tex = DownloadHandlerTexture.GetContent(uwr);
tex.name = Path.GetFileNameWithoutExtension(path);
onFinish(tex);
}
uwr.Dispose();
}
} else {
Texture2D tex = new Texture2D(2, 2, TextureFormat.ARGB32, true, linear);
if (!tex.LoadImage(bytes)) {
Debug.Log("mimeType not supported");
yield break;
} else onFinish(tex);
}
}
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
public ImportTask(List<GLTFImage> images, string directoryRoot, GLTFBufferView.ImportTask bufferViewTask) : base(bufferViewTask) {
task = new Task(() => {
// No images
if (images == null) return;
Result = new ImportResult[images.Count];
for (int i = 0; i < images.Count; i++) {
string fullUri = directoryRoot + images[i].uri;
if (!string.IsNullOrEmpty(images[i].uri)) {
if (File.Exists(fullUri)) {
// If the file is found at fullUri, read it
byte[] bytes = File.ReadAllBytes(fullUri);
Result[i] = new ImportResult(bytes, fullUri);
} else if (images[i].uri.StartsWith("data:")) {
// If the image is embedded, find its Base64 content and save as byte array
string content = images[i].uri.Split(',').Last();
byte[] imageBytes = Convert.FromBase64String(content);
Result[i] = new ImportResult(imageBytes);
}
} else if (images[i].bufferView.HasValue && !string.IsNullOrEmpty(images[i].mimeType)) {
GLTFBufferView.ImportResult view = bufferViewTask.Result[images[i].bufferView.Value];
byte[] bytes = new byte[view.byteLength];
view.stream.Position = view.byteOffset;
view.stream.Read(bytes, 0, view.byteLength);
Result[i] = new ImportResult(bytes);
} else {
Debug.Log("Couldn't find texture at " + fullUri);
}
}
});
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5361c1ef422e7a9469e3fea3cb895c64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,318 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Siccity.GLTFUtility.Converters;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Scripting;
using Newtonsoft.Json.Linq;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#material
[Preserve] public class GLTFMaterial {
#if UNITY_EDITOR
public static Material defaultMaterial { get { return _defaultMaterial != null ? _defaultMaterial : _defaultMaterial = UnityEditor.AssetDatabase.GetBuiltinExtraResource<Material>("Default-Material.mat"); } }
private static Material _defaultMaterial;
#else
public static Material defaultMaterial { get { return null; } }
#endif
public string name;
public PbrMetalRoughness pbrMetallicRoughness;
public TextureInfo normalTexture;
public TextureInfo occlusionTexture;
public TextureInfo emissiveTexture;
[JsonConverter(typeof(ColorRGBConverter))] public Color emissiveFactor = Color.black;
[JsonConverter(typeof(EnumConverter))] public AlphaMode alphaMode = AlphaMode.OPAQUE;
public float alphaCutoff = 0.5f;
public bool doubleSided = false;
public Extensions extensions;
public JObject extras;
public class ImportResult {
public Material material;
}
public IEnumerator CreateMaterial(GLTFTexture.ImportResult[] textures, ShaderSettings shaderSettings, Action<Material> onFinish) {
Material mat = null;
IEnumerator en = null;
// Load metallic-roughness materials
if (pbrMetallicRoughness != null) {
en = pbrMetallicRoughness.CreateMaterial(textures, alphaMode, shaderSettings, x => mat = x);
while (en.MoveNext()) { yield return null; };
}
// Load specular-glossiness materials
else if (extensions != null && extensions.KHR_materials_pbrSpecularGlossiness != null) {
en = extensions.KHR_materials_pbrSpecularGlossiness.CreateMaterial(textures, alphaMode, shaderSettings, x => mat = x);
while (en.MoveNext()) { yield return null; };
}
// Load fallback material
else mat = new Material(Shader.Find("Standard"));
// Normal texture
if (normalTexture != null) {
en = TryGetTexture(textures, normalTexture, true, tex => {
if (tex != null) {
mat.SetTexture("_BumpMap", tex);
mat.EnableKeyword("_NORMALMAP");
mat.SetFloat("_BumpScale", normalTexture.scale);
if (normalTexture.extensions != null) {
normalTexture.extensions.Apply(normalTexture, mat, "_BumpMap");
}
}
});
while (en.MoveNext()) { yield return null; };
}
// Occlusion texture
if (occlusionTexture != null) {
en = TryGetTexture(textures, occlusionTexture, true, tex => {
if (tex != null) {
mat.SetTexture("_OcclusionMap", tex);
if (occlusionTexture.extensions != null) {
occlusionTexture.extensions.Apply(occlusionTexture, mat, "_OcclusionMap");
}
}
});
while (en.MoveNext()) { yield return null; };
}
// Emissive factor
if (emissiveFactor != Color.black) {
mat.SetColor("_EmissionColor", emissiveFactor);
mat.EnableKeyword("_EMISSION");
}
// Emissive texture
if (emissiveTexture != null) {
en = TryGetTexture(textures, emissiveTexture, false, tex => {
if (tex != null) {
mat.SetTexture("_EmissionMap", tex);
mat.EnableKeyword("_EMISSION");
if (emissiveTexture.extensions != null) {
emissiveTexture.extensions.Apply(emissiveTexture, mat, "_EmissionMap");
}
}
});
while (en.MoveNext()) { yield return null; };
}
if (alphaMode == AlphaMode.MASK) {
mat.SetFloat("_AlphaCutoff", alphaCutoff);
}
mat.name = name;
onFinish(mat);
}
public static IEnumerator TryGetTexture(GLTFTexture.ImportResult[] textures, TextureInfo texture, bool linear, Action<Texture2D> onFinish, Action<float> onProgress = null) {
if (texture == null || texture.index < 0) {
if (onProgress != null) onProgress(1f);
onFinish(null);
}
if (textures == null) {
if (onProgress != null) onProgress(1f);
onFinish(null);
}
if (textures.Length <= texture.index) {
Debug.LogWarning("Attempted to get texture index " + texture.index + " when only " + textures.Length + " exist");
if (onProgress != null) onProgress(1f);
onFinish(null);
}
IEnumerator en = textures[texture.index].GetTextureCached(linear, onFinish, onProgress);
while (en.MoveNext()) { yield return null; };
}
[Preserve] public class Extensions {
public PbrSpecularGlossiness KHR_materials_pbrSpecularGlossiness = null;
}
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#pbrmetallicroughness
[Preserve] public class PbrMetalRoughness {
[JsonConverter(typeof(ColorRGBAConverter))] public Color baseColorFactor = Color.white;
public TextureInfo baseColorTexture;
public float metallicFactor = 1f;
public float roughnessFactor = 1f;
public TextureInfo metallicRoughnessTexture;
public IEnumerator CreateMaterial(GLTFTexture.ImportResult[] textures, AlphaMode alphaMode, ShaderSettings shaderSettings, Action<Material> onFinish) {
// Shader
Shader sh = null;
if (alphaMode == AlphaMode.BLEND) sh = shaderSettings.MetallicBlend;
else sh = shaderSettings.Metallic;
// Material
Material mat = new Material(sh);
mat.color = baseColorFactor;
mat.SetFloat("_Metallic", metallicFactor);
mat.SetFloat("_Roughness", roughnessFactor);
// Assign textures
if (textures != null) {
// Base color texture
if (baseColorTexture != null && baseColorTexture.index >= 0) {
if (textures.Length <= baseColorTexture.index) {
Debug.LogWarning("Attempted to get basecolor texture index " + baseColorTexture.index + " when only " + textures.Length + " exist");
} else {
IEnumerator en = textures[baseColorTexture.index].GetTextureCached(false, tex => {
if (tex != null) {
mat.SetTexture("_MainTex", tex);
if (baseColorTexture.extensions != null) {
baseColorTexture.extensions.Apply(baseColorTexture, mat, "_MainTex");
}
}
});
while (en.MoveNext()) { yield return null; };
}
}
// Metallic roughness texture
if (metallicRoughnessTexture != null && metallicRoughnessTexture.index >= 0) {
if (textures.Length <= metallicRoughnessTexture.index) {
Debug.LogWarning("Attempted to get metallicRoughness texture index " + metallicRoughnessTexture.index + " when only " + textures.Length + " exist");
} else {
IEnumerator en = TryGetTexture(textures, metallicRoughnessTexture, true, tex => {
if (tex != null) {
mat.SetTexture("_MetallicGlossMap", tex);
mat.EnableKeyword("_METALLICGLOSSMAP");
if (metallicRoughnessTexture.extensions != null) {
metallicRoughnessTexture.extensions.Apply(metallicRoughnessTexture, mat, "_MetallicGlossMap");
}
}
});
while (en.MoveNext()) { yield return null; };
}
}
}
// After the texture and color is extracted from the glTFObject
if (mat.HasProperty("_BaseMap")) mat.SetTexture("_BaseMap", mat.mainTexture);
if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", baseColorFactor);
onFinish(mat);
}
}
[Preserve] public class PbrSpecularGlossiness {
/// <summary> The reflected diffuse factor of the material </summary>
[JsonConverter(typeof(ColorRGBAConverter))] public Color diffuseFactor = Color.white;
/// <summary> The diffuse texture </summary>
public TextureInfo diffuseTexture;
/// <summary> The reflected diffuse factor of the material </summary>
[JsonConverter(typeof(ColorRGBConverter))] public Color specularFactor = Color.white;
/// <summary> The glossiness or smoothness of the material </summary>
public float glossinessFactor = 1f;
/// <summary> The specular-glossiness texture </summary>
public TextureInfo specularGlossinessTexture;
public IEnumerator CreateMaterial(GLTFTexture.ImportResult[] textures, AlphaMode alphaMode, ShaderSettings shaderSettings, Action<Material> onFinish) {
// Shader
Shader sh = null;
if (alphaMode == AlphaMode.BLEND) sh = shaderSettings.SpecularBlend;
else sh = shaderSettings.Specular;
// Material
Material mat = new Material(sh);
mat.color = diffuseFactor;
mat.SetColor("_SpecColor", specularFactor);
mat.SetFloat("_GlossyReflections", glossinessFactor);
// Assign textures
if (textures != null) {
// Diffuse texture
if (diffuseTexture != null) {
if (textures.Length <= diffuseTexture.index) {
Debug.LogWarning("Attempted to get diffuseTexture texture index " + diffuseTexture.index + " when only " + textures.Length + " exist");
} else {
IEnumerator en = textures[diffuseTexture.index].GetTextureCached(false, tex => {
if (tex != null) {
mat.SetTexture("_MainTex", tex);
if (diffuseTexture.extensions != null) {
diffuseTexture.extensions.Apply(diffuseTexture, mat, "_MainTex");
}
}
});
while (en.MoveNext()) { yield return null; };
}
}
// Specular texture
if (specularGlossinessTexture != null) {
if (textures.Length <= specularGlossinessTexture.index) {
Debug.LogWarning("Attempted to get specularGlossinessTexture texture index " + specularGlossinessTexture.index + " when only " + textures.Length + " exist");
} else {
mat.EnableKeyword("_SPECGLOSSMAP");
IEnumerator en = textures[specularGlossinessTexture.index].GetTextureCached(false, tex => {
if (tex != null) {
mat.SetTexture("_SpecGlossMap", tex);
mat.EnableKeyword("_SPECGLOSSMAP");
if (specularGlossinessTexture.extensions != null) {
specularGlossinessTexture.extensions.Apply(specularGlossinessTexture, mat, "_SpecGlossMap");
}
}
});
while (en.MoveNext()) { yield return null; };
}
}
}
onFinish(mat);
}
}
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#normaltextureinfo
[Preserve] public class TextureInfo {
[JsonProperty(Required = Required.Always)] public int index;
public int texCoord = 0;
public float scale = 1;
public Extensions extensions;
[Preserve] public class Extensions {
public KHR_texture_transform KHR_texture_transform;
public void Apply(GLTFMaterial.TextureInfo texInfo, Material material, string textureSamplerName) {
// TODO: check if GLTFObject has extensionUsed/extensionRequired for these extensions
if (KHR_texture_transform != null) {
KHR_texture_transform.Apply(texInfo, material, textureSamplerName);
}
}
}
public interface IExtension {
void Apply(GLTFMaterial.TextureInfo texInfo, Material material, string textureSamplerName);
}
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
private List<GLTFMaterial> materials;
private GLTFTexture.ImportTask textureTask;
private ImportSettings importSettings;
public ImportTask(List<GLTFMaterial> materials, GLTFTexture.ImportTask textureTask, ImportSettings importSettings) : base(textureTask) {
this.materials = materials;
this.textureTask = textureTask;
this.importSettings = importSettings;
task = new Task(() => {
if (materials == null) return;
Result = new ImportResult[materials.Count];
});
}
public override IEnumerator OnCoroutine(Action<float> onProgress = null) {
// No materials
if (materials == null) {
if (onProgress != null) onProgress.Invoke(1f);
IsCompleted = true;
yield break;
}
for (int i = 0; i < Result.Length; i++) {
Result[i] = new ImportResult();
IEnumerator en = materials[i].CreateMaterial(textureTask.Result, importSettings.shaderOverrides, x => Result[i].material = x);
while (en.MoveNext()) { yield return null; };
if (Result[i].material.name == null) Result[i].material.name = "material" + i;
if (onProgress != null) onProgress.Invoke((float) (i + 1) / (float) Result.Length);
yield return null;
}
IsCompleted = true;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9293e8f93131ab84f9bcf4cc5ab769a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,421 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#mesh
[Preserve] public class GLTFMesh {
#region Serialization
[JsonProperty(Required = Required.Always)] public List<GLTFPrimitive> primitives;
/// <summary> Morph target weights </summary>
public List<float> weights;
public string name;
public Extras extras;
public class Extras {
/// <summary>
/// Morph target names. Not part of the official spec, but pretty much a standard.
/// Discussed here https://github.com/KhronosGroup/glTF/issues/1036
/// </summary>
public string[] targetNames;
}
#endregion
#region Import
public class ImportResult {
public Material[] materials;
public Mesh mesh;
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
private class MeshData {
string name;
List<Vector3> normals = new List<Vector3>();
List<List<int>> submeshTris = new List<List<int>>();
List<RenderingMode> submeshTrisMode = new List<RenderingMode>();
List<Vector3> verts = new List<Vector3>();
List<Vector4> tangents = new List<Vector4>();
List<Color> colors = new List<Color>();
List<BoneWeight> weights = null;
List<Vector2> uv1 = null;
List<Vector2> uv2 = null;
List<Vector2> uv3 = null;
List<Vector2> uv4 = null;
List<Vector2> uv5 = null;
List<Vector2> uv6 = null;
List<Vector2> uv7 = null;
List<Vector2> uv8 = null;
List<BlendShape> blendShapes = new List<BlendShape>();
List<int> submeshVertexStart = new List<int>();
private class BlendShape {
public string name;
public Vector3[] pos, norm, tan;
}
public MeshData(GLTFMesh gltfMesh, GLTFAccessor.ImportResult[] accessors, GLTFBufferView.ImportResult[] bufferViews) {
name = gltfMesh.name;
if (gltfMesh.primitives.Count == 0) {
Debug.LogWarning("0 primitives in mesh");
} else {
for (int i = 0; i < gltfMesh.primitives.Count; i++) {
GLTFPrimitive primitive = gltfMesh.primitives[i];
// Load draco mesh
if (primitive.extensions != null && primitive.extensions.KHR_draco_mesh_compression != null) {
GLTFPrimitive.DracoMeshCompression draco = primitive.extensions.KHR_draco_mesh_compression;
GLTFBufferView.ImportResult bufferView = bufferViews[draco.bufferView];
GLTFUtilityDracoLoader loader = new GLTFUtilityDracoLoader();
byte[] buffer = new byte[bufferView.byteLength];
bufferView.stream.Seek(bufferView.byteOffset, System.IO.SeekOrigin.Begin);
bufferView.stream.Read(buffer, 0, bufferView.byteLength);
GLTFUtilityDracoLoader.MeshAttributes attribs = new GLTFUtilityDracoLoader.MeshAttributes(
primitive.extensions.KHR_draco_mesh_compression.attributes.POSITION ?? -1,
primitive.extensions.KHR_draco_mesh_compression.attributes.NORMAL ?? -1,
primitive.extensions.KHR_draco_mesh_compression.attributes.TEXCOORD_0 ?? -1,
primitive.extensions.KHR_draco_mesh_compression.attributes.JOINTS_0 ?? -1,
primitive.extensions.KHR_draco_mesh_compression.attributes.WEIGHTS_0 ?? -1,
primitive.extensions.KHR_draco_mesh_compression.attributes.COLOR_0 ?? -1
);
//Mesh mesh = loader.LoadMesh(buffer, attribs);
GLTFUtilityDracoLoader.AsyncMesh asyncMesh = loader.LoadMesh(buffer, attribs);
if (asyncMesh == null) Debug.LogWarning("Draco mesh couldn't be loaded");
submeshTrisMode.Add(primitive.mode);
// Tris
int vertCount = verts.Count();
submeshTris.Add(asyncMesh.tris.Reverse().Select(x => x + vertCount).ToList());
verts.AddRange(asyncMesh.verts.Select(x => new Vector3(-x.x, x.y, x.z)));
if (asyncMesh.norms != null) {
normals.AddRange(asyncMesh.norms.Select(v => { v.x = -v.x; return v; }));
}
//tangents.AddRange(asyncMesh.tangents.Select(v => { v.y = -v.y; v.z = -v.z; return v; }));
// Weights
if (asyncMesh.boneWeights != null) {
if (weights == null) weights = new List<BoneWeight>();
weights.AddRange(asyncMesh.boneWeights);
}
// BlendShapes not supported yet
/* for (int k = 0; k < mesh.blendShapeCount; k++) {
int frameCount = mesh.GetBlendShapeFrameCount(k);
BlendShape blendShape = new BlendShape();
blendShape.pos = new Vector3[frameCount];
blendShape.norm = new Vector3[frameCount];
blendShape.tan = new Vector3[frameCount];
for (int o = 0; o < frameCount; o++) {
mesh.GetBlendShapeFrameVertices(k, o, blendShape.pos, blendShape.norm, blendShape.tan);
}
blendShapes.Add(blendShape);
} */
// UVs
if (asyncMesh.uv != null) {
if (uv1 == null) uv1 = new List<Vector2>();
uv1.AddRange(asyncMesh.uv.Select(x => new Vector2(x.x, -x.y)));
}
}
// Load normal mesh
else {
int vertStartIndex = verts.Count;
submeshVertexStart.Add(vertStartIndex);
// Verts - (X points left in GLTF)
if (primitive.attributes.POSITION.HasValue) {
IEnumerable<Vector3> newVerts = accessors[primitive.attributes.POSITION.Value].ReadVec3(true).Select(v => { v.x = -v.x; return v; });
verts.AddRange(newVerts);
}
int vertCount = verts.Count;
// Tris - (Invert all triangles. Instead of flipping each triangle, just flip the entire array. Much easier)
if (primitive.indices.HasValue) {
submeshTris.Add(new List<int>(accessors[primitive.indices.Value].ReadInt().Reverse().Select(x => x + vertStartIndex)));
submeshTrisMode.Add(primitive.mode);
}
/// Normals - (X points left in GLTF)
if (primitive.attributes.NORMAL.HasValue) {
normals.AddRange(accessors[primitive.attributes.NORMAL.Value].ReadVec3(true).Select(v => { v.x = -v.x; return v; }));
}
// Tangents - (X points left in GLTF)
if (primitive.attributes.TANGENT.HasValue) {
tangents.AddRange(accessors[primitive.attributes.TANGENT.Value].ReadVec4(true).Select(v => { v.y = -v.y; v.z = -v.z; return v; }));
}
// Vertex colors
if (primitive.attributes.COLOR_0.HasValue) {
colors.AddRange(accessors[primitive.attributes.COLOR_0.Value].ReadColor());
}
// Weights
if (primitive.attributes.WEIGHTS_0.HasValue && primitive.attributes.JOINTS_0.HasValue) {
Vector4[] weights0 = accessors[primitive.attributes.WEIGHTS_0.Value].ReadVec4(true);
Vector4[] joints0 = accessors[primitive.attributes.JOINTS_0.Value].ReadVec4();
if (joints0.Length == weights0.Length) {
BoneWeight[] boneWeights = new BoneWeight[weights0.Length];
for (int k = 0; k < boneWeights.Length; k++) {
NormalizeWeights(ref weights0[k]);
boneWeights[k].weight0 = weights0[k].x;
boneWeights[k].weight1 = weights0[k].y;
boneWeights[k].weight2 = weights0[k].z;
boneWeights[k].weight3 = weights0[k].w;
boneWeights[k].boneIndex0 = Mathf.RoundToInt(joints0[k].x);
boneWeights[k].boneIndex1 = Mathf.RoundToInt(joints0[k].y);
boneWeights[k].boneIndex2 = Mathf.RoundToInt(joints0[k].z);
boneWeights[k].boneIndex3 = Mathf.RoundToInt(joints0[k].w);
}
if (weights == null) weights = new List<BoneWeight>(new BoneWeight[vertCount - boneWeights.Length]);
weights.AddRange(boneWeights);
} else Debug.LogWarning("WEIGHTS_0 and JOINTS_0 not same length. Skipped");
} else {
if (weights != null) weights.AddRange(new BoneWeight[vertCount - weights.Count]);
}
// UVs
ReadUVs(ref uv1, accessors, primitive.attributes.TEXCOORD_0, vertCount);
ReadUVs(ref uv2, accessors, primitive.attributes.TEXCOORD_1, vertCount);
ReadUVs(ref uv3, accessors, primitive.attributes.TEXCOORD_2, vertCount);
ReadUVs(ref uv4, accessors, primitive.attributes.TEXCOORD_3, vertCount);
ReadUVs(ref uv5, accessors, primitive.attributes.TEXCOORD_4, vertCount);
ReadUVs(ref uv6, accessors, primitive.attributes.TEXCOORD_5, vertCount);
ReadUVs(ref uv7, accessors, primitive.attributes.TEXCOORD_6, vertCount);
ReadUVs(ref uv8, accessors, primitive.attributes.TEXCOORD_7, vertCount);
}
}
bool hasTargetNames = gltfMesh.extras != null && gltfMesh.extras.targetNames != null;
if (hasTargetNames) {
if (gltfMesh.primitives.All(x => x.targets.Count != gltfMesh.extras.targetNames.Length)) {
Debug.LogWarning("Morph target names found in mesh " + name + " but array length does not match primitive morph target array length");
hasTargetNames = false;
}
}
// Read blend shapes after knowing final vertex count
int finalVertCount = verts.Count;
for (int i = 0; i < gltfMesh.primitives.Count; i++) {
GLTFPrimitive primitive = gltfMesh.primitives[i];
if (primitive.targets != null) {
for (int k = 0; k < primitive.targets.Count; k++) {
BlendShape blendShape = new BlendShape();
blendShape.pos = GetMorphWeights(primitive.targets[k].POSITION, submeshVertexStart[i], finalVertCount, accessors);
blendShape.norm = GetMorphWeights(primitive.targets[k].NORMAL, submeshVertexStart[i], finalVertCount, accessors);
blendShape.tan = GetMorphWeights(primitive.targets[k].TANGENT, submeshVertexStart[i], finalVertCount, accessors);
if (hasTargetNames) blendShape.name = gltfMesh.extras.targetNames[k];
else blendShape.name = "morph-" + blendShapes.Count;
blendShapes.Add(blendShape);
}
}
}
}
}
private Vector3[] GetMorphWeights(int? accessor, int vertStartIndex, int vertCount, GLTFAccessor.ImportResult[] accessors) {
if (accessor.HasValue) {
if (accessors[accessor.Value] == null) {
Debug.LogWarning("Accessor is null");
return new Vector3[vertCount];
}
Vector3[] accessorData = accessors[accessor.Value].ReadVec3(true).Select(v => { v.x = -v.x; return v; }).ToArray();
if (accessorData.Length != vertCount) {
Vector3[] resized = new Vector3[vertCount];
Array.Copy(accessorData, 0, resized, vertStartIndex, accessorData.Length);
return resized;
} else return accessorData;
} else return new Vector3[vertCount];
}
public Mesh ToMesh() {
Mesh mesh = new Mesh();
if (verts.Count >= ushort.MaxValue) mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.vertices = verts.ToArray();
mesh.subMeshCount = submeshTris.Count;
var onlyTriangles = true;
for (int i = 0; i < submeshTris.Count; i++) {
switch (submeshTrisMode[i]) {
case RenderingMode.POINTS:
mesh.SetIndices(submeshTris[i].ToArray(), MeshTopology.Points, i);
onlyTriangles = false;
break;
case RenderingMode.LINES:
mesh.SetIndices(submeshTris[i].ToArray(), MeshTopology.Lines, i);
onlyTriangles = false;
break;
case RenderingMode.LINE_STRIP:
mesh.SetIndices(submeshTris[i].ToArray(), MeshTopology.LineStrip, i);
onlyTriangles = false;
break;
case RenderingMode.TRIANGLES:
mesh.SetTriangles(submeshTris[i].ToArray(), i);
break;
default:
Debug.LogWarning("GLTF rendering mode " + submeshTrisMode[i] + " not supported.");
return null;
}
}
mesh.colors = colors.ToArray();
if (uv1 != null) mesh.uv = uv1.ToArray();
if (uv2 != null) mesh.uv2 = uv2.ToArray();
if (uv3 != null) mesh.uv3 = uv3.ToArray();
if (uv4 != null) mesh.uv4 = uv4.ToArray();
if (uv5 != null) mesh.uv5 = uv5.ToArray();
if (uv6 != null) mesh.uv6 = uv6.ToArray();
if (uv7 != null) mesh.uv7 = uv7.ToArray();
if (uv8 != null) mesh.uv8 = uv8.ToArray();
if (weights != null) mesh.boneWeights = weights.ToArray();
mesh.RecalculateBounds();
// Blend shapes
for (int i = 0; i < blendShapes.Count; i++) {
mesh.AddBlendShapeFrame(blendShapes[i].name, 1f, blendShapes[i].pos, blendShapes[i].norm, blendShapes[i].tan);
}
if (normals.Count == 0 && onlyTriangles)
mesh.RecalculateNormals();
else
mesh.normals = normals.ToArray();
if (tangents.Count == 0 && onlyTriangles)
mesh.RecalculateTangents();
else
mesh.tangents = tangents.ToArray();
mesh.name = name;
return mesh;
}
public void NormalizeWeights(ref Vector4 weights) {
float total = weights.x + weights.y + weights.z + weights.w;
float mult = 1f / total;
weights.x *= mult;
weights.y *= mult;
weights.z *= mult;
weights.w *= mult;
}
private void ReadUVs(ref List<Vector2> uvs, GLTFAccessor.ImportResult[] accessors, int? texcoord, int vertCount) {
// If there are no valid texcoords
if (!texcoord.HasValue) {
// If there are already uvs, add some empty filler uvs so it still matches the vertex array
if (uvs != null) uvs.AddRange(new Vector2[vertCount - uvs.Count]);
return;
}
Vector2[] _uvs = accessors[texcoord.Value].ReadVec2(true);
FlipY(ref _uvs);
if (uvs == null) uvs = new List<Vector2>(_uvs);
else uvs.AddRange(_uvs);
}
public void FlipY(ref Vector2[] uv) {
for (int i = 0; i < uv.Length; i++) {
uv[i].y = 1 - uv[i].y;
}
}
}
private MeshData[] meshData;
private List<GLTFMesh> meshes;
private GLTFMaterial.ImportTask materialTask;
public ImportTask(List<GLTFMesh> meshes, GLTFAccessor.ImportTask accessorTask, GLTFBufferView.ImportTask bufferViewTask, GLTFMaterial.ImportTask materialTask, ImportSettings importSettings) : base(accessorTask, materialTask) {
this.meshes = meshes;
this.materialTask = materialTask;
task = new Task(() => {
if (meshes == null) return;
meshData = new MeshData[meshes.Count];
for (int i = 0; i < meshData.Length; i++) {
meshData[i] = new MeshData(meshes[i], accessorTask.Result, bufferViewTask.Result);
}
});
}
public override IEnumerator OnCoroutine(Action<float> onProgress = null) {
// No mesh
if (meshData == null) {
if (onProgress != null) onProgress.Invoke(1f);
IsCompleted = true;
yield break;
}
Result = new ImportResult[meshData.Length];
for (int i = 0; i < meshData.Length; i++) {
if (meshData[i] == null) {
Debug.LogWarning("Mesh " + i + " import error");
continue;
}
Result[i] = new ImportResult();
Result[i].mesh = meshData[i].ToMesh();
Result[i].materials = new Material[meshes[i].primitives.Count];
for (int k = 0; k < meshes[i].primitives.Count; k++) {
int? matIndex = meshes[i].primitives[k].material;
if (matIndex.HasValue && materialTask.Result != null && materialTask.Result.Count() > matIndex.Value) {
GLTFMaterial.ImportResult matImport = materialTask.Result[matIndex.Value];
if (matImport != null) Result[i].materials[k] = matImport.material;
else {
Debug.LogWarning("Mesh[" + i + "].matIndex points to null material (index " + matIndex.Value + ")");
Result[i].materials[k] = GLTFMaterial.defaultMaterial;
}
} else {
Result[i].materials[k] = GLTFMaterial.defaultMaterial;
}
}
if (string.IsNullOrEmpty(Result[i].mesh.name)) Result[i].mesh.name = "mesh" + i;
if (onProgress != null) onProgress.Invoke((float) (i + 1) / (float) meshData.Length);
yield return null;
}
IsCompleted = true;
}
}
#endregion
#region Export
public class ExportResult : GLTFMesh {
[JsonIgnore] public Mesh mesh;
}
public static List<ExportResult> Export(List<GLTFNode.ExportResult> nodes) {
List<ExportResult> results = new List<ExportResult>();
for (int i = 0; i < nodes.Count; i++) {
if (nodes[i].filter) {
Mesh mesh = nodes[i].filter.sharedMesh;
if (mesh) {
nodes[i].mesh = results.Count;
results.Add(Export(mesh));
}
}
}
return results;
}
public static ExportResult Export(Mesh mesh) {
ExportResult result = new ExportResult();
result.name = mesh.name;
result.primitives = new List<GLTFPrimitive>();
for (int i = 0; i < mesh.subMeshCount; i++) {
GLTFPrimitive primitive = new GLTFPrimitive();
result.primitives.Add(primitive);
}
return result;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1ddbab1e140e1234fae1dc56d0d0f479
timeCreated: 1539024463
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,209 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Siccity.GLTFUtility.Converters;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#node
[Preserve] public class GLTFNode {
#region Serialization
public string name;
/// <summary> Indices of child nodes </summary>
public int[] children;
/// <summary> Local TRS </summary>
[JsonConverter(typeof(Matrix4x4Converter))] public Matrix4x4 matrix = Matrix4x4.identity;
/// <summary> Local position </summary>
[JsonConverter(typeof(TranslationConverter))] public Vector3 translation = Vector3.zero;
/// <summary> Local rotation </summary>
[JsonConverter(typeof(QuaternionConverter))] public Quaternion rotation = Quaternion.identity;
/// <summary> Local scale </summary>
[JsonConverter(typeof(Vector3Converter))] public Vector3 scale = Vector3.one;
public int? mesh;
public int? skin;
public int? camera;
public int? weights;
public bool ShouldSerializetranslation() { return translation != Vector3.zero; }
public bool ShouldSerializerotation() { return rotation != Quaternion.identity; }
public bool ShouldSerializescale() { return scale != Vector3.one; }
#endregion
#region Import
public class ImportResult {
public int? parent;
public int[] children;
public Transform transform;
public bool IsRoot { get { return !parent.HasValue; } }
}
/// <summary> Set local position, rotation and scale </summary>
public void ApplyTRS(Transform transform) {
if(matrix!=Matrix4x4.identity)
matrix.UnpackTRS(ref translation, ref rotation, ref scale);
transform.localPosition = translation;
transform.localRotation = rotation;
transform.localScale = scale;
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
List<GLTFNode> nodes;
GLTFMesh.ImportTask meshTask;
GLTFSkin.ImportTask skinTask;
List<GLTFCamera> cameras;
public ImportTask(List<GLTFNode> nodes, GLTFMesh.ImportTask meshTask, GLTFSkin.ImportTask skinTask, List<GLTFCamera> cameras) : base(meshTask, skinTask) {
this.nodes = nodes;
this.meshTask = meshTask;
this.skinTask = skinTask;
this.cameras = cameras;
//task = new Task(() => { });
}
public override IEnumerator OnCoroutine(Action<float> onProgress = null) {
// No nodes
if (nodes == null) {
if (onProgress != null) onProgress.Invoke(1f);
IsCompleted = true;
yield break;
}
Result = new ImportResult[nodes.Count];
// Initialize transforms
for (int i = 0; i < Result.Length; i++) {
Result[i] = new GLTFNode.ImportResult();
Result[i].transform = new GameObject().transform;
Result[i].transform.name = nodes[i].name;
}
// Set up hierarchy
for (int i = 0; i < Result.Length; i++) {
if (nodes[i].children != null) {
int[] children = nodes[i].children;
Result[i].children = children;
for (int k = 0; k < children.Length; k++) {
int childIndex = children[k];
Result[childIndex].parent = i;
Result[childIndex].transform.parent = Result[i].transform;
}
}
}
// Apply TRS
for (int i = 0; i < Result.Length; i++) {
nodes[i].ApplyTRS(Result[i].transform);
}
// Setup components
for (int i = 0; i < Result.Length; i++) {
// Setup mesh
if (nodes[i].mesh.HasValue) {
GLTFMesh.ImportResult meshResult = meshTask.Result[nodes[i].mesh.Value];
if (meshResult == null) continue;
Mesh mesh = meshResult.mesh;
Renderer renderer;
if (nodes[i].skin.HasValue) {
GLTFSkin.ImportResult skin = skinTask.Result[nodes[i].skin.Value];
renderer = skin.SetupSkinnedRenderer(Result[i].transform.gameObject, mesh, Result);
} else if (mesh.blendShapeCount > 0) {
// Blend shapes require skinned mesh renderer
SkinnedMeshRenderer mr = Result[i].transform.gameObject.AddComponent<SkinnedMeshRenderer>();
mr.sharedMesh = mesh;
renderer = mr;
} else {
MeshRenderer mr = Result[i].transform.gameObject.AddComponent<MeshRenderer>();
MeshFilter mf = Result[i].transform.gameObject.AddComponent<MeshFilter>();
renderer = mr;
mf.sharedMesh = mesh;
}
//Materials
renderer.materials = meshResult.materials;
if (string.IsNullOrEmpty(Result[i].transform.name)) Result[i].transform.name = "node" + i;
} else {
if (string.IsNullOrEmpty(Result[i].transform.name)) Result[i].transform.name = "node" + i;
}
// Setup camera
if (nodes[i].camera.HasValue) {
GLTFCamera cameraData = cameras[nodes[i].camera.Value];
Camera camera = Result[i].transform.gameObject.AddComponent<Camera>();
Result[i].transform.localRotation = Result[i].transform.localRotation * Quaternion.Euler(0, 180, 0);
if (cameraData.type == CameraType.orthographic) {
camera.orthographic = true;
camera.nearClipPlane = cameraData.orthographic.znear;
camera.farClipPlane = cameraData.orthographic.zfar;
camera.orthographicSize = cameraData.orthographic.ymag;
} else {
camera.orthographic = false;
camera.nearClipPlane = cameraData.perspective.znear;
if (cameraData.perspective.zfar.HasValue) camera.farClipPlane = cameraData.perspective.zfar.Value;
if (cameraData.perspective.aspectRatio.HasValue) camera.aspect = cameraData.perspective.aspectRatio.Value;
camera.fieldOfView = Mathf.Rad2Deg * cameraData.perspective.yfov;
}
}
}
IsCompleted = true;
}
}
#endregion
#region Export
public class ExportResult : GLTFNode {
[JsonIgnore] public MeshRenderer renderer;
[JsonIgnore] public MeshFilter filter;
[JsonIgnore] public SkinnedMeshRenderer skinnedRenderer;
}
public static List<ExportResult> Export(Transform root) {
List<ExportResult> nodes = new List<ExportResult>();
CreateNodeListRecursive(root, nodes);
return nodes;
}
private static void CreateNodeListRecursive(Transform transform, List<ExportResult> nodes) {
ExportResult node = new ExportResult();
node.name = transform.name;
node.translation = transform.localPosition;
node.rotation = transform.localRotation;
node.scale = transform.localScale;
node.renderer = transform.gameObject.GetComponent<MeshRenderer>();
node.filter = transform.gameObject.GetComponent<MeshFilter>();
node.skinnedRenderer = transform.gameObject.GetComponent<SkinnedMeshRenderer>();
nodes.Add(node);
if (transform.childCount > 0) {
if (transform.childCount > 0) {
node.children = new int[transform.childCount];
for (int i = 0; i < node.children.Length; i++) {
Transform child = transform.GetChild(i);
node.children[i] = nodes.Count;
CreateNodeListRecursive(child, nodes);
}
}
}
}
#endregion
}
public static class GLTFNodeExtensions {
#region Import
/// <summary> Returns the root if there is one, otherwise creates a new empty root </summary>
public static GameObject GetRoot(this GLTFNode.ImportResult[] nodes) {
GLTFNode.ImportResult[] roots = nodes.Where(x => x.IsRoot).ToArray();
if (roots.Length == 1) return roots[0].transform.gameObject;
else {
GameObject root = new GameObject("Root");
for (int i = 0; i < roots.Length; i++) {
roots[i].transform.parent = root.transform;
}
return root;
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 276c161f2b15c0a48b89210deb9a6e24
timeCreated: 1539024181
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
[Preserve] public class GLTFObject {
public int? scene;
[JsonProperty(Required = Required.Always)] public GLTFAsset asset;
public List<GLTFScene> scenes;
public List<GLTFNode> nodes;
public List<GLTFMesh> meshes;
public List<GLTFAnimation> animations;
public List<GLTFBuffer> buffers;
public List<GLTFBufferView> bufferViews;
public List<GLTFAccessor> accessors;
public List<GLTFSkin> skins;
public List<GLTFTexture> textures;
public List<GLTFImage> images;
public List<GLTFMaterial> materials;
public List<GLTFCamera> cameras;
public List<string> extensionsUsed;
public List<string> extensionsRequired;
public JObject extras;
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 84e30c2ce8f61b443800564b827d7395
timeCreated: 1539024463
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#primitive
[Preserve] public class GLTFPrimitive {
[JsonProperty(Required = Required.Always)] public GLTFAttributes attributes;
/// <summary> Rendering mode</summary>
public RenderingMode mode = RenderingMode.TRIANGLES;
public int? indices;
public int? material;
/// <summary> Morph targets </summary>
public List<GLTFAttributes> targets;
public Extensions extensions;
[Preserve] public class GLTFAttributes {
public int? POSITION;
public int? NORMAL;
public int? TANGENT;
public int? COLOR_0;
public int? TEXCOORD_0;
public int? TEXCOORD_1;
public int? TEXCOORD_2;
public int? TEXCOORD_3;
public int? TEXCOORD_4;
public int? TEXCOORD_5;
public int? TEXCOORD_6;
public int? TEXCOORD_7;
public int? JOINTS_0;
public int? JOINTS_1;
public int? JOINTS_2;
public int? JOINTS_3;
public int? WEIGHTS_0;
public int? WEIGHTS_1;
public int? WEIGHTS_2;
public int? WEIGHTS_3;
}
[Preserve] public class Extensions {
public DracoMeshCompression KHR_draco_mesh_compression;
}
[Preserve] public class DracoMeshCompression {
public int bufferView = 0;
public GLTFAttributes attributes;
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f9acfe0fd7cc80849ad51216685cb042
timeCreated: 1539024463
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#specifying-extensions
// https://github.com/KhronosGroup/glTF/issues/1628
/// <summary> Will eventually become the base class of all gltf classes to enable extensions, but this isn't supported yet </summary>
[Preserve] public class GLTFProperty {
public object extensions;
public object extras;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1cd28e55dfa3bf142b439eaaa1014a79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#scene
[Preserve] public class GLTFScene {
/// <summary> Indices of nodes </summary>
public List<int> nodes;
public string name;
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1328a8789f9666842985198e60afb2ad
timeCreated: 1539024181
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#skin
[Preserve] public class GLTFSkin {
/// <summary> Index of accessor containing inverse bind shape matrices </summary>
public int? inverseBindMatrices;
public int[] joints;
public int? skeleton;
public string name;
public class ImportResult {
public Matrix4x4[] inverseBindMatrices;
public int[] joints;
#region Import
public SkinnedMeshRenderer SetupSkinnedRenderer(GameObject go, Mesh mesh, GLTFNode.ImportResult[] nodes) {
SkinnedMeshRenderer smr = go.AddComponent<SkinnedMeshRenderer>();
Transform[] bones = new Transform[joints.Length];
for (int i = 0; i < bones.Length; i++) {
int jointNodeIndex = joints[i];
GLTFNode.ImportResult jointNode = nodes[jointNodeIndex];
bones[i] = jointNode.transform;
if (string.IsNullOrEmpty(jointNode.transform.name)) jointNode.transform.name = "joint" + i;
}
smr.bones = bones;
smr.rootBone = bones[0];
// Bindposes
if (inverseBindMatrices != null) {
if (inverseBindMatrices.Length != joints.Length) Debug.LogWarning("InverseBindMatrices count and joints count not the same");
Matrix4x4 m = nodes[0].transform.localToWorldMatrix;
Matrix4x4[] bindPoses = new Matrix4x4[joints.Length];
for (int i = 0; i < joints.Length; i++) {
bindPoses[i] = inverseBindMatrices[i];
}
mesh.bindposes = bindPoses;
} else {
Matrix4x4 m = nodes[0].transform.localToWorldMatrix;
Matrix4x4[] bindPoses = new Matrix4x4[joints.Length];
for (int i = 0; i < joints.Length; i++) {
bindPoses[i] = nodes[joints[i]].transform.worldToLocalMatrix * m;
}
mesh.bindposes = bindPoses;
}
smr.sharedMesh = mesh;
return smr;
}
}
public ImportResult Import(GLTFAccessor.ImportResult[] accessors) {
ImportResult result = new ImportResult();
result.joints = joints;
// Inverse bind matrices
if (inverseBindMatrices.HasValue) {
result.inverseBindMatrices = accessors[inverseBindMatrices.Value].ReadMatrix4x4();
for (int i = 0; i < result.inverseBindMatrices.Length; i++) {
// Flip the matrix from GLTF (right handed) to Unity (left handed) format.
// This was done through comparing the GLTF matrix to
// the correctly imported matrix from the source model,
// and flipping the values where needed.
// Notice how the rows become collumns
Matrix4x4 m = result.inverseBindMatrices[i];
Vector4 row0 = m.GetRow(0);
row0.y = -row0.y;
row0.z = -row0.z;
Vector4 row1 = m.GetRow(1);
row1.x = -row1.x;
Vector4 row2 = m.GetRow(2);
row2.x = -row2.x;
Vector4 row3 = m.GetRow(3);
row3.x = -row3.x;
m.SetColumn(0, row0);
m.SetColumn(1, row1);
m.SetColumn(2, row2);
m.SetColumn(3, row3);
result.inverseBindMatrices[i] = m;
}
}
return result;
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
public ImportTask(List<GLTFSkin> skins, GLTFAccessor.ImportTask accessorTask) : base(accessorTask) {
task = new Task(() => {
if (skins == null) return;
Result = new ImportResult[skins.Count];
for (int i = 0; i < Result.Length; i++) {
Result[i] = skins[i].Import(accessorTask.Result);
}
});
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 861c8afb28d0d2946aeef4c5091068a5
timeCreated: 1539024463
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Scripting;
namespace Siccity.GLTFUtility {
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#texture
[Preserve] public class GLTFTexture {
public int? sampler;
public int? source;
public string name;
public class ImportResult {
private GLTFImage.ImportResult image;
private Texture2D cache;
/// <summary> Constructor </summary>
public ImportResult(GLTFImage.ImportResult image) {
this.image = image;
}
/// <summary> Create or return cached texture </summary>
public IEnumerator GetTextureCached(bool linear, Action<Texture2D> onFinish, Action<float> onProgress = null) {
if (cache == null) {
IEnumerator en = image.CreateTextureAsync(linear, x => cache = x, onProgress);
while (en.MoveNext()) { yield return null; };
}
onFinish(cache);
}
}
public ImportResult Import(GLTFImage.ImportResult[] images) {
if (source.HasValue) {
ImportResult result = new ImportResult(images[source.Value]);
return result;
}
return null;
}
public class ImportTask : Importer.ImportTask<ImportResult[]> {
public ImportTask(List<GLTFTexture> textures, GLTFImage.ImportTask imageTask) : base(imageTask) {
task = new Task(() => {
if (textures == null) return;
Result = new ImportResult[textures.Count];
for (int i = 0; i < Result.Length; i++) {
Result[i] = textures[i].Import(imageTask.Result);
}
});
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80d754e1551a4ee43a1e649d964d6610
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: