diff --git a/UniGLTF/.gitignore b/UniGLTF/.gitignore new file mode 100644 index 000000000..d18d9f4b0 --- /dev/null +++ b/UniGLTF/.gitignore @@ -0,0 +1,40 @@ +[Ll]ibrary/ +[Tt]emp/ +[Oo]bj/ +[Bb]uild/ +[Bb]uilds/ +Assets/AssetStoreTools* + +# Visual Studio 2015 cache directory +/.vs/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta + +# Unity3D Generated File On Crash Reports +sysinfo.txt + +# Builds +*.apk +*.unitypackage + +/Assets/Unity Technologies +/Assets/Unity Technologies.meta + +/Assets/glTF-Sample-Models.meta diff --git a/UniGLTF/.gitmodules b/UniGLTF/.gitmodules new file mode 100644 index 000000000..568d97bee --- /dev/null +++ b/UniGLTF/.gitmodules @@ -0,0 +1,9 @@ +[submodule "UniJSON"] + path = UniJSON + url = https://github.com/ousttrue/UniJSON.git +[submodule "UniHumanoid"] + path = UniHumanoid + url = https://github.com/ousttrue/UniHumanoid.git +[submodule "DepthFirstScheduler"] + path = DepthFirstScheduler + url = https://github.com/ousttrue/DepthFirstScheduler.git diff --git a/UniGLTF/Core.meta b/UniGLTF/Core.meta new file mode 100644 index 000000000..a3c576c34 --- /dev/null +++ b/UniGLTF/Core.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: d2d37db807d5f0946b5d264a870ac053 +folderAsset: yes +timeCreated: 1517139118 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Editor.meta b/UniGLTF/Core/Editor.meta new file mode 100644 index 000000000..5bb625d2a --- /dev/null +++ b/UniGLTF/Core/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d3f12e850b80b874da5050c9599b8abc +folderAsset: yes +timeCreated: 1521096862 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Editor/MaterialTests.cs b/UniGLTF/Core/Editor/MaterialTests.cs new file mode 100644 index 000000000..7685532c4 --- /dev/null +++ b/UniGLTF/Core/Editor/MaterialTests.cs @@ -0,0 +1,180 @@ +using NUnit.Framework; + + +namespace UniGLTF +{ + public class MaterialTests + { + [Test] + public void UnlitShaderImportTest() + { + var shaderStore = new ShaderStore(null); + + { + // OPAQUE/Color + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "OPAQUE", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorFactor = new float[] { 1, 0, 0, 1 }, + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // OPAQUE/Texture + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "OPAQUE", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorTexture = new glTFMaterialBaseColorTextureInfo(), + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // OPAQUE/Color/Texture + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "OPAQUE", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorFactor = new float[] { 1, 0, 0, 1 }, + baseColorTexture = new glTFMaterialBaseColorTextureInfo(), + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // BLEND/Color + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "BLEND", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorFactor = new float[] { 1, 0, 0, 1 }, + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // BLEND/Texture + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "BLEND", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorTexture = new glTFMaterialBaseColorTextureInfo(), + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // BLEND/Color/Texture + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "BLEND", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorFactor = new float[] { 1, 0, 0, 1 }, + baseColorTexture = new glTFMaterialBaseColorTextureInfo(), + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // MASK/Texture + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "MASK", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorTexture = new glTFMaterialBaseColorTextureInfo(), + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // MASK/Color/Texture + var shader = shaderStore.GetShader(new glTFMaterial + { + alphaMode = "MASK", + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorFactor = new float[] { 1, 0, 0, 1 }, + baseColorTexture = new glTFMaterialBaseColorTextureInfo(), + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + + { + // default + var shader = shaderStore.GetShader(new glTFMaterial + { + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit { } + } + }); + Assert.AreEqual("UniGLTF/UniUnlit", shader.name); + } + } + + [Test] + public void MaterialImportTest() + { + var shaderStore = new ShaderStore(null); + var materialImporter = new MaterialImporter(shaderStore, null); + + { + var material = materialImporter.CreateMaterial(0, new glTFMaterial + { + + }); + Assert.AreEqual("Standard", material.shader.name); + } + } + } +} diff --git a/UniGLTF/Core/Editor/MaterialTests.cs.meta b/UniGLTF/Core/Editor/MaterialTests.cs.meta new file mode 100644 index 000000000..5e987915c --- /dev/null +++ b/UniGLTF/Core/Editor/MaterialTests.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 37dbc1bc37728a44f8e33ad44e407c91 +timeCreated: 1533626339 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Editor/UniGLTFTests.cs b/UniGLTF/Core/Editor/UniGLTFTests.cs new file mode 100644 index 000000000..d66e9e264 --- /dev/null +++ b/UniGLTF/Core/Editor/UniGLTFTests.cs @@ -0,0 +1,195 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; +using UnityEngine; + + +namespace UniGLTF +{ + public class UniGLTFTests + { + static GameObject CreateSimpelScene() + { + var root = new GameObject("gltfRoot").transform; + + var scene = new GameObject("scene0").transform; + scene.SetParent(root, false); + scene.localPosition = new Vector3(1, 2, 3); + + return root.gameObject; + } + + void AssertAreEqual(Transform go, Transform other) + { + var lt = go.Traverse().GetEnumerator(); + var rt = go.Traverse().GetEnumerator(); + + while (lt.MoveNext()) + { + if (!rt.MoveNext()) + { + throw new Exception("rt shorter"); + } + + MonoBehaviourComparator.AssertAreEquals(lt.Current.gameObject, rt.Current.gameObject); + } + + if (rt.MoveNext()) + { + throw new Exception("rt longer"); + } + } + + [Test] + public void UniGLTFSimpleSceneTest() + { + var go = CreateSimpelScene(); + var context = new ImporterContext(); + + try + { + // export + var gltf = new glTF(); + + string json = null; + using (var exporter = new gltfExporter(gltf)) + { + exporter.Prepare(go); + exporter.Export(); + + // remove empty buffer + gltf.buffers.Clear(); + + json = gltf.ToJson(); + } + + // import + context.ParseJson(json, new SimpleStorage(new ArraySegment())); + //Debug.LogFormat("{0}", context.Json); + context.Load(); + + AssertAreEqual(go.transform, context.Root.transform); + } + finally + { + //Debug.LogFormat("Destory, {0}", go.name); + GameObject.DestroyImmediate(go); + context.EditorDestroyRootAndAssets(); + } + } + + void BufferTest(int init, params int[] size) + { + var initBytes = init == 0 ? null : new byte[init]; + var storage = new ArrayByteBuffer(initBytes); + var buffer = new glTFBuffer(storage); + + var values = new List(); + int offset = 0; + foreach (var x in size) + { + var nums = Enumerable.Range(offset, x).Select(y => (Byte)y).ToArray(); + values.AddRange(nums); + var bytes = new ArraySegment(nums); + offset += x; + buffer.Append(bytes, glBufferTarget.NONE); + } + + Assert.AreEqual(values.Count, buffer.byteLength); + Assert.True(Enumerable.SequenceEqual(values, buffer.GetBytes().ToArray())); + } + + [Test] + public void BufferTest() + { + BufferTest(0, 0, 100, 200); + BufferTest(0, 128); + BufferTest(0, 256); + + BufferTest(1024, 0); + BufferTest(1024, 128); + BufferTest(1024, 2048); + BufferTest(1024, 900, 900); + } + + [Test] + public void UnityPathTest() + { + var root = UnityPath.FromUnityPath("."); + Assert.IsFalse(root.IsNull); + Assert.IsFalse(root.IsUnderAssetsFolder); + Assert.AreEqual(UnityPath.FromUnityPath("."), root); + + var assets = UnityPath.FromUnityPath("Assets"); + Assert.IsFalse(assets.IsNull); + Assert.IsTrue(assets.IsUnderAssetsFolder); + + var rootChild = root.Child("Assets"); + Assert.AreEqual(assets, rootChild); + + var assetsChild = assets.Child("Hoge"); + var hoge = UnityPath.FromUnityPath("Assets/Hoge"); + Assert.AreEqual(assetsChild, hoge); + + //var children = root.TravserseDir().ToArray(); + } + + [Test] + public void VersionChecker() + { + Assert.False(ImporterContext.IsGeneratedUniGLTFAndOlderThan("hoge", 1, 16)); + Assert.False(ImporterContext.IsGeneratedUniGLTFAndOlderThan("UniGLTF-1.16", 1, 16)); + Assert.True(ImporterContext.IsGeneratedUniGLTFAndOlderThan("UniGLTF-1.15", 1, 16)); + Assert.False(ImporterContext.IsGeneratedUniGLTFAndOlderThan("UniGLTF-11.16", 1, 16)); + Assert.True(ImporterContext.IsGeneratedUniGLTFAndOlderThan("UniGLTF-0.16", 1, 16)); + Assert.True(ImporterContext.IsGeneratedUniGLTFAndOlderThan("UniGLTF", 1, 16)); + } + + [Test] + public void MeshTest() + { + var mesh = new glTFMesh("mesh") + { + primitives = new List + { + new glTFPrimitives + { + attributes=new glTFAttributes + { + POSITION=0, + } + } + } + }; + + var f = new JsonFormatter(); + f.Serialize(mesh); + + var json = new Utf8String(f.GetStoreBytes()).ToString(); + Debug.Log(json); + } + + [Test] + public void PrimitiveTest() + { + var prims = new List { + new glTFPrimitives + { + attributes = new glTFAttributes + { + POSITION = 0, + } + } + }; + + var f = new JsonFormatter(); + f.Serialize(prims); + + var json = new Utf8String(f.GetStoreBytes()).ToString(); + Debug.Log(json); + } + + } +} diff --git a/UniGLTF/Core/Editor/UniGLTFTests.cs.meta b/UniGLTF/Core/Editor/UniGLTFTests.cs.meta new file mode 100644 index 000000000..2c26ee504 --- /dev/null +++ b/UniGLTF/Core/Editor/UniGLTFTests.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 164dff9806a684447ae2c9d679ae730b +timeCreated: 1521096875 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts.meta b/UniGLTF/Core/Scripts.meta new file mode 100644 index 000000000..18a2d6bef --- /dev/null +++ b/UniGLTF/Core/Scripts.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: d0989add64612874d9cb36855d07677d +folderAsset: yes +timeCreated: 1517139165 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Editor.meta b/UniGLTF/Core/Scripts/Editor.meta new file mode 100644 index 000000000..c488d26e4 --- /dev/null +++ b/UniGLTF/Core/Scripts/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 37a5b86b0d204ea49b66dba21b239b22 +folderAsset: yes +timeCreated: 1517119647 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Editor/ImporterMenu.cs b/UniGLTF/Core/Scripts/Editor/ImporterMenu.cs new file mode 100644 index 000000000..d12a02b04 --- /dev/null +++ b/UniGLTF/Core/Scripts/Editor/ImporterMenu.cs @@ -0,0 +1,51 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + + +namespace UniGLTF +{ + public static class ImporterMenu + { + [MenuItem(UniGLTFVersion.UNIGLTF_VERSION + "/Import", priority = 1)] + public static void ImportMenu() + { + var path = EditorUtility.OpenFilePanel("open gltf", "", "gltf,glb,zip"); + if (string.IsNullOrEmpty(path)) + { + return; + } + + if (Application.isPlaying) + { + // + // load into scene + // + var context = new ImporterContext(); + context.Load(path); + context.ShowMeshes(); + Selection.activeGameObject = context.Root; + } + else + { + // + // save as asset + // + if (path.StartsWithUnityAssetPath()) + { + Debug.LogWarningFormat("disallow import from folder under the Assets"); + return; + } + + var assetPath = EditorUtility.SaveFilePanel("save prefab", "Assets", Path.GetFileNameWithoutExtension(path), "prefab"); + if (string.IsNullOrEmpty(path)) + { + return; + } + + // import as asset + gltfAssetPostprocessor.ImportAsset(path, Path.GetExtension(path).ToLower(), UnityPath.FromFullpath(assetPath)); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/Editor/ImporterMenu.cs.meta b/UniGLTF/Core/Scripts/Editor/ImporterMenu.cs.meta new file mode 100644 index 000000000..47d70b596 --- /dev/null +++ b/UniGLTF/Core/Scripts/Editor/ImporterMenu.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: ae2833922b06981439883d1e924b4cd0 +timeCreated: 1517153624 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Editor/UniGLTFVersionMenu.cs b/UniGLTF/Core/Scripts/Editor/UniGLTFVersionMenu.cs new file mode 100644 index 000000000..de2390c2b --- /dev/null +++ b/UniGLTF/Core/Scripts/Editor/UniGLTFVersionMenu.cs @@ -0,0 +1,45 @@ +#if UNIGLTF_DEVELOP +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; + + +namespace UniGLTF +{ + public static class UniGLTFVersionMenu + { + public const int MENU_PRIORITY = 99; + static string path = "Assets/UniGLTF/Core/Scripts/UniGLTFVersion.cs"; + + const string template = @" +namespace UniGLTF +{{ + public static partial class UniGLTFVersion + {{ + public const int MAJOR = {0}; + public const int MINOR = {1}; + + public const string VERSION = ""{0}.{1}""; + }} +}} +"; + + [MenuItem(UniGLTFVersion.UNIGLTF_VERSION + "/Increment", priority = MENU_PRIORITY)] + public static void IncrementVersion() + { + var source = string.Format(template, UniGLTFVersion.MAJOR, UniGLTFVersion.MINOR + 1); + File.WriteAllText(path, source); + AssetDatabase.Refresh(); + } + + [MenuItem(UniGLTFVersion.UNIGLTF_VERSION + "/Decrement", priority = MENU_PRIORITY)] + public static void DecrementVersion() + { + var source = string.Format(template, UniGLTFVersion.MAJOR, UniGLTFVersion.MINOR - 1); + File.WriteAllText(path, source); + AssetDatabase.Refresh(); + } + } +} +#endif diff --git a/UniGLTF/Core/Scripts/Editor/UniGLTFVersionMenu.cs.meta b/UniGLTF/Core/Scripts/Editor/UniGLTFVersionMenu.cs.meta new file mode 100644 index 000000000..ce8f77c74 --- /dev/null +++ b/UniGLTF/Core/Scripts/Editor/UniGLTFVersionMenu.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 49d23365a29018d4cb142227a01e662d +timeCreated: 1522130550 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Editor/gltfAssetPostprocessor.cs b/UniGLTF/Core/Scripts/Editor/gltfAssetPostprocessor.cs new file mode 100644 index 000000000..2990f3035 --- /dev/null +++ b/UniGLTF/Core/Scripts/Editor/gltfAssetPostprocessor.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEngine; + + +namespace UniGLTF +{ + public class gltfAssetPostprocessor : AssetPostprocessor + { + static void OnPostprocessAllAssets(string[] importedAssets, + string[] deletedAssets, + string[] movedAssets, + string[] movedFromAssetPaths) + { + foreach (string path in importedAssets) + { + var ext = Path.GetExtension(path).ToLower(); + switch (ext) + { + case ".gltf": + case ".glb": + { + var gltfPath = UnityPath.FromUnityPath(path); + var prefabPath = gltfPath.Parent.Child(gltfPath.FileNameWithoutExtension + ".prefab"); + ImportAsset(UnityPath.FromUnityPath(path).FullPath, ext, prefabPath); + break; + } + } + } + } + + public static void ImportAsset(string src, string ext, UnityPath prefabPath) + { + if (!prefabPath.IsUnderAssetsFolder) + { + Debug.LogWarningFormat("out of asset path: {0}", prefabPath); + return; + } + + var context = new ImporterContext(); + context.Parse(src); + + // Extract textures to assets folder + context.ExtranctImages(prefabPath); + + ImportDelayed(src, prefabPath, context); + } + + static void ImportDelayed(string src, UnityPath prefabPath, ImporterContext context) + { + EditorApplication.delayCall += () => + { + // + // After textures imported(To ensure TextureImporter be accessible). + // + try + { + context.Load(); + context.SaveAsAsset(prefabPath); + context.EditorDestroyRoot(); + } + catch (UniGLTFNotSupportedException ex) + { + Debug.LogWarningFormat("{0}: {1}", + src, + ex.Message + ); + context.EditorDestroyRootAndAssets(); + } + catch (Exception ex) + { + Debug.LogErrorFormat("import error: {0}", src); + Debug.LogErrorFormat("{0}", ex); + context.EditorDestroyRootAndAssets(); + } + }; + } + } +} diff --git a/UniGLTF/Core/Scripts/Editor/gltfAssetPostprocessor.cs.meta b/UniGLTF/Core/Scripts/Editor/gltfAssetPostprocessor.cs.meta new file mode 100644 index 000000000..bf1323947 --- /dev/null +++ b/UniGLTF/Core/Scripts/Editor/gltfAssetPostprocessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7bbb264b34e75dd438622e1f29f0f46c +timeCreated: 1517119659 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Extensions.meta b/UniGLTF/Core/Scripts/Extensions.meta new file mode 100644 index 000000000..6765ba93c --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: bb1523b753d4a6444935951652053804 +folderAsset: yes +timeCreated: 1514252348 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Extensions/ArrayExtensions.cs b/UniGLTF/Core/Scripts/Extensions/ArrayExtensions.cs new file mode 100644 index 000000000..3aebceef0 --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/ArrayExtensions.cs @@ -0,0 +1,127 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; + + +namespace UniGLTF +{ + public static class Pin + { + public static Pin Create(ArraySegment src) where T : struct + { + return new Pin(src); + } + public static Pin Create(T[] src) where T : struct + { + return Create(new ArraySegment(src)); + } + } + public class Pin : IDisposable + where T : struct + { + GCHandle m_pinnedArray; + + ArraySegment m_src; + + public int Length + { + get + { + return m_src.Count * Marshal.SizeOf(typeof(T)); + } + } + + public Pin(ArraySegment src) + { + m_src = src; + m_pinnedArray = GCHandle.Alloc(src.Array, GCHandleType.Pinned); + } + + public IntPtr Ptr + { + get + { + var ptr = m_pinnedArray.AddrOfPinnedObject(); + return new IntPtr(ptr.ToInt64() + m_src.Offset); + } + } + + #region IDisposable Support + private bool disposedValue = false; // 重複する呼び出しを検出するには + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: マネージ状態を破棄します (マネージ オブジェクト)。 + } + + // TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下のファイナライザーをオーバーライドします。 + // TODO: 大きなフィールドを null に設定します。 + if (m_pinnedArray.IsAllocated) + { + m_pinnedArray.Free(); + } + + disposedValue = true; + } + } + + // TODO: 上の Dispose(bool disposing) にアンマネージ リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします。 + // ~Pin() { + // // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 + // Dispose(false); + // } + + // このコードは、破棄可能なパターンを正しく実装できるように追加されました。 + public void Dispose() + { + // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 + Dispose(true); + // TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。 + // GC.SuppressFinalize(this); + } + #endregion + } + + public static class ArrayExtensions + { + public static int MarshalCoyTo(this ArraySegment src, T[] dst) where T : struct + { + var size = dst.Length * Marshal.SizeOf(typeof(T)); + using (var pin = Pin.Create(dst)) + { + Marshal.Copy(src.Array, src.Offset, pin.Ptr, size); + } + return size; + } + + public static Byte[] ToArray(this ArraySegment src) + { + var dst = new byte[src.Count]; + Array.Copy(src.Array, src.Offset, dst, 0, src.Count); + return dst; + } + + public static T[] SelectInplace(this T[] src, Func pred) + { + for (int i = 0; i < src.Length; ++i) + { + src[i] = pred(src[i]); + } + return src; + } + } + + public static class ListExtensions + { + public static void Assign(this List dst, T[] src, Func pred) + { + dst.Capacity = src.Length; + dst.AddRange(src.Select(pred)); + } + } +} diff --git a/UniGLTF/Core/Scripts/Extensions/ArrayExtensions.cs.meta b/UniGLTF/Core/Scripts/Extensions/ArrayExtensions.cs.meta new file mode 100644 index 000000000..e6658a3ff --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/ArrayExtensions.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e843bb7270bc9f54f8495e1e35f82e8d +timeCreated: 1514252357 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Extensions/JsonParserExtensions.cs b/UniGLTF/Core/Scripts/Extensions/JsonParserExtensions.cs new file mode 100644 index 000000000..e8e5341eb --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/JsonParserExtensions.cs @@ -0,0 +1,19 @@ +using System.Linq; +using UnityEngine; +using System.Collections.Generic; + + +namespace UniJSON +{ + public static class JsonParserExtensions + { + public static List DeserializeList(this ListTreeNode jsonList) + { + return jsonList.ArrayItems().Select(x => { + + return JsonUtility.FromJson(new Utf8String(x.Value.Bytes).ToString()); + + }).ToList(); + } + } +} diff --git a/UniGLTF/Core/Scripts/Extensions/JsonParserExtensions.cs.meta b/UniGLTF/Core/Scripts/Extensions/JsonParserExtensions.cs.meta new file mode 100644 index 000000000..6aa0b19a6 --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/JsonParserExtensions.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: d0a724a200cc85b4887efa87292c6626 +timeCreated: 1515608626 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Extensions/StringExtensions.cs b/UniGLTF/Core/Scripts/Extensions/StringExtensions.cs new file mode 100644 index 000000000..bd5e5cdfe --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/StringExtensions.cs @@ -0,0 +1,73 @@ +using System.IO; +using UnityEngine; + +namespace UniGLTF +{ + public static class StringExtensions + { + public static string ToLowerCamelCase(this string lower) + { + return lower.Substring(0, 1).ToLower() + lower.Substring(1); + } + public static string ToUpperCamelCase(this string lower) + { + return lower.Substring(0, 1).ToUpper() + lower.Substring(1); + } + + static string m_unityBasePath; + public static string UnityBasePath + { + get + { + if (m_unityBasePath == null) + { + m_unityBasePath = Path.GetFullPath(Application.dataPath + "/..").Replace("\\", "/"); + } + return m_unityBasePath; + } + } + + public static string AssetPathToFullPath(this string path) + { + return UnityBasePath + "/" + path; + } + + public static bool StartsWithUnityAssetPath(this string path) + { + return path.Replace("\\", "/").StartsWith(UnityBasePath + "/Assets"); + } + + public static string ToUnityRelativePath(this string path) + { + path = path.Replace("\\", "/"); + if (path.StartsWith(UnityBasePath)) + { + return path.Substring(UnityBasePath.Length + 1); + } + + //Debug.LogWarningFormat("{0} is starts with {1}", path, basePath); + return path; + } + + static readonly char[] EscapeChars = new char[] + { + '\\', + '/', + ':', + '*', + '?', + '"', + '<', + '>', + '|', + }; + public static string EscapeFilePath(this string path) + { + foreach(var x in EscapeChars) + { + path = path.Replace(x, '+'); + } + return path; + } + } +} diff --git a/UniGLTF/Core/Scripts/Extensions/StringExtensions.cs.meta b/UniGLTF/Core/Scripts/Extensions/StringExtensions.cs.meta new file mode 100644 index 000000000..6c068c8b3 --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/StringExtensions.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 54b33d3e5f599eb419fae2bbf00f1613 +timeCreated: 1517471530 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Extensions/UnityExtensions.cs b/UniGLTF/Core/Scripts/Extensions/UnityExtensions.cs new file mode 100644 index 000000000..49784a7f2 --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/UnityExtensions.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public struct PosRot + { + public Vector3 Position; + public Quaternion Rotation; + + public static PosRot FromGlobalTransform(Transform t) + { + return new PosRot + { + Position = t.position, + Rotation = t.rotation, + }; + } + } + + public class BlendShape + { + public string Name; + + public BlendShape(string name) + { + Name = name; + } + + public List Positions = new List(); + public List Normals = new List(); + public List Tangents = new List(); + } + + public static class UnityExtensions + { + public static Vector4 ReverseZ(this Vector4 v) + { + return new Vector4(v.x, v.y, -v.z, v.w); + } + + public static Vector3 ReverseZ(this Vector3 v) + { + return new Vector3(v.x, v.y, -v.z); + } + + [Obsolete] + public static Vector2 ReverseY(this Vector2 v) + { + return new Vector2(v.x, -v.y); + } + + public static Vector2 ReverseUV(this Vector2 v) + { + return new Vector2(v.x, 1.0f - v.y); + } + + public static Quaternion ReverseZ(this Quaternion q) + { + float angle; + Vector3 axis; + q.ToAngleAxis(out angle, out axis); + return Quaternion.AngleAxis(-angle, ReverseZ(axis)); + } + + public static Matrix4x4 Matrix4x4FromColumns(Vector4 c0, Vector4 c1, Vector4 c2, Vector4 c3) + { +#if UNITY_2017_1_OR_NEWER + return new Matrix4x4(c0, c1, c2, c3); +#else + var m = default(Matrix4x4); + m.SetColumn(0, c0); + m.SetColumn(1, c1); + m.SetColumn(2, c2); + m.SetColumn(3, c3); + return m; +#endif + } + + public static Matrix4x4 Matrix4x4FromRotation(Quaternion q) + { +#if UNITY_2017_1_OR_NEWER + return Matrix4x4.Rotate(q); +#else + var m = default(Matrix4x4); + m.SetTRS(Vector3.zero, q, Vector3.one); + return m; +#endif + } + + public static Matrix4x4 ReverseZ(this Matrix4x4 m) + { + m.SetTRS(m.ExtractPosition().ReverseZ(), m.ExtractRotation().ReverseZ(), m.ExtractScale()); + return m; + } + + public static Matrix4x4 MatrixFromArray(float[] values) + { + var m = new Matrix4x4(); + m.m00 = values[0]; + m.m10 = values[1]; + m.m20 = values[2]; + m.m30 = values[3]; + m.m01 = values[4]; + m.m11 = values[5]; + m.m21 = values[6]; + m.m31 = values[7]; + m.m02 = values[8]; + m.m12 = values[9]; + m.m22 = values[10]; + m.m32 = values[11]; + m.m03 = values[12]; + m.m13 = values[13]; + m.m23 = values[14]; + m.m33 = values[15]; + return m; + } + + // https://forum.unity.com/threads/how-to-assign-matrix4x4-to-transform.121966/ + public static Quaternion ExtractRotation(this Matrix4x4 matrix) + { + Vector3 forward; + forward.x = matrix.m02; + forward.y = matrix.m12; + forward.z = matrix.m22; + + Vector3 upwards; + upwards.x = matrix.m01; + upwards.y = matrix.m11; + upwards.z = matrix.m21; + + return Quaternion.LookRotation(forward, upwards); + } + + public static Vector3 ExtractPosition(this Matrix4x4 matrix) + { + Vector3 position; + position.x = matrix.m03; + position.y = matrix.m13; + position.z = matrix.m23; + return position; + } + + public static Vector3 ExtractScale(this Matrix4x4 matrix) + { + Vector3 scale; + scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude; + scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude; + scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude; + return scale; + } + + public static string RelativePathFrom(this Transform self, Transform root) + { + var path = new List(); + for (var current = self; current != null; current = current.parent) + { + if (current == root) + { + return String.Join("/", path.ToArray()); + } + + path.Insert(0, current.name); + } + + throw new Exception("no RelativePath"); + } + + public static Transform GetChildByName(this Transform self, string childName) + { + foreach (Transform child in self) + { + if (child.name == childName) + { + return child; + } + } + + throw new KeyNotFoundException(); + } + + public static Transform GetFromPath(this Transform self, string path) + { + var current = self; + + var splited = path.Split('/'); + + foreach (var childName in splited) + { + current = current.GetChildByName(childName); + } + + return current; + } + + public static IEnumerable GetChildren(this Transform self) + { + foreach (Transform child in self) + { + yield return child; + } + } + + public static IEnumerable Traverse(this Transform t) + { + yield return t; + foreach (Transform x in t) + { + foreach (Transform y in x.Traverse()) + { + yield return y; + } + } + } + + public static Transform FindDescenedant(this Transform t, string name) + { + return t.Traverse().First(x => x.name == name); + } + + public static IEnumerable Ancestors(this Transform t) + { + yield return t; + if (t.parent != null) + { + foreach (Transform x in t.parent.Ancestors()) + { + yield return x; + } + } + } + + public static float[] ToArray(this Quaternion q) + { + return new float[] { q.x, q.y, q.z, q.w }; + } + + public static float[] ToArray(this Vector3 v) + { + return new float[] { v.x, v.y, v.z }; + } + + public static float[] ToArray(this Vector4 v) + { + return new float[] { v.x, v.y, v.z, v.w }; + } + + public static float[] ToArray(this Color c) + { + return new float[] { c.r, c.g, c.b, c.a }; + } + + public static void ReverseZRecursive(this Transform root) + { + var globalMap = root.Traverse().ToDictionary(x => x, x => PosRot.FromGlobalTransform(x)); + + foreach (var x in root.Traverse()) + { + x.position = globalMap[x].Position.ReverseZ(); + x.rotation = globalMap[x].Rotation.ReverseZ(); + } + } + + public static Mesh GetSharedMesh(this Transform t) + { + var meshFilter = t.GetComponent(); + if (meshFilter != null) + { + return meshFilter.sharedMesh; + } + + var skinnedMeshRenderer = t.GetComponent(); + if (skinnedMeshRenderer != null) + { + return skinnedMeshRenderer.sharedMesh; + } + + return null; + } + + public static Material[] GetSharedMaterials(this Transform t) + { + var renderer = t.GetComponent(); + if (renderer != null) + { + return renderer.sharedMaterials; + } + + return new Material[] { }; + } + + public static bool Has(this Transform transform, T t) where T : Component + { + return transform.GetComponent() == t; + } + + public static T GetOrAddComponent(this GameObject go) where T : Component + { + var c = go.GetComponent(); + if (c != null) + { + return c; + } + return go.AddComponent(); + } + } +} diff --git a/UniGLTF/Core/Scripts/Extensions/UnityExtensions.cs.meta b/UniGLTF/Core/Scripts/Extensions/UnityExtensions.cs.meta new file mode 100644 index 000000000..062aed465 --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/UnityExtensions.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 5294813527b3278458026afc820dd63d +timeCreated: 1515606586 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Extensions/glTFExtensions.cs b/UniGLTF/Core/Scripts/Extensions/glTFExtensions.cs new file mode 100644 index 000000000..91c697c84 --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/glTFExtensions.cs @@ -0,0 +1,203 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using UnityEngine; + + +namespace UniGLTF +{ + [Serializable, StructLayout(LayoutKind.Sequential, Pack = 1)] + struct UShort4 + { + public ushort x; + public ushort y; + public ushort z; + public ushort w; + + public UShort4(ushort _x, ushort _y, ushort _z, ushort _w) + { + x = _x; + y = _y; + z = _z; + w = _w; + } + } + + public static class glTFExtensions + { + struct ComponentVec + { + public glComponentType ComponentType; + public int ElementCount; + + public ComponentVec(glComponentType componentType, int elementCount) + { + ComponentType = componentType; + ElementCount = elementCount; + } + } + + static Dictionary ComponentTypeMap = new Dictionary + { + { typeof(Vector2), new ComponentVec(glComponentType.FLOAT, 2) }, + { typeof(Vector3), new ComponentVec(glComponentType.FLOAT, 3) }, + { typeof(Vector4), new ComponentVec(glComponentType.FLOAT, 4) }, + { typeof(UShort4), new ComponentVec(glComponentType.UNSIGNED_SHORT, 4) }, + { typeof(Matrix4x4), new ComponentVec(glComponentType.FLOAT, 16) }, + { typeof(Color), new ComponentVec(glComponentType.FLOAT, 4) }, + }; + + static glComponentType GetComponentType() + { + var cv = default(ComponentVec); + if (ComponentTypeMap.TryGetValue(typeof(T), out cv)) + { + return cv.ComponentType; + } + else if (typeof(T) == typeof(uint)) + { + return glComponentType.UNSIGNED_INT; + } + else if (typeof(T) == typeof(float)) + { + return glComponentType.FLOAT; + } + else + { + throw new NotImplementedException(typeof(T).Name); + } + } + + static string GetAccessorType() + { + var cv = default(ComponentVec); + if (ComponentTypeMap.TryGetValue(typeof(T), out cv)) + { + switch (cv.ElementCount) + { + case 2: return "VEC2"; + case 3: return "VEC3"; + case 4: return "VEC4"; + case 16: return "MAT4"; + default: throw new Exception(); + } + } + else + { + return "SCALAR"; + } + } + + static int GetAccessorElementCount() + { + var cv = default(ComponentVec); + if (ComponentTypeMap.TryGetValue(typeof(T), out cv)) + { + return cv.ElementCount; + } + else + { + return 1; + } + } + + public static int ExtendBufferAndGetAccessorIndex(this glTF gltf, int bufferIndex, T[] array, + glBufferTarget target = glBufferTarget.NONE) where T : struct + { + return gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, new ArraySegment(array), target); + } + + public static int ExtendBufferAndGetAccessorIndex(this glTF gltf, int bufferIndex, + ArraySegment array, + glBufferTarget target = glBufferTarget.NONE) where T : struct + { + if (array.Count == 0) + { + return -1; + } + var viewIndex = ExtendBufferAndGetViewIndex(gltf, bufferIndex, array, target); + + // index buffer's byteStride is unnecessary + gltf.bufferViews[viewIndex].byteStride = 0; + + var accessorIndex = gltf.accessors.Count; + gltf.accessors.Add(new glTFAccessor + { + bufferView = viewIndex, + byteOffset = 0, + componentType = GetComponentType(), + type = GetAccessorType(), + count = array.Count, + }); + return accessorIndex; + } + + public static int ExtendBufferAndGetViewIndex(this glTF gltf, int bufferIndex, + T[] array, + glBufferTarget target = glBufferTarget.NONE) where T : struct + { + return ExtendBufferAndGetViewIndex(gltf, bufferIndex, new ArraySegment(array), target); + } + + public static int ExtendBufferAndGetViewIndex(this glTF gltf, int bufferIndex, + ArraySegment array, + glBufferTarget target = glBufferTarget.NONE) where T : struct + { + if (array.Count == 0) + { + return -1; + } + var view = gltf.buffers[bufferIndex].Append(array, target); + var viewIndex = gltf.bufferViews.Count; + gltf.bufferViews.Add(view); + return viewIndex; + } + + public static int ExtendSparseBufferAndGetAccessorIndex(this glTF gltf, int bufferIndex, + int accessorCount, + T[] sparseValues, int[] sparseIndices, int sparseViewIndex, + glBufferTarget target = glBufferTarget.NONE) where T : struct + { + return ExtendSparseBufferAndGetAccessorIndex(gltf, bufferIndex, + accessorCount, + new ArraySegment(sparseValues), sparseIndices, sparseViewIndex, + target); + } + + public static int ExtendSparseBufferAndGetAccessorIndex(this glTF gltf, int bufferIndex, + int accessorCount, + ArraySegment sparseValues, int[] sparseIndices, int sparseIndicesViewIndex, + glBufferTarget target = glBufferTarget.NONE) where T : struct + { + if (sparseValues.Count == 0) + { + return -1; + } + var sparseValuesViewIndex = ExtendBufferAndGetViewIndex(gltf, bufferIndex, sparseValues, target); + var accessorIndex = gltf.accessors.Count; + gltf.accessors.Add(new glTFAccessor + { + byteOffset = 0, + componentType = GetComponentType(), + type = GetAccessorType(), + count = accessorCount, + + sparse = new glTFSparse + { + count=sparseIndices.Length, + indices = new glTFSparseIndices + { + bufferView = sparseIndicesViewIndex, + componentType = glComponentType.UNSIGNED_INT + }, + values = new glTFSparseValues + { + bufferView = sparseValuesViewIndex, + } + } + }); + return accessorIndex; + } + } +} diff --git a/UniGLTF/Core/Scripts/Extensions/glTFExtensions.cs.meta b/UniGLTF/Core/Scripts/Extensions/glTFExtensions.cs.meta new file mode 100644 index 000000000..33e943640 --- /dev/null +++ b/UniGLTF/Core/Scripts/Extensions/glTFExtensions.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 870cb47398900534994d90eb1cecace8 +timeCreated: 1517038417 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format.meta b/UniGLTF/Core/Scripts/Format.meta new file mode 100644 index 000000000..31f52eefb --- /dev/null +++ b/UniGLTF/Core/Scripts/Format.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 91b71b4caaefeae4fa5ca31908b35a86 +folderAsset: yes +timeCreated: 1516616861 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/BytesBuffer.cs b/UniGLTF/Core/Scripts/Format/BytesBuffer.cs new file mode 100644 index 000000000..38a27ec3a --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/BytesBuffer.cs @@ -0,0 +1,187 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + + +namespace UniGLTF +{ + public interface IBytesBuffer + { + string Uri { get; } + ArraySegment GetBytes(); + glTFBufferView Extend(ArraySegment array, glBufferTarget target) where T : struct; + } + + public static class IBytesBufferExtensions + { + public static glTFBufferView Extend(this IBytesBuffer buffer, T[] array, glBufferTarget target) where T : struct + { + return buffer.Extend(new ArraySegment(array), target); + } + } + + /// + /// for buffer with uri read + /// + public class UriByteBuffer : IBytesBuffer + { + public string Uri + { + get; + private set; + } + + Byte[] m_bytes; + public ArraySegment GetBytes() + { + return new ArraySegment(m_bytes); + } + + public UriByteBuffer(string baseDir, string uri) + { + Uri = uri; + m_bytes = ReadFromUri(baseDir, uri); + } + + const string DataPrefix = "data:application/octet-stream;base64,"; + + const string DataPrefix2 = "data:application/gltf-buffer;base64,"; + + const string DataPrefix3 = "data:image/jpeg;base64,"; + + public static Byte[] ReadEmbeded(string uri) + { + var pos = uri.IndexOf(";base64,"); + if (pos < 0) + { + throw new NotImplementedException(); + } + else + { + return Convert.FromBase64String(uri.Substring(pos + 8)); + } + } + + Byte[] ReadFromUri(string baseDir, string uri) + { + var bytes = ReadEmbeded(uri); + if (bytes != null) + { + return bytes; + } + else + { + // as local file path + return File.ReadAllBytes(Path.Combine(baseDir, uri)); + } + } + + public glTFBufferView Extend(ArraySegment array, glBufferTarget target) where T : struct + { + throw new NotImplementedException(); + } + } + + /// + /// for glb chunk buffer read + /// + public class ArraySegmentByteBuffer : IBytesBuffer + { + ArraySegment m_bytes; + + public ArraySegmentByteBuffer(ArraySegment bytes) + { + m_bytes = bytes; + } + + public string Uri + { + get; + private set; + } + + public glTFBufferView Extend(ArraySegment array, glBufferTarget target) where T : struct + { + throw new NotImplementedException(); + } + + public ArraySegment GetBytes() + { + return m_bytes; + } + } + + /// + /// for exporter + /// + public class ArrayByteBuffer : IBytesBuffer + { + public string Uri + { + get; + private set; + } + + Byte[] m_bytes; + int m_used; + + public ArrayByteBuffer(Byte[] bytes = null) + { + Uri = ""; + m_bytes = bytes; + } + + public glTFBufferView Extend(ArraySegment array, glBufferTarget target) where T : struct + { + using (var pin = Pin.Create(array)) + { + var elementSize = Marshal.SizeOf(typeof(T)); + var view = Extend(pin.Ptr, array.Count * elementSize, elementSize, target); + return view; + } + } + + public glTFBufferView Extend(IntPtr p, int bytesLength, int stride, glBufferTarget target) + { + var tmp = m_bytes; + // alignment + var padding = m_used % stride == 0 ? 0 : stride - m_used % stride; + + if (m_bytes == null || m_used + padding + bytesLength > m_bytes.Length) + { + // recreate buffer + m_bytes = new Byte[m_used + padding + bytesLength]; + if (m_used > 0) + { + Buffer.BlockCopy(tmp, 0, m_bytes, 0, m_used); + } + } + if (m_used + padding + bytesLength > m_bytes.Length) + { + throw new ArgumentOutOfRangeException(); + } + + Marshal.Copy(p, m_bytes, m_used + padding, bytesLength); + var result=new glTFBufferView + { + buffer = 0, + byteLength = bytesLength, + byteOffset = m_used + padding, + byteStride = stride, + target = target, + }; + m_used = m_used + padding + bytesLength; + return result; + } + + public ArraySegment GetBytes() + { + if (m_bytes == null) + { + return new ArraySegment(); + } + + return new ArraySegment(m_bytes, 0, m_used); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/BytesBuffer.cs.meta b/UniGLTF/Core/Scripts/Format/BytesBuffer.cs.meta new file mode 100644 index 000000000..ca3050609 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/BytesBuffer.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 33b0000c5446b7547bcad1da1e9768ed +timeCreated: 1516730619 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras.meta b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras.meta new file mode 100644 index 000000000..44b87167a --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ba47ab6d00c723348b9ac86141cb7bfd +folderAsset: yes +timeCreated: 1533605176 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_draco_mesh_compression.cs b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_draco_mesh_compression.cs new file mode 100644 index 000000000..11111e27b --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_draco_mesh_compression.cs @@ -0,0 +1,32 @@ +using System; +using UniJSON; + + +namespace UniGLTF +{ + [Serializable] + public class glTF_KHR_draco_mesh_compression : JsonSerializableBase + { + [JsonSchema(Required = true, Minimum = 0)] + public int bufferView = -1; + public glTFAttributes attributes; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + //throw new NotImplementedException(); + } + } + + [Serializable] + public partial class glTFPrimitives_extensions : ExtensionsBase + { + [JsonSchema(Required = true)] + public glTF_KHR_draco_mesh_compression KHR_draco_mesh_compression; + + [JsonSerializeMembers] + void SerializeMembers_draco(GLTFJsonFormatter f) + { + //throw new NotImplementedException(); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_draco_mesh_compression.cs.meta b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_draco_mesh_compression.cs.meta new file mode 100644 index 000000000..3cdd0a83e --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_draco_mesh_compression.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2d21c7591557c7744a6fa34a7acace91 +timeCreated: 1533605595 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_materials_unlit.cs b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_materials_unlit.cs new file mode 100644 index 000000000..6797c4c26 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_materials_unlit.cs @@ -0,0 +1,55 @@ +using System; +using UniJSON; + +namespace UniGLTF +{ + [Serializable] + public class glTF_KHR_materials_unlit : JsonSerializableBase + { + public static string ExtensionName + { + get + { + return "KHR_materials_unlit"; + } + } + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + //throw new System.NotImplementedException(); + } + + public static glTFMaterial CreateDefault() + { + return new glTFMaterial + { + pbrMetallicRoughness = new glTFPbrMetallicRoughness + { + baseColorFactor = new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, + roughnessFactor = 0.9f, + metallicFactor = 0.0f, + }, + extensions = new glTFMaterial_extensions + { + KHR_materials_unlit = new glTF_KHR_materials_unlit(), + }, + }; + } + } + + [Serializable] + public partial class glTFMaterial_extensions : ExtensionsBase + { + [JsonSchema(Required = true)] + public glTF_KHR_materials_unlit KHR_materials_unlit; + + [JsonSerializeMembers] + void SerializeMembers_unlit(GLTFJsonFormatter f) + { + if (KHR_materials_unlit != null) + { + f.KeyValue(() => KHR_materials_unlit); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_materials_unlit.cs.meta b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_materials_unlit.cs.meta new file mode 100644 index 000000000..9fb277611 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/KHR_materials_unlit.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2c95b5ebe597e924fa2e7fcce3a65f76 +timeCreated: 1533606191 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFCameraExtensions.cs b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFCameraExtensions.cs new file mode 100644 index 000000000..4f5d0e18c --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFCameraExtensions.cs @@ -0,0 +1,27 @@ +using System; +using UniJSON; + + +namespace UniGLTF +{ + [Serializable] + [ItemJsonSchema(ValueType = ValueNodeType.Object)] + public partial class glTFOrthographic_extensions : ExtensionsBase { } + + [Serializable] + public partial class glTFOrthographic_extras : ExtraBase { } + + [Serializable] + [ItemJsonSchema(ValueType = ValueNodeType.Object)] + public partial class glTFPerspective_extensions : ExtensionsBase { } + + [Serializable] + public partial class glTFPerspective_extras : ExtraBase { } + + [Serializable] + [ItemJsonSchema(ValueType = ValueNodeType.Object)] + public partial class glTFCamera_extensions : ExtensionsBase { } + + [Serializable] + public partial class glTFCamera_extras : ExtraBase { } +} diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFCameraExtensions.cs.meta b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFCameraExtensions.cs.meta new file mode 100644 index 000000000..3f5c26d33 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFCameraExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 38f59c64e05855b41be64595c570e366 +timeCreated: 1533605499 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFExtensions.cs b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFExtensions.cs new file mode 100644 index 000000000..0c192865b --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFExtensions.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Reflection; +using UniJSON; + + +namespace UniGLTF +{ + #region Base + public class JsonSerializeMembersAttribute : Attribute { } + + public class PartialExtensionBase : JsonSerializableBase + { + protected override void SerializeMembers(GLTFJsonFormatter f) + { + foreach (var method in this.GetType().GetMethods(BindingFlags.Instance | + BindingFlags.Public | BindingFlags.NonPublic)) + { + if (method.GetCustomAttributes(typeof(JsonSerializeMembersAttribute), true).Any()) + { + method.Invoke(this, new[] { f }); + } + } + } + + public int __count + { + get + { + return typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.GetCustomAttributes(typeof(JsonSerializeMembersAttribute), true).Any()) + .Count(); + } + } + } + + [ItemJsonSchema(ValueType = ValueNodeType.Object)] + [JsonSchema(MinProperties = 1)] + public partial class ExtensionsBase : PartialExtensionBase + { + } + + [JsonSchema(MinProperties = 1)] + public partial class ExtraBase : PartialExtensionBase + { + } + #endregion + + [Serializable] + public partial class glTF_extensions : ExtensionsBase { } + + [Serializable] + public partial class gltf_extras : ExtraBase { } +} diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFExtensions.cs.meta b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFExtensions.cs.meta new file mode 100644 index 000000000..f1fd71eec --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 356f989283db8c848b851b82c34086d1 +timeCreated: 1532101527 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFMesh.Primitives.extras.targetNames.cs b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFMesh.Primitives.extras.targetNames.cs new file mode 100644 index 000000000..f9bfb7952 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFMesh.Primitives.extras.targetNames.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using UniJSON; + + +namespace UniGLTF +{ + /// + /// https://github.com/KhronosGroup/glTF/issues/1036 + /// + [Serializable] + public partial class glTFPrimitives_extras : ExtraBase + { + [JsonSchema(Required = true, MinItems = 1)] + public List targetNames = new List(); + + [JsonSerializeMembers] + void PrimitiveMembers(GLTFJsonFormatter f) + { + if (targetNames.Count > 0) + { + f.Key("targetNames"); + f.BeginList(); + foreach (var x in targetNames) + { + f.Value(x); + } + f.EndList(); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFMesh.Primitives.extras.targetNames.cs.meta b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFMesh.Primitives.extras.targetNames.cs.meta new file mode 100644 index 000000000..10ec6ee57 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFMesh.Primitives.extras.targetNames.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e95762d0b18d11243b32a56f21cef862 +timeCreated: 1533605545 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFNode.cs b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFNode.cs new file mode 100644 index 000000000..f41ec3847 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFNode.cs @@ -0,0 +1,11 @@ +using System; + + +namespace UniGLTF +{ + [Serializable] + public partial class glTFNode_extensions : ExtensionsBase { } + + [Serializable] + public partial class glTFNode_extra : ExtraBase { } +} diff --git a/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFNode.cs.meta b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFNode.cs.meta new file mode 100644 index 000000000..d68a4cd38 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/ExtensionsAndExtras/glTFNode.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: eef0405c0c9670947ab6fa2034f0343f +timeCreated: 1533605847 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/GLTFJsonFormatter.cs b/UniGLTF/Core/Scripts/Format/GLTFJsonFormatter.cs new file mode 100644 index 000000000..f171e85cb --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/GLTFJsonFormatter.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using UniJSON; + + +namespace UniGLTF +{ + public class GLTFJsonFormatter: UniJSON.JsonFormatter + { + public void GLTFValue(JsonSerializableBase s) + { + CommaCheck(); + Store.Write(s.ToJson()); + } + + public void GLTFValue(IEnumerable values) where T : JsonSerializableBase + { + BeginList(); + foreach (var value in values) + { + GLTFValue(value); + } + EndList(); + } + + public void GLTFValue(List values) + { + BeginList(); + foreach (var value in values) + { + this.Value(value); + } + EndList(); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/GLTFJsonFormatter.cs.meta b/UniGLTF/Core/Scripts/Format/GLTFJsonFormatter.cs.meta new file mode 100644 index 000000000..eb52e7f5d --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/GLTFJsonFormatter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b80a7ee38e762de44a90d60d9d4ff4dd +timeCreated: 1531806259 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/JsonSerializableBase.cs b/UniGLTF/Core/Scripts/Format/JsonSerializableBase.cs new file mode 100644 index 000000000..de1eee467 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/JsonSerializableBase.cs @@ -0,0 +1,22 @@ +using System; + + +namespace UniGLTF +{ + [Serializable] + public abstract class JsonSerializableBase + { + protected abstract void SerializeMembers(GLTFJsonFormatter f); + + public string ToJson() + { + var f = new GLTFJsonFormatter(); + f.BeginMap(); + + SerializeMembers(f); + + f.EndMap(); + return f.ToString(); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/JsonSerializableBase.cs.meta b/UniGLTF/Core/Scripts/Format/JsonSerializableBase.cs.meta new file mode 100644 index 000000000..a21428132 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/JsonSerializableBase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d169186adbfc59b4b882611d37f0a282 +timeCreated: 1532078625 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/MonoBehaviourComparator.cs b/UniGLTF/Core/Scripts/Format/MonoBehaviourComparator.cs new file mode 100644 index 000000000..dceccdb9f --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/MonoBehaviourComparator.cs @@ -0,0 +1,68 @@ +using System; +using UnityEngine; + + +namespace UniGLTF +{ + public static class MonoBehaviourComparator + { + public static bool AssertAreEquals(GameObject l, GameObject r) + { + return + l.name == r.name + && AssertAreEquals(l, r, + (x, y) => AssertAreEquals(x[0], y[0])) + && AssertAreEquals(l, r, + (x, y) => AssertAreEquals(x[0], y[0])) + && AssertAreEquals(l, r, + (x, y) => AssertAreEquals(x[0], y[0])) + && AssertAreEquals(l, r, + (x, y) => AssertAreEquals(x[0], y[0])) + ; + } + + public static bool AssertAreEquals(GameObject l, GameObject r, Func pred) where T : Component + { + var ll = l.GetComponents(); + var rr = r.GetComponents(); + if (ll.Length != rr.Length) + { + return false; + } + if (ll.Length == 0) + { + return true; + } + return pred(ll, rr); + } + + public static bool AssertAreEquals(Transform l, Transform r) + { + return + (l.localPosition == r.localPosition) + && (l.localRotation == r.localRotation) + && (l.localScale == r.localScale) + ; + } + + public static bool AssertAreEquals(MeshFilter l, MeshFilter r) + { + throw new NotImplementedException(); + } + + public static bool AssertAreEquals(MeshRenderer l, MeshRenderer r) + { + throw new NotImplementedException(); + } + + public static bool AssertAreEquals(SkinnedMeshRenderer l, SkinnedMeshRenderer r) + { + throw new NotImplementedException(); + } + + public static bool AssetAreEquals(Texture2D l, Texture2D r) + { + throw new NotImplementedException(); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/MonoBehaviourComparator.cs.meta b/UniGLTF/Core/Scripts/Format/MonoBehaviourComparator.cs.meta new file mode 100644 index 000000000..615422974 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/MonoBehaviourComparator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7cee53236ca4f234ab42ba8e92afd178 +timeCreated: 1521104277 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glEnum.cs b/UniGLTF/Core/Scripts/Format/glEnum.cs new file mode 100644 index 000000000..f2ad5a884 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glEnum.cs @@ -0,0 +1,48 @@ +/// +/// https://gist.github.com/szimek/763999 +/// +namespace UniGLTF +{ + public enum glComponentType : int + { + BYTE = 5120, // signed ? + UNSIGNED_BYTE = 5121, + + SHORT = 5122, + UNSIGNED_SHORT = 5123, + + //INT = 5124, + UNSIGNED_INT = 5125, + + FLOAT = 5126, + } + + public enum glBufferTarget : int + { + NONE = 0, + ARRAY_BUFFER = 34962, + ELEMENT_ARRAY_BUFFER = 34963, + } + + public enum glFilter : int + { + NONE = 0, + NEAREST = 9728, + LINEAR = 9729, + + #region for minFilter only + NEAREST_MIPMAP_NEAREST = 9984, + LINEAR_MIPMAP_NEAREST = 9985, + NEAREST_MIPMAP_LINEAR = 9986, + LINEAR_MIPMAP_LINEAR = 9987, + #endregion + } + + public enum glWrap : int + { + NONE = 0, + CLAMP_TO_EDGE = 33071, + REPEAT = 10497, + MIRRORED_REPEAT = 33648, + } +} diff --git a/UniGLTF/Core/Scripts/Format/glEnum.cs.meta b/UniGLTF/Core/Scripts/Format/glEnum.cs.meta new file mode 100644 index 000000000..d10911349 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glEnum.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 378358a997cd2e9418f180593fb04a6f +timeCreated: 1516212452 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTF.cs b/UniGLTF/Core/Scripts/Format/glTF.cs new file mode 100644 index 000000000..9481cd5dc --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTF.cs @@ -0,0 +1,518 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UniJSON; + + +namespace UniGLTF +{ + [Serializable] + public class gltfScene : JsonSerializableBase + { + [JsonSchema(MinItems = 1)] + [ItemJsonSchema(Minimum = 0)] + public int[] nodes; + + public object extensions; + public object extras; + public string name; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => nodes); + } + } + + [Serializable] + public class glTF : JsonSerializableBase, IEquatable + { + [JsonSchema(Required = true)] + public glTFAssets asset = new glTFAssets(); + + #region Buffer + [JsonSchema(MinItems = 1)] + public List buffers = new List(); + public int AddBuffer(IBytesBuffer bytesBuffer) + { + var index = buffers.Count; + buffers.Add(new glTFBuffer(bytesBuffer)); + return index; + } + + [JsonSchema(MinItems = 1)] + public List bufferViews = new List(); + public int AddBufferView(glTFBufferView view) + { + var index = bufferViews.Count; + bufferViews.Add(view); + return index; + } + + [JsonSchema(MinItems = 1)] + public List accessors = new List(); + + T[] GetAttrib(glTFAccessor accessor, glTFBufferView view) where T : struct + { + return GetAttrib(accessor.count, accessor.byteOffset, view); + } + T[] GetAttrib(int count, int byteOffset, glTFBufferView view) where T : struct + { + var attrib = new T[count]; + // + var segment = buffers[view.buffer].GetBytes(); + var bytes = new ArraySegment(segment.Array, segment.Offset + view.byteOffset + byteOffset, count * view.byteStride); + bytes.MarshalCoyTo(attrib); + return attrib; + } + + public ArraySegment GetViewBytes(int bufferView) + { + var view = bufferViews[bufferView]; + var segment = buffers[view.buffer].GetBytes(); + return new ArraySegment(segment.Array, segment.Offset + view.byteOffset, view.byteLength); + } + + IEnumerable _GetIndices(glTFAccessor accessor, out int count) + { + count = accessor.count; + var view = bufferViews[accessor.bufferView]; + switch ((glComponentType)accessor.componentType) + { + case glComponentType.UNSIGNED_BYTE: + { + return GetAttrib(accessor, view).Select(x => (int)(x)); + } + + case glComponentType.UNSIGNED_SHORT: + { + return GetAttrib(accessor, view).Select(x => (int)(x)); + } + + case glComponentType.UNSIGNED_INT: + { + return GetAttrib(accessor, view).Select(x => (int)(x)); + } + } + throw new NotImplementedException("GetIndices: unknown componenttype: " + accessor.componentType); + } + + IEnumerable _GetIndices(glTFBufferView view, int count, int byteOffset, glComponentType componentType) + { + switch (componentType) + { + case glComponentType.UNSIGNED_BYTE: + { + return GetAttrib(count, byteOffset, view).Select(x => (int)(x)); + } + + case glComponentType.UNSIGNED_SHORT: + { + return GetAttrib(count, byteOffset, view).Select(x => (int)(x)); + } + + case glComponentType.UNSIGNED_INT: + { + return GetAttrib(count, byteOffset, view).Select(x => (int)(x)); + } + } + throw new NotImplementedException("GetIndices: unknown componenttype: " + componentType); + } + + public int[] GetIndices(int accessorIndex) + { + int count; + var result = _GetIndices(accessors[accessorIndex], out count); + var indices = new int[count]; + + // flip triangles + var it = result.GetEnumerator(); + { + for (int i = 0; i < count; i += 3) + { + it.MoveNext(); indices[i + 2] = it.Current; + it.MoveNext(); indices[i + 1] = it.Current; + it.MoveNext(); indices[i] = it.Current; + } + } + + return indices; + } + + public T[] GetArrayFromAccessor(int accessorIndex) where T : struct + { + var vertexAccessor = accessors[accessorIndex]; + + if (vertexAccessor.count <= 0) return new T[] { }; + + var result = (vertexAccessor.bufferView != -1) + ? GetAttrib(vertexAccessor, bufferViews[vertexAccessor.bufferView]) + : new T[vertexAccessor.count] + ; + + var sparse = vertexAccessor.sparse; + if (sparse != null && sparse.count > 0) + { + // override sparse values + var indices = _GetIndices(bufferViews[sparse.indices.bufferView], sparse.count, sparse.indices.byteOffset, sparse.indices.componentType); + var values = GetAttrib(sparse.count, sparse.values.byteOffset, bufferViews[sparse.values.bufferView]); + + var it = indices.GetEnumerator(); + for (int i = 0; i < sparse.count; ++i) + { + it.MoveNext(); + result[it.Current] = values[i]; + } + } + return result; + } + + public float[] GetArrayFromAccessorAsFloat(int accessorIndex) + { + var vertexAccessor = accessors[accessorIndex]; + + if (vertexAccessor.count <= 0) return new float[] { }; + + var bufferCount = vertexAccessor.count * vertexAccessor.TypeCount; + var result = (vertexAccessor.bufferView != -1) + ? GetAttrib(bufferCount, vertexAccessor.byteOffset, bufferViews[vertexAccessor.bufferView]) + : new float[bufferCount] + ; + + var sparse = vertexAccessor.sparse; + if (sparse != null && sparse.count > 0) + { + // override sparse values + var indices = _GetIndices(bufferViews[sparse.indices.bufferView], sparse.count, sparse.indices.byteOffset, sparse.indices.componentType); + var values = GetAttrib(sparse.count * vertexAccessor.TypeCount, sparse.values.byteOffset, bufferViews[sparse.values.bufferView]); + + var it = indices.GetEnumerator(); + for (int i = 0; i < sparse.count; ++i) + { + it.MoveNext(); + result[it.Current] = values[i]; + } + } + return result; + } + #endregion + + [JsonSchema(MinItems = 1)] + public List textures = new List(); + + [JsonSchema(MinItems = 1)] + public List samplers = new List(); + public glTFTextureSampler GetSampler(int index) + { + if (samplers.Count == 0) + { + samplers.Add(new glTFTextureSampler()); // default sampler + } + + return samplers[index]; + } + + [JsonSchema(MinItems = 1)] + public List images = new List(); + + public int GetImageIndexFromTextureIndex(int textureIndex) + { + return textures[textureIndex].source; + } + + public glTFImage GetImageFromTextureIndex(int textureIndex) + { + return images[GetImageIndexFromTextureIndex(textureIndex)]; + } + + public glTFTextureSampler GetSamplerFromTextureIndex(int textureIndex) + { + var samplerIndex = textures[textureIndex].sampler; + return GetSampler(samplerIndex); + } + + public ArraySegment GetImageBytes(IStorage storage, int imageIndex, out string textureName) + { + var image = images[imageIndex]; + if (string.IsNullOrEmpty(image.uri)) + { + // + // use buffer view (GLB) + // + //m_imageBytes = ToArray(byteSegment); + textureName = !string.IsNullOrEmpty(image.name) ? image.name : string.Format("{0:00}#GLB", imageIndex); + return GetViewBytes(image.bufferView); + } + else + { + if (image.uri.StartsWith("data:")) + { + textureName = !string.IsNullOrEmpty(image.name) ? image.name : string.Format("{0:00}#Base64Embeded", imageIndex); + } + else + { + textureName = !string.IsNullOrEmpty(image.name) ? image.name : Path.GetFileNameWithoutExtension(image.uri); + } + return storage.Get(image.uri); + } + } + + [JsonSchema(MinItems = 1)] + public List materials = new List(); + public string GetUniqueMaterialName(int index) + { + if (materials.Any(x => string.IsNullOrEmpty(x.name)) + || materials.Select(x => x.name).Distinct().Count() != materials.Count) + { + return String.Format("{0:00}_{1}", index, materials[index].name); + } + else + { + return materials[index].name; + } + } + + public bool MaterialHasVertexColor(glTFMaterial material) + { + if (material == null) + { + return false; + } + + var materialIndex = materials.IndexOf(material); + if (materialIndex == -1) + { + return false; + } + + return MaterialHasVertexColor(materialIndex); + } + + [JsonSchema(MinItems = 1)] + public List meshes = new List(); + + public bool MaterialHasVertexColor(int materialIndex) + { + if (materialIndex < 0 || materialIndex >= materials.Count) + { + return false; + } + + var hasVertexColor = meshes.SelectMany(x => x.primitives).Any(x => x.material == materialIndex && x.HasVertexColor); + return hasVertexColor; + } + + [JsonSchema(MinItems = 1)] + public List nodes = new List(); + + [JsonSchema(MinItems = 1)] + public List skins = new List(); + + [JsonSchema(Dependencies = new string[] { "scenes" }, Minimum = 0)] + public int scene; + + [JsonSchema(MinItems = 1)] + public List scenes = new List(); + public int[] rootnodes + { + get + { + return scenes[scene].nodes; + } + } + + [JsonSchema(MinItems = 1)] + public List animations = new List(); + + [JsonSchema(MinItems = 1)] + public List cameras = new List(); + + [JsonSchema(MinItems = 1)] + public List extensionsUsed = new List(); + + [JsonSchema(MinItems = 1)] + public List extensionsRequired = new List(); + + public glTF_extensions extensions = new glTF_extensions(); + public gltf_extras extras = new gltf_extras(); + + public override string ToString() + { + return string.Format("{0}", asset); + } + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + if (extensionsUsed.Count > 0) + { + f.KeyValue(() => extensionsUsed); + } + if (extensions.__count > 0) + { + f.KeyValue(() => extensions); + } + if (extras.__count > 0) + { + f.KeyValue(() => extras); + } + + f.KeyValue(() => asset); + + // buffer + if (buffers.Any()) + { + f.KeyValue(() => buffers); + } + if (bufferViews.Any()) + { + f.Key("bufferViews"); f.GLTFValue(bufferViews); + } + if (accessors.Any()) + { + f.Key("accessors"); f.GLTFValue(accessors); + } + + // materials + if (images.Any()) + { + f.Key("images"); f.GLTFValue(images); + if (samplers.Count == 0) + { + samplers.Add(new glTFTextureSampler()); + } + } + + if (samplers.Any()) + { + f.Key("samplers"); f.GLTFValue(samplers); + } + + if (textures.Any()) + { + f.Key("textures"); f.GLTFValue(textures); + } + if (materials.Any()) + { + f.Key("materials"); f.GLTFValue(materials); + } + + // meshes + if (meshes.Any()) + { + f.KeyValue(() => meshes); + } + if (skins.Any()) + { + f.KeyValue(() => skins); + } + + // scene + if (nodes.Any()) + { + f.KeyValue(() => nodes); + } + if (scenes.Any()) + { + f.KeyValue(() => scenes); + if (scene >= 0) + { + f.KeyValue(() => scene); + } + } + + // animations + if (animations.Any()) + { + f.Key("animations"); f.GLTFValue(animations); + } + } + + public bool Equals(glTF other) + { + return + textures.SequenceEqual(other.textures) + && samplers.SequenceEqual(other.samplers) + && images.SequenceEqual(other.images) + && materials.SequenceEqual(other.materials) + && meshes.SequenceEqual(other.meshes) + && nodes.SequenceEqual(other.nodes) + && skins.SequenceEqual(other.skins) + && scene == other.scene + && scenes.SequenceEqual(other.scenes) + && animations.SequenceEqual(other.animations) + ; + } + + bool UsedExtension(string key) + { + if (extensionsUsed.Contains(key)) + { + return true; + } + + return false; + } + + static Utf8String s_extensions = Utf8String.From("extensions"); + + void Traverse(ListTreeNode node, JsonFormatter f, Utf8String parentKey) + { + if(node.IsMap()) + { + f.BeginMap(); + foreach(var kv in node.ObjectItems()) + { + if (parentKey == s_extensions) + { + if (!UsedExtension(kv.Key.GetString())) + { + continue; + } + } + f.Key(kv.Key.GetUtf8String()); + Traverse(kv.Value, f, kv.Key.GetUtf8String()); + } + f.EndMap(); + } + else if(node.IsArray()) + { + f.BeginList(); + foreach(var x in node.ArrayItems()) + { + Traverse(x, f, default(Utf8String)); + } + f.EndList(); + } + else + { + f.Value(node); + } + } + + string RemoveUnusedExtensions(string json) + { + var f = new JsonFormatter(); + + Traverse(JsonParser.Parse(json), f, default(Utf8String)); + + return f.ToString(); + } + + public byte[] ToGlbBytes(bool UseUniJSONSerializer = false) + { + string json; + if (UseUniJSONSerializer) + { + json = JsonSchema.FromType(GetType()).Serialize(this); + } + else + { + json = ToJson(); + } + + RemoveUnusedExtensions(json); + + return Glb.ToBytes(json, buffers[0].GetBytes()); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTF.cs.meta b/UniGLTF/Core/Scripts/Format/glTF.cs.meta new file mode 100644 index 000000000..b10478954 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTF.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 317adeaa75a118a4f857721c90946933 +timeCreated: 1516618885 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFAnimation.cs b/UniGLTF/Core/Scripts/Format/glTFAnimation.cs new file mode 100644 index 000000000..04de4b8a8 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFAnimation.cs @@ -0,0 +1,210 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using UniJSON; + + +namespace UniGLTF +{ + [Serializable] + public class glTFAnimationTarget : JsonSerializableBase + { + [JsonSchema(Minimum = 0)] + public int node; + + [JsonSchema(Required = true, EnumValues = new object[] { "translation", "rotation", "scale", "weights" }, EnumSerializationType = EnumSerializationType.AsString)] + public string path; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => node); + if (!string.IsNullOrEmpty(path)) + { + f.KeyValue(() => path); + } + } + + public enum Interpolations + { + LINEAR, + STEP, + CUBICSPLINE + } + + public const string PATH_TRANSLATION = "translation"; + public const string PATH_EULER_ROTATION = "rotation"; + public const string PATH_ROTATION = "rotation"; + public const string PATH_SCALE = "scale"; + public const string PATH_WEIGHT = "weights"; + public const string NOT_IMPLEMENTED = "NotImplemented"; + + public enum AnimationPropertys + { + Translation, + EulerRotation, + Rotation, + Scale, + Weight, + BlendShape, + + NotImplemented + } + + public static string GetPathName(AnimationPropertys property) + { + switch (property) + { + case AnimationPropertys.Translation: + return PATH_TRANSLATION; + case AnimationPropertys.EulerRotation: + case AnimationPropertys.Rotation: + return PATH_ROTATION; + case AnimationPropertys.Scale: + return PATH_SCALE; + case AnimationPropertys.BlendShape: + return PATH_WEIGHT; + default: throw new NotImplementedException(); + } + } + + public static AnimationPropertys GetAnimationProperty(string path) + { + switch (path) + { + case PATH_TRANSLATION: + return AnimationPropertys.Translation; + case PATH_ROTATION: + return AnimationPropertys.Rotation; + case PATH_SCALE: + return AnimationPropertys.Scale; + case PATH_WEIGHT: + return AnimationPropertys.BlendShape; + default: throw new NotImplementedException(); + } + } + + public static int GetElementCount(AnimationPropertys property) + { + switch (property) + { + case AnimationPropertys.Translation: return 3; + case AnimationPropertys.EulerRotation: return 3; + case AnimationPropertys.Rotation: return 4; + case AnimationPropertys.Scale: return 3; + case AnimationPropertys.BlendShape: return 1; + default: throw new NotImplementedException(); + } + } + + public static int GetElementCount(string path) + { + return GetElementCount(GetAnimationProperty(path)); + } + } + + [Serializable] + public class glTFAnimationChannel : JsonSerializableBase + { + [JsonSchema(Required = true, Minimum = 0)] + public int sampler = -1; + + [JsonSchema(Required = true)] + public glTFAnimationTarget target; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => sampler); + f.KeyValue(() => target); + } + } + + [Serializable] + public class glTFAnimationSampler : JsonSerializableBase + { + [JsonSchema(Required = true, Minimum = 0)] + public int input = -1; + + [JsonSchema(EnumValues = new object[] { "LINEAR", "STEP", "CUBICSPLINE" }, EnumSerializationType = EnumSerializationType.AsString)] + public string interpolation; + + [JsonSchema(Required = true, Minimum = 0)] + public int output = -1; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => input); + if (!string.IsNullOrEmpty(interpolation)) + { + f.KeyValue(() => interpolation); + } + f.KeyValue(() => output); + } + } + + [Serializable] + public class glTFAnimation : JsonSerializableBase + { + public string name = ""; + + [JsonSchema(Required = true, MinItems = 1)] + public List channels = new List(); + + [JsonSchema(Required = true, MinItems = 1)] + public List samplers = new List(); + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + if (!string.IsNullOrEmpty(name)) + { + f.KeyValue(() => name); + } + + f.KeyValue(() => channels); + f.KeyValue(() => samplers); + } + + public int AddChannelAndGetSampler(int nodeIndex, glTFAnimationTarget.AnimationPropertys property) + { + // find channel + var channel = channels.FirstOrDefault(x => x.target.node == nodeIndex && x.target.path == glTFAnimationTarget.GetPathName(property)); + if (channel != null) + { + return channel.sampler; + } + + // not found. create new + var samplerIndex = samplers.Count; + var sampler = new glTFAnimationSampler(); + samplers.Add(sampler); + + channel = new glTFAnimationChannel + { + sampler = samplerIndex, + target = new glTFAnimationTarget + { + node = nodeIndex, + path = glTFAnimationTarget.GetPathName(property), + }, + }; + channels.Add(channel); + + return samplerIndex; + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFAnimation.cs.meta b/UniGLTF/Core/Scripts/Format/glTFAnimation.cs.meta new file mode 100644 index 000000000..4ca7197c1 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFAnimation.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: f571aa160b9de354c8f5fcc6a38e41f1 +timeCreated: 1515833098 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFAssets.cs b/UniGLTF/Core/Scripts/Format/glTFAssets.cs new file mode 100644 index 000000000..74875e6ac --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFAssets.cs @@ -0,0 +1,34 @@ +using System; +using UniJSON; + +namespace UniGLTF +{ + [Serializable] + public class glTFAssets : JsonSerializableBase + { + public string generator; + + [JsonSchema(Required = true, Pattern = "^[0-9]+\\.[0-9]+$")] + public string version; + + public string copyright; + + [JsonSchema(Pattern = "^[0-9]+\\.[0-9]+$")] + public string minVersion; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.Key("generator"); f.Value(generator); + f.Key("version"); f.Value(version); + } + + public override string ToString() + { + return string.Format("GLTF-{0} generated by {1}", version, generator); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFAssets.cs.meta b/UniGLTF/Core/Scripts/Format/glTFAssets.cs.meta new file mode 100644 index 000000000..3e851a19e --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFAssets.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 1b759a27457c3cb49adea3fba807e447 +timeCreated: 1516618004 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFBuffer.cs b/UniGLTF/Core/Scripts/Format/glTFBuffer.cs new file mode 100644 index 000000000..2d6c14314 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFBuffer.cs @@ -0,0 +1,261 @@ +using System; +using System.Linq; +using UniJSON; + +namespace UniGLTF +{ + [Serializable] + public class glTFBuffer : JsonSerializableBase + { + IBytesBuffer Storage; + + public void OpenStorage(IStorage storage) + { + Storage = new ArraySegmentByteBuffer(storage.Get(uri)); + /* + if (string.IsNullOrEmpty(uri)) + { + Storage = (glbDataBytes); + } + else + { + Storage = new UriByteBuffer(baseDir, uri); + } + */ + } + + public glTFBuffer(IBytesBuffer storage) + { + Storage = storage; + } + + public string uri; + + [JsonSchema(Required = true, Minimum = 1)] + public int byteLength; + + // empty schemas + public object extensions; + public object extras; + public string name; + + public glTFBufferView Append(T[] array, glBufferTarget target) where T : struct + { + return Append(new ArraySegment(array), target); + } + public glTFBufferView Append(ArraySegment segment, glBufferTarget target) where T : struct + { + var view = Storage.Extend(segment, target); + byteLength = Storage.GetBytes().Count; + return view; + } + + public ArraySegment GetBytes() + { + return Storage.GetBytes(); + } + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + if (!string.IsNullOrEmpty(uri)) + { + f.KeyValue(() => uri); + } + f.KeyValue(() => byteLength); + } + } + + [Serializable] + public class glTFBufferView : JsonSerializableBase + { + [JsonSchema(Required = true, Minimum = 0)] + public int buffer; + + [JsonSchema(Minimum = 0)] + public int byteOffset; + + [JsonSchema(Required = true, Minimum = 1)] + public int byteLength; + + [JsonSchema(Minimum = 4, Maximum = 252, MultipleOf = 4)] + public int byteStride; + + [JsonSchema(EnumSerializationType = EnumSerializationType.AsInt, EnumExcludes = new object[] { glBufferTarget.NONE })] + public glBufferTarget target; + + // empty schemas + public object extensions; + public object extras; + public string name; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => buffer); + f.KeyValue(() => byteOffset); + f.KeyValue(() => byteLength); + if (target != glBufferTarget.NONE) + { + f.Key("target"); f.Value((int)target); + } + /* When this is not defined, data is tightly packed. When two or more accessors use the same bufferView, this field must be defined. + if (byteStride >= 4) + { + f.KeyValue(() => byteStride); + } + */ + } + } + + [Serializable] + public class glTFSparseIndices : JsonSerializableBase + { + [JsonSchema(Required = true, Minimum = 0)] + public int bufferView = -1; + + [JsonSchema(Minimum = 0)] + public int byteOffset; + + [JsonSchema(Required = true, EnumValues = new object[] { 5121, 5123, 5125 })] + public glComponentType componentType; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => bufferView); + f.KeyValue(() => byteOffset); + f.Key("componentType"); f.Value((int)componentType); + } + } + + [Serializable] + public class glTFSparseValues : JsonSerializableBase + { + [JsonSchema(Required = true, Minimum = 0)] + public int bufferView = -1; + + [JsonSchema(Minimum = 0)] + public int byteOffset; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => bufferView); + f.KeyValue(() => byteOffset); + } + } + + [Serializable] + public class glTFSparse : JsonSerializableBase + { + [JsonSchema(Required = true, Minimum = 1)] + public int count; + + [JsonSchema(Required = true)] + public glTFSparseIndices indices; + + [JsonSchema(Required = true)] + public glTFSparseValues values; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => count); + f.KeyValue(() => indices); + f.KeyValue(() => values); + } + } + + [Serializable] + public class glTFAccessor : JsonSerializableBase + { + [JsonSchema(Minimum = 0)] + public int bufferView = -1; + + [JsonSchema(Minimum = 0, Dependencies = new string[] { "bufferView" })] + public int byteOffset; + + [JsonSchema(Required = true, EnumValues = new object[] { "SCALAR", "VEC2", "VEC3", "VEC4", "MAT2", "MAT3", "MAT4" }, EnumSerializationType = EnumSerializationType.AsString)] + public string type; + + public int TypeCount + { + get + { + switch (type) + { + case "SCALAR": + return 1; + case "VEC2": + return 2; + case "VEC3": + return 3; + case "VEC4": + case "MAT2": + return 4; + case "MAT3": + return 9; + case "MAT4": + return 16; + default: + throw new NotImplementedException(); + } + } + } + + [JsonSchema(Required = true, EnumSerializationType = EnumSerializationType.AsInt)] + public glComponentType componentType; + + [JsonSchema(Required = true, Minimum = 1)] + public int count; + + [JsonSchema(MinItems = 1, MaxItems = 16)] + public float[] max; + + [JsonSchema(MinItems = 1, MaxItems = 16)] + public float[] min; + + public bool normalized; + public glTFSparse sparse; + + // empty schemas + public string name; + + public object extensions; + + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => bufferView); + f.KeyValue(() => byteOffset); + f.KeyValue(() => type); + f.Key("componentType"); f.Value((int)componentType); + f.KeyValue(() => count); + if (max != null && max.Any()) + { + f.KeyValue(() => max); + } + if (min != null && min.Any()) + { + f.KeyValue(() => min); + } + + if (sparse != null && sparse.count > 0) + { + f.KeyValue(() => sparse); + } + + f.KeyValue(() => normalized); + f.KeyValue(() => name); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFBuffer.cs.meta b/UniGLTF/Core/Scripts/Format/glTFBuffer.cs.meta new file mode 100644 index 000000000..876e42e6f --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFBuffer.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 50888f361f18c3140a23e9bfdcf43557 +timeCreated: 1515832638 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFCamera.cs b/UniGLTF/Core/Scripts/Format/glTFCamera.cs new file mode 100644 index 000000000..36dcc9832 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFCamera.cs @@ -0,0 +1,60 @@ +using System; +using UniJSON; + +namespace UniGLTF +{ + public enum ProjectionType + { + Perspective, + Orthographic + } + + [Serializable] + public class glTFOrthographic + { + [JsonSchema(Required = true)] + public float xmag; + [JsonSchema(Required = true)] + public float ymag; + [JsonSchema(Required = true, Minimum = 0.0f, ExclusiveMinimum = true)] + public float zfar; + [JsonSchema(Required = true, Minimum = 0.0f)] + public float znear; + + [JsonSchema(MinProperties = 1)] + public glTFOrthographic_extensions extensions; + [JsonSchema(MinProperties = 1)] + public glTFOrthographic_extras extras; + } + + [Serializable] + public class glTFPerspective + { + [JsonSchema(Minimum = 0.0f, ExclusiveMinimum = true)] + public float aspectRatio; + [JsonSchema(Required = true, Minimum = 0.0f, ExclusiveMinimum = true)] + public float yfov; + [JsonSchema(Minimum = 0.0f, ExclusiveMinimum = true)] + public float zfar; + [JsonSchema(Required = true, Minimum = 0.0f, ExclusiveMinimum = true)] + public float znear; + + public glTFPerspective_extensions extensions; + public glTFPerspective_extras extras; + } + + [Serializable] + public class glTFCamera + { + public glTFOrthographic orthographic; + public glTFPerspective perspective; + + [JsonSchema(Required = true, EnumSerializationType = EnumSerializationType.AsLowerString)] + public ProjectionType type; + + public string name; + + public glTFCamera_extensions extensions; + public glTFCamera_extras extras; + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFCamera.cs.meta b/UniGLTF/Core/Scripts/Format/glTFCamera.cs.meta new file mode 100644 index 000000000..2ecc50075 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFCamera.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a4ff02eb93400b142a5ff47d1067bd8b +timeCreated: 1531635880 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFMaterial.cs b/UniGLTF/Core/Scripts/Format/glTFMaterial.cs new file mode 100644 index 000000000..10c344eaf --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFMaterial.cs @@ -0,0 +1,225 @@ +using System; +using UniJSON; + +namespace UniGLTF +{ + public enum glTFTextureTypes + { + BaseColor, + Metallic, + Normal, + Occlusion, + Emissive, + Unknown + } + + public interface IglTFTextureinfo + { + glTFTextureTypes TextreType { get; } + } + + [Serializable] + public abstract class glTFTextureInfo : JsonSerializableBase, IglTFTextureinfo + { + [JsonSchema(Required = true, Minimum = 0)] + public int index = -1; + + [JsonSchema(Minimum = 0)] + public int texCoord; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => index); + f.KeyValue(() => texCoord); + } + + public abstract glTFTextureTypes TextreType { get; } + } + + + [Serializable] + public class glTFMaterialBaseColorTextureInfo : glTFTextureInfo + { + public override glTFTextureTypes TextreType + { + get { return glTFTextureTypes.BaseColor; } + } + } + + [Serializable] + public class glTFMaterialMetallicRoughnessTextureInfo : glTFTextureInfo + { + public override glTFTextureTypes TextreType + { + get { return glTFTextureTypes.Metallic; } + } + } + + [Serializable] + public class glTFMaterialNormalTextureInfo : glTFTextureInfo + { + public float scale = 1.0f; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => scale); + base.SerializeMembers(f); + } + + public override glTFTextureTypes TextreType + { + get { return glTFTextureTypes.Normal; } + } + } + + [Serializable] + public class glTFMaterialOcclusionTextureInfo : glTFTextureInfo + { + [JsonSchema(Minimum = 0.0, Maximum = 1.0)] + public float strength = 1.0f; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => strength); + base.SerializeMembers(f); + } + + public override glTFTextureTypes TextreType + { + get { return glTFTextureTypes.Occlusion; } + } + } + + [Serializable] + public class glTFMaterialEmissiveTextureInfo : glTFTextureInfo + { + public override glTFTextureTypes TextreType + { + get { return glTFTextureTypes.Emissive; } + } + } + + [Serializable] + public class glTFPbrMetallicRoughness : JsonSerializableBase + { + public glTFMaterialBaseColorTextureInfo baseColorTexture = null; + + [JsonSchema(MinItems = 4, MaxItems = 4)] + [ItemJsonSchema(Minimum = 0.0, Maximum = 1.0)] + public float[] baseColorFactor; + + public glTFMaterialMetallicRoughnessTextureInfo metallicRoughnessTexture = null; + + [JsonSchema(Minimum = 0.0, Maximum = 1.0)] + public float metallicFactor = 1.0f; + + [JsonSchema(Minimum = 0.0, Maximum = 1.0)] + public float roughnessFactor = 1.0f; + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + if (baseColorTexture != null) + { + f.KeyValue(() => baseColorTexture); + } + if (baseColorFactor != null) + { + f.KeyValue(() => baseColorFactor); + } + if (metallicRoughnessTexture != null) + { + f.KeyValue(() => metallicRoughnessTexture); + } + f.KeyValue(() => metallicFactor); + f.KeyValue(() => roughnessFactor); + } + } + + [Serializable] + public class glTFMaterial : JsonSerializableBase + { + public string name; + public glTFPbrMetallicRoughness pbrMetallicRoughness; + public glTFMaterialNormalTextureInfo normalTexture = null; + + public glTFMaterialOcclusionTextureInfo occlusionTexture = null; + + public glTFMaterialEmissiveTextureInfo emissiveTexture = null; + + [JsonSchema(MinItems = 3, MaxItems = 3)] + [ItemJsonSchema(Minimum = 0.0, Maximum = 1.0)] + public float[] emissiveFactor; + + [JsonSchema(EnumValues = new object[] { "OPAQUE", "MASK", "BLEND" }, EnumSerializationType = EnumSerializationType.AsUpperString)] + public string alphaMode; + + [JsonSchema(Dependencies = new string[] { "alphaMode" }, Minimum = 0.0)] + public float alphaCutoff = 0.5f; + + public bool doubleSided; + + [JsonSchema(SkipSchemaComparison = true)] + public glTFMaterial_extensions extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + if (!String.IsNullOrEmpty(name)) + { + f.Key("name"); f.Value(name); + } + if (pbrMetallicRoughness != null) + { + f.Key("pbrMetallicRoughness"); f.GLTFValue(pbrMetallicRoughness); + } + if (normalTexture != null) + { + f.Key("normalTexture"); f.GLTFValue(normalTexture); + } + if (occlusionTexture != null) + { + f.Key("occlusionTexture"); f.GLTFValue(occlusionTexture); + } + if (emissiveTexture != null) + { + f.Key("emissiveTexture"); f.GLTFValue(emissiveTexture); + } + if (emissiveFactor != null) + { + f.Key("emissiveFactor"); f.Serialize(emissiveFactor); + } + + f.KeyValue(() => doubleSided); + + if (!string.IsNullOrEmpty(alphaMode)) + { + f.KeyValue(() => alphaMode); + } + + if (extensions != null) + { + f.KeyValue(() => extensions); + } + } + + public glTFTextureInfo[] GetTextures() + { + return new glTFTextureInfo[] + { + pbrMetallicRoughness.baseColorTexture, + pbrMetallicRoughness.metallicRoughnessTexture, + normalTexture, + occlusionTexture, + emissiveTexture + }; + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFMaterial.cs.meta b/UniGLTF/Core/Scripts/Format/glTFMaterial.cs.meta new file mode 100644 index 000000000..69c4328ec --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFMaterial.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: f6315a9155238724c9b221c155c73f68 +timeCreated: 1515601872 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFMesh.cs b/UniGLTF/Core/Scripts/Format/glTFMesh.cs new file mode 100644 index 000000000..854c1fac5 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFMesh.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using UniJSON; + +namespace UniGLTF +{ + [Serializable] + public class glTFAttributes : JsonSerializableBase + { + [JsonSchema(Minimum = 0)] + public int POSITION = -1; + + [JsonSchema(Minimum = 0)] + public int NORMAL = -1; + + [JsonSchema(Minimum = 0)] + public int TANGENT = -1; + + [JsonSchema(Minimum = 0)] + public int TEXCOORD_0 = -1; + + [JsonSchema(Minimum = 0)] + public int COLOR_0 = -1; + + [JsonSchema(Minimum = 0)] + public int JOINTS_0 = -1; + + [JsonSchema(Minimum = 0)] + public int WEIGHTS_0 = -1; + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override bool Equals(object obj) + { + var rhs = obj as glTFAttributes; + if (rhs == null) + { + return base.Equals(obj); + } + + return POSITION == rhs.POSITION + && NORMAL == rhs.NORMAL + && TANGENT == rhs.TANGENT + && TEXCOORD_0 == rhs.TEXCOORD_0 + && COLOR_0 == rhs.COLOR_0 + && JOINTS_0 == rhs.JOINTS_0 + && WEIGHTS_0 == rhs.WEIGHTS_0 + ; + } + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => POSITION); + if (NORMAL != -1) f.KeyValue(() => NORMAL); + if (TANGENT != -1) f.KeyValue(() => TANGENT); + if (TEXCOORD_0 != -1) f.KeyValue(() => TEXCOORD_0); + if (COLOR_0 != -1) f.KeyValue(() => COLOR_0); + if (JOINTS_0 != -1) f.KeyValue(() => JOINTS_0); + if (WEIGHTS_0 != -1) f.KeyValue(() => WEIGHTS_0); + } + } + + [Serializable] + public class gltfMorphTarget : JsonSerializableBase + { + public int POSITION = -1; + public int NORMAL = -1; + public int TANGENT = -1; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => POSITION); + if (NORMAL >= 0) f.KeyValue(() => NORMAL); + if (TANGENT >= 0) f.KeyValue(() => TANGENT); + } + } + + /// + /// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/schema/mesh.primitive.schema.json + /// + [Serializable] + public class glTFPrimitives : JsonSerializableBase + { + [JsonSchema(EnumValues = new object[] { 0, 1, 2, 3, 4, 5, 6 })] + public int mode; + + [JsonSchema(Minimum = 0)] + public int indices = -1; + + [JsonSchema(Required = true, SkipSchemaComparison = true)] + public glTFAttributes attributes; + + public bool HasVertexColor + { + get + { + return attributes.COLOR_0 != -1; + } + } + + [JsonSchema(Minimum = 0)] + public int material; + + [JsonSchema(MinItems = 1)] + [ItemJsonSchema(SkipSchemaComparison = true)] + public List targets = new List(); + + public glTFPrimitives_extras extras = new glTFPrimitives_extras(); + + [JsonSchema(SkipSchemaComparison = true)] + public glTFPrimitives_extensions extensions = new glTFPrimitives_extensions(); + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => mode); + f.KeyValue(() => indices); + f.Key("attributes"); f.GLTFValue(attributes); + f.KeyValue(() => material); + if (targets != null && targets.Count > 0) + { + f.Key("targets"); f.GLTFValue(targets); + } + if (extensions.KHR_draco_mesh_compression != null) + { + f.KeyValue(() => extensions); + } + if (extras.targetNames.Count > 0) + { + f.KeyValue(() => extras); + } + } + } + + [Serializable] + public class glTFMesh : JsonSerializableBase + { + public string name; + + [JsonSchema(Required = true, MinItems = 1)] + public List primitives; + + [JsonSchema(MinItems = 1)] + public float[] weights; + + // empty schemas + public object extensions; + public object extras; + + public glTFMesh(string _name) + { + name = _name; + primitives = new List(); + } + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => name); + f.Key("primitives"); f.GLTFValue(primitives); + if (weights != null && weights.Length > 0) + { + f.KeyValue(() => weights); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFMesh.cs.meta b/UniGLTF/Core/Scripts/Format/glTFMesh.cs.meta new file mode 100644 index 000000000..5d8622c5a --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFMesh.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 20634c83ea30c99449cd85ef46368b13 +timeCreated: 1516622346 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFNode.cs b/UniGLTF/Core/Scripts/Format/glTFNode.cs new file mode 100644 index 000000000..e21dc10f0 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFNode.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using UniJSON; + +namespace UniGLTF +{ + [Serializable] + public class glTFNode : JsonSerializableBase + { + public string name = ""; + + [JsonSchema(MinItems = 1)] + [ItemJsonSchema(Minimum = 0)] + public int[] children; + + [JsonSchema(MinItems = 16, MaxItems = 16)] + public float[] matrix; + + [JsonSchema(MinItems = 3, MaxItems = 3)] + public float[] translation; + + [JsonSchema(MinItems = 4, MaxItems = 4)] + [ItemJsonSchema(Minimum = -1.0, Maximum = 1.0)] + public float[] rotation; + + [JsonSchema(MinItems = 3, MaxItems = 3)] + public float[] scale; + + [JsonSchema(Minimum = 0)] + public int mesh = -1; + + [JsonSchema(Dependencies = new string[] { "mesh" }, Minimum = 0)] + public int skin = -1; + + [JsonSchema(Dependencies = new string[] { "mesh" }, MinItems = 1)] + public float[] weights; + + [JsonSchema(Minimum = 0)] + public int camera = -1; + + // empty schemas + public glTFNode_extensions extensions; + public glTFNode_extra extras = new glTFNode_extra(); + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + if (children != null && children.Any()) + { + f.Key("children"); f.BeginList(); + foreach (var child in children) + { + f.Value(child); + } + f.EndList(); + } + + if (!string.IsNullOrEmpty(name)) f.KeyValue(() => name); + if (matrix != null) f.KeyValue(() => matrix); + if (translation != null) f.KeyValue(() => translation); + if (rotation != null) f.KeyValue(() => rotation); + if (scale != null) f.KeyValue(() => scale); + + if (mesh >= 0) f.KeyValue(() => mesh); + if (camera >= 0) f.KeyValue(() => camera); + if (skin >= 0) + { + f.KeyValue(() => skin); + + if (extras.__count > 0) + { + f.KeyValue(() => extras); + } + } + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFNode.cs.meta b/UniGLTF/Core/Scripts/Format/glTFNode.cs.meta new file mode 100644 index 000000000..b973253a0 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFNode.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 655775a9435baed4281c569519829362 +timeCreated: 1515833011 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFSkin.cs b/UniGLTF/Core/Scripts/Format/glTFSkin.cs new file mode 100644 index 000000000..c66e1e977 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFSkin.cs @@ -0,0 +1,34 @@ +using System; +using UniJSON; + +namespace UniGLTF +{ + [Serializable] + public class glTFSkin : JsonSerializableBase + { + [JsonSchema(Minimum = 0)] + public int inverseBindMatrices = -1; + + [JsonSchema(Required = true, MinItems = 1)] + [ItemJsonSchema(Minimum = 0)] + public int[] joints; + + [JsonSchema(Minimum = 0)] + public int skeleton = -1; + + // empty schemas + public object extensions; + public object extras; + public string name; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => inverseBindMatrices); + f.KeyValue(() => joints); + if (skeleton >= 0) + { + f.KeyValue(() => skeleton); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFSkin.cs.meta b/UniGLTF/Core/Scripts/Format/glTFSkin.cs.meta new file mode 100644 index 000000000..3d20b395d --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFSkin.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 592c03928131f494f99dca5ef46a8ba0 +timeCreated: 1516722509 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glTFTexture.cs b/UniGLTF/Core/Scripts/Format/glTFTexture.cs new file mode 100644 index 000000000..a8ef58995 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFTexture.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using UniJSON; + +namespace UniGLTF +{ + [Serializable] + public class glTFTextureSampler : JsonSerializableBase + { + [JsonSchema(EnumSerializationType = EnumSerializationType.AsInt, + EnumExcludes = new object[] { + glFilter.NONE, + glFilter.NEAREST_MIPMAP_NEAREST, + glFilter.LINEAR_MIPMAP_NEAREST, + glFilter.NEAREST_MIPMAP_LINEAR, + glFilter.LINEAR_MIPMAP_LINEAR, + })] + public glFilter magFilter = glFilter.NEAREST; + + [JsonSchema(EnumSerializationType = EnumSerializationType.AsInt, + EnumExcludes = new object[] { glFilter.NONE })] + public glFilter minFilter = glFilter.NEAREST; + + [JsonSchema(EnumSerializationType = EnumSerializationType.AsInt, + EnumExcludes = new object[] { glWrap.NONE })] + public glWrap wrapS = glWrap.REPEAT; + + [JsonSchema(EnumSerializationType = EnumSerializationType.AsInt, + EnumExcludes = new object[] { glWrap.NONE })] + public glWrap wrapT = glWrap.REPEAT; + + // empty schemas + public object extensions; + public object extras; + public string name; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.Key("magFilter"); f.Value((int)magFilter); + f.Key("minFilter"); f.Value((int)minFilter); + f.Key("wrapS"); f.Value((int)wrapS); + f.Key("wrapT"); f.Value((int)wrapT); + } + } + + [Serializable] + public class glTFImage : JsonSerializableBase + { + public string name; + public string uri; + + [JsonSchema(Dependencies = new string[] { "mimeType" }, Minimum = 0)] + public int bufferView; + + [JsonSchema(EnumValues = new object[] { "image/jpeg", "image/png" }, EnumSerializationType =EnumSerializationType.AsString)] + public string mimeType; + + public string GetExt() + { + switch (mimeType) + { + case "image/png": + return ".png"; + + case "image/jpeg": + return ".jpg"; + + default: + if (uri.StartsWith("data:image/jpeg;")) + { + return ".jpg"; + } + else if (uri.StartsWith("data:image/png;")) + { + return ".png"; + } + else + { + return Path.GetExtension(uri).ToLower(); + } + } + } + + // empty schemas + public object extensions; + public object extras; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => name); + if (!string.IsNullOrEmpty(uri)) + { + f.KeyValue(() => uri); + } + else + { + f.KeyValue(() => bufferView); + f.KeyValue(() => mimeType); + } + } + } + + [Serializable] + public class glTFTexture : JsonSerializableBase + { + [JsonSchema(Minimum = 0)] + public int sampler; + + [JsonSchema(Minimum = 0)] + public int source; + + // empty schemas + public object extensions; + public object extras; + public string name; + + protected override void SerializeMembers(GLTFJsonFormatter f) + { + f.KeyValue(() => sampler); + f.KeyValue(() => source); + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glTFTexture.cs.meta b/UniGLTF/Core/Scripts/Format/glTFTexture.cs.meta new file mode 100644 index 000000000..b38c9e723 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glTFTexture.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 5eabe858953de424787a21d633fedda0 +timeCreated: 1515832646 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/Format/glbTypes.cs b/UniGLTF/Core/Scripts/Format/glbTypes.cs new file mode 100644 index 000000000..74ae03374 --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glbTypes.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; +using System.Text; + +namespace UniGLTF +{ + public enum GlbChunkType : UInt32 + { + JSON = 0x4E4F534A, + BIN = 0x004E4942, + } + + public struct GlbHeader + { + public static void WriteTo(Stream s) + { + s.WriteByte((Byte)'g'); + s.WriteByte((Byte)'l'); + s.WriteByte((Byte)'T'); + s.WriteByte((Byte)'F'); + var bytes = BitConverter.GetBytes((UInt32)2); + s.Write(bytes, 0, bytes.Length); + } + } + + public struct GlbChunk + { + public GlbChunkType ChunkType; + public ArraySegment Bytes; + + public GlbChunk(string json) : this( + GlbChunkType.JSON, + new ArraySegment(Encoding.UTF8.GetBytes(json)) + ) + { + } + + public GlbChunk(ArraySegment bytes) : this( + GlbChunkType.BIN, + bytes + ) + { + } + + public GlbChunk(GlbChunkType type, ArraySegment bytes) + { + ChunkType = type; + Bytes = bytes; + } + + byte GetPaddingByte() + { + // chunk type + switch (ChunkType) + { + case GlbChunkType.JSON: + return 0x20; + + case GlbChunkType.BIN: + return 0x00; + + default: + throw new Exception("unknown chunk type: " + ChunkType); + } + } + + public int WriteTo(Stream s) + { + // padding + var paddingValue = Bytes.Count % 4; + var padding = (paddingValue > 0) ? 4 - paddingValue : 0; + + // size + var bytes = BitConverter.GetBytes((int)(Bytes.Count + padding)); + s.Write(bytes, 0, bytes.Length); + + // chunk type + switch (ChunkType) + { + case GlbChunkType.JSON: + s.WriteByte((byte)'J'); + s.WriteByte((byte)'S'); + s.WriteByte((byte)'O'); + s.WriteByte((byte)'N'); + break; + + case GlbChunkType.BIN: + s.WriteByte((byte)'B'); + s.WriteByte((byte)'I'); + s.WriteByte((byte)'N'); + s.WriteByte((byte)0); + break; + + default: + throw new Exception("unknown chunk type: " + ChunkType); + } + + // body + s.Write(Bytes.Array, Bytes.Offset, Bytes.Count); + + // 4byte align + var pad = GetPaddingByte(); + for(int i=0; i body) + { + using (var s = new MemoryStream()) + { + GlbHeader.WriteTo(s); + + var pos = s.Position; + s.Position += 4; // skip total size + + int size = 12; + + { + var chunk = new GlbChunk(json); + size += chunk.WriteTo(s); + } + { + var chunk = new GlbChunk(body); + size += chunk.WriteTo(s); + } + + s.Position = pos; + var bytes = BitConverter.GetBytes(size); + s.Write(bytes, 0, bytes.Length); + + return s.ToArray(); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/Format/glbTypes.cs.meta b/UniGLTF/Core/Scripts/Format/glbTypes.cs.meta new file mode 100644 index 000000000..fdc70e48e --- /dev/null +++ b/UniGLTF/Core/Scripts/Format/glbTypes.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 9238dc0c44df9484bbdca85d818c9b73 +timeCreated: 1516616871 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO.meta b/UniGLTF/Core/Scripts/IO.meta new file mode 100644 index 000000000..1081f515a --- /dev/null +++ b/UniGLTF/Core/Scripts/IO.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: a7addf524d7f84841b039527f0273bfa +folderAsset: yes +timeCreated: 1517139195 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/AnimationCurveData.cs b/UniGLTF/Core/Scripts/IO/AnimationCurveData.cs new file mode 100644 index 000000000..7b31e2bc2 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationCurveData.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + class AnimationCurveData + { +#if UNITY_EDITOR + public AnimationUtility.TangentMode TangentMode { get; private set; } + public glTFAnimationTarget.AnimationPropertys AnimationProperty { get; private set; } + public int SamplerIndex { get; private set; } + public int ElementCount { get; private set; } + public readonly List Keyframes = new List(); + + public AnimationCurveData(AnimationUtility.TangentMode tangentMode, glTFAnimationTarget.AnimationPropertys property, int samplerIndex, int elementCount) + { + TangentMode = tangentMode; + AnimationProperty = property; + SamplerIndex = samplerIndex; + ElementCount = elementCount; + } + + public string GetInterpolation() + { + switch (TangentMode) + { + case AnimationUtility.TangentMode.Linear: + return glTFAnimationTarget.Interpolations.LINEAR.ToString(); + case AnimationUtility.TangentMode.Constant: + return glTFAnimationTarget.Interpolations.STEP.ToString(); + default: + return glTFAnimationTarget.Interpolations.LINEAR.ToString(); + } + } + + /// + /// キーフレームのデータを入力する + /// + /// + /// + /// + public void SetKeyframeData(float time, float value, int valueOffset) + { + var existKeyframe = Keyframes.FirstOrDefault(x => x.Time == time); + if (existKeyframe != null) + { + existKeyframe.SetValue(value, valueOffset); + } + else + { + var newKeyframe = GetKeyframeData(AnimationProperty, ElementCount); + newKeyframe.Time = time; + newKeyframe.SetValue(value, valueOffset); + Keyframes.Add(newKeyframe); + } + } + + /// + /// キー情報がなかった要素に対して直前のキーの値を入力する + /// + public void RecountEmptyKeyframe() + { + if (Keyframes.Count == 0) + { + return; + } + + Keyframes.Sort((x, y) => (x.Time < y.Time) ? -1 : 1); + + for (int i = 1; i < Keyframes.Count; i++) + { + var current = Keyframes[i]; + var last = Keyframes[i - 1]; + for (int j = 0; j < current.EnterValues.Length; j++) + { + if (!current.EnterValues[j]) + { + Keyframes[i].SetValue(last.Values[j], j); + } + } + + } + } + + /// + /// アニメーションプロパティに対応したキーフレームを挿入する + /// + /// + /// + private static AnimationKeyframeData GetKeyframeData(glTFAnimationTarget.AnimationPropertys property, int elementCount) + { + switch (property) + { + case glTFAnimationTarget.AnimationPropertys.Translation: + return new AnimationKeyframeData(elementCount, (values) => + { + var temp = new Vector3(values[0], values[1], values[2]); + return temp.ReverseZ().ToArray(); + }); + case glTFAnimationTarget.AnimationPropertys.Rotation: + return new AnimationKeyframeData(elementCount, (values) => + { + var temp = new Quaternion(values[0], values[1], values[2], values[3]); + return temp.ReverseZ().ToArray(); + }); + case glTFAnimationTarget.AnimationPropertys.Scale: + return new AnimationKeyframeData(elementCount, null); + case glTFAnimationTarget.AnimationPropertys.BlendShape: + return new AnimationKeyframeData(elementCount, null); + default: + return null; + } + } +#endif + } +} \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/AnimationCurveData.cs.meta b/UniGLTF/Core/Scripts/IO/AnimationCurveData.cs.meta new file mode 100644 index 000000000..1efa99190 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationCurveData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1af21722605d44f58deebfcfca642b32 +timeCreated: 1537442711 \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/AnimationExporter.cs b/UniGLTF/Core/Scripts/IO/AnimationExporter.cs new file mode 100644 index 000000000..fc52ad963 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationExporter.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + + public static class AnimationExporter + { + public class InputOutputValues + { + public float[] Input; + public float[] Output; + } + + public class AnimationWithSampleCurves + { + public glTFAnimation Animation; + public Dictionary SamplerMap = new Dictionary(); + } + +#if UNITY_EDITOR + public static List GetAnimationClips(Animation animation) + { + var clips = new List(); + foreach (AnimationState state in animation) + { + clips.Add(state.clip); + } + return clips; + } + + public static List GetAnimationClips(Animator animator) + { + var clips = new List(); + + RuntimeAnimatorController runtimeAnimatorController = animator.runtimeAnimatorController; + UnityEditor.Animations.AnimatorController animationController = runtimeAnimatorController as UnityEditor.Animations.AnimatorController; + + if (animationController == null) + { + return clips; + } + + foreach (var layer in animationController.layers) + { + foreach (var state in layer.stateMachine.states) + { + clips.Add(state.state.motion as AnimationClip); + } + } + return clips; + } + + static int GetNodeIndex(Transform root, List nodes, string path) + { + var descendant = root.GetFromPath(path); + return nodes.IndexOf(descendant); + } + + public static glTFAnimationTarget.AnimationPropertys PropertyToTarget(string property) + { + if (property.StartsWith("m_LocalPosition.")) + { + return glTFAnimationTarget.AnimationPropertys.Translation; + } + else if (property.StartsWith("localEulerAnglesRaw.")) + { + return glTFAnimationTarget.AnimationPropertys.EulerRotation; + } + else if (property.StartsWith("m_LocalRotation.")) + { + return glTFAnimationTarget.AnimationPropertys.Rotation; + } + else if (property.StartsWith("m_LocalScale.")) + { + return glTFAnimationTarget.AnimationPropertys.Scale; + } + else if (property.StartsWith("blendShape.")) + { + return glTFAnimationTarget.AnimationPropertys.BlendShape; + } + else + { + return glTFAnimationTarget.AnimationPropertys.NotImplemented; + } + } + + public static int GetElementOffset(string property) + { + if (property.EndsWith(".x")) + { + return 0; + } + if (property.EndsWith(".y") || property.StartsWith("blendShape.")) + { + return 1; + } + if (property.EndsWith(".z")) + { + return 2; + } + if (property.EndsWith(".w")) + { + return 3; + } + else + { + throw new NotImplementedException(); + } + } + + public static AnimationWithSampleCurves Export(AnimationClip clip, Transform root, List nodes) + { + var animation = new AnimationWithSampleCurves + { + Animation = new glTFAnimation(), + }; + +#if UNITY_5_6_OR_NEWER + List curveDatas = new List(); + + foreach (var binding in AnimationUtility.GetCurveBindings(clip)) + { + var curve = AnimationUtility.GetEditorCurve(clip, binding); + + var property = AnimationExporter.PropertyToTarget(binding.propertyName); + if (property == glTFAnimationTarget.AnimationPropertys.NotImplemented) + { + Debug.LogWarning("Not Implemented keyframe property : " + binding.propertyName); + continue; + } + if (property == glTFAnimationTarget.AnimationPropertys.EulerRotation) + { + Debug.LogWarning("Interpolation setting of AnimationClip should be Quaternion"); + continue; + } + + var nodeIndex = GetNodeIndex(root, nodes, binding.path); + var samplerIndex = animation.Animation.AddChannelAndGetSampler(nodeIndex, property); + var elementCount = 0; + if (property == glTFAnimationTarget.AnimationPropertys.BlendShape) + { + var mesh = nodes[nodeIndex].GetComponent().sharedMesh; + elementCount = mesh.blendShapeCount; + } + else + { + elementCount = glTFAnimationTarget.GetElementCount(property); + } + + // 同一のsamplerIndexが割り当てられているcurveDataがある場合はそれを使用し、無ければ作る + var curveData = curveDatas.FirstOrDefault(x => x.SamplerIndex == samplerIndex); + if (curveData == null) + { + curveData = new AnimationCurveData(AnimationUtility.GetKeyRightTangentMode(curve, 0), property, samplerIndex, elementCount); + curveDatas.Add(curveData); + } + + // 全てのキーフレームを回収 + int elementOffset = 0; + float valueFactor = 1.0f; + if (property == glTFAnimationTarget.AnimationPropertys.BlendShape) + { + var mesh = nodes[nodeIndex].GetComponent().sharedMesh; + var blendShapeName = binding.propertyName.Replace("blendShape.", ""); + elementOffset = mesh.GetBlendShapeIndex(blendShapeName); + valueFactor = 0.01f; + } + else + { + elementOffset = AnimationExporter.GetElementOffset(binding.propertyName); + } + + if (elementOffset >= 0 && elementOffset < elementCount) + { + for (int i = 0; i < curve.keys.Length; i++) + { + curveData.SetKeyframeData(curve.keys[i].time, curve.keys[i].value * valueFactor, elementOffset); + } + } + } + + //キー挿入 + foreach (var curve in curveDatas) + { + if (curve.Keyframes.Count == 0) + continue; + + curve.RecountEmptyKeyframe(); + + var elementNum = curve.Keyframes.First().Values.Length; + var values = default(InputOutputValues); + if (!animation.SamplerMap.TryGetValue(curve.SamplerIndex, out values)) + { + values = new InputOutputValues(); + values.Input = new float[curve.Keyframes.Count]; + values.Output = new float[curve.Keyframes.Count * elementNum]; + animation.SamplerMap[curve.SamplerIndex] = values; + animation.Animation.samplers[curve.SamplerIndex].interpolation = curve.GetInterpolation(); + } + + int keyframeIndex = 0; + foreach (var keyframe in curve.Keyframes) + { + values.Input[keyframeIndex] = keyframe.Time; + Buffer.BlockCopy(keyframe.GetRightHandCoordinate(), 0, values.Output, keyframeIndex * elementNum * sizeof(float), elementNum * sizeof(float)); + keyframeIndex++; + } + } +#endif + + return animation; + } +#endif + } + } \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/AnimationExporter.cs.meta b/UniGLTF/Core/Scripts/IO/AnimationExporter.cs.meta new file mode 100644 index 000000000..d92b7028c --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationExporter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 015ae41bf6cb4428b8257ead79772908 +timeCreated: 1537443293 \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/AnimationImporter.cs b/UniGLTF/Core/Scripts/IO/AnimationImporter.cs new file mode 100644 index 000000000..e792cfc76 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationImporter.cs @@ -0,0 +1,330 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF +{ + public static class AnimationImporter + { + private enum TangentMode + { + Linear, + Constant, + Cubicspline + } + + private static TangentMode GetTangentMode(string interpolation) + { + if (interpolation == glTFAnimationTarget.Interpolations.LINEAR.ToString()) + { + return TangentMode.Linear; + } + else if (interpolation == glTFAnimationTarget.Interpolations.STEP.ToString()) + { + return TangentMode.Constant; + } + else if (interpolation == glTFAnimationTarget.Interpolations.CUBICSPLINE.ToString()) + { + return TangentMode.Cubicspline; + } + else + { + throw new NotImplementedException(); + } + } + + private static void CalculateTanget(List keyframes, int current) + { + int back = current - 1; + if (back < 0) + { + return; + } + if (current < keyframes.Count) + { + var rightTangent = (keyframes[current].value - keyframes[back].value) / (keyframes[current].time - keyframes[back].time); + keyframes[back] = new Keyframe(keyframes[back].time, keyframes[back].value, keyframes[back].inTangent, rightTangent); + + var leftTangent = (keyframes[back].value - keyframes[current].value) / (keyframes[back].time - keyframes[current].time); + keyframes[current] = new Keyframe(keyframes[current].time, keyframes[current].value, leftTangent, 0); + } + } + + public static Quaternion GetShortest(Quaternion last, Quaternion rot) + { + if (Quaternion.Dot(last, rot) > 0.0) + { + return rot; + } + else + { + return new Quaternion(-rot.x, -rot.y, -rot.z, -rot.w); + } + + } + + public delegate float[] ReverseZ(float[] current, float[] last); + public static void SetAnimationCurve( + AnimationClip targetClip, + string relativePath, + string[] propertyNames, + float[] input, + float[] output, + string interpolation, + Type curveType, + ReverseZ reverse) + { + var tangentMode = GetTangentMode(interpolation); + + var curveCount = propertyNames.Length; + AnimationCurve[] curves = new AnimationCurve[curveCount]; + List[] keyframes = new List[curveCount]; + + int elementNum = curveCount; + int inputIndex = 0; + //Quaternion用 + float[] last = new float[curveCount]; + if (last.Length == 4) + { + last[3] = 1.0f; + } + for (inputIndex = 0; inputIndex < input.Length; ++inputIndex) + { + var time = input[inputIndex]; + var outputIndex = 0; + if (tangentMode == TangentMode.Cubicspline) + { + outputIndex = inputIndex * elementNum * 3; + var value = new float[curveCount]; + for (int i = 0; i < value.Length; i++) + { + value[i] = output[outputIndex + elementNum + i]; + } + var reversed = reverse(value, last); + last = reversed; + for (int i = 0; i < keyframes.Length; i++) + { + if (keyframes[i] == null) + keyframes[i] = new List(); + keyframes[i].Add(new Keyframe( + time, + reversed[i], + output[outputIndex + i], + output[outputIndex + i + elementNum * 2])); + } + } + else + { + outputIndex = inputIndex * elementNum; + var value = new float[curveCount]; + for (int i = 0; i < value.Length; i++) + { + value[i] = output[outputIndex + i]; + } + var reversed = reverse(value, last); + last = reversed; + + for (int i = 0; i < keyframes.Length; i++) + { + if (keyframes[i] == null) + keyframes[i] = new List(); + if (tangentMode == TangentMode.Linear) + { + keyframes[i].Add(new Keyframe(time, reversed[i], 0, 0)); + if (keyframes[i].Count > 0) + { + CalculateTanget(keyframes[i], keyframes[i].Count - 1); + } + } + else if (tangentMode == TangentMode.Constant) + keyframes[i].Add(new Keyframe(time, reversed[i], 0, float.PositiveInfinity)); + } + } + } + + for (int i = 0; i < curves.Length; i++) + { + curves[i] = new AnimationCurve(); + for (int j = 0; j < keyframes[i].Count; j++) + { + curves[i].AddKey(keyframes[i][j]); + } + + targetClip.SetCurve(relativePath, curveType, propertyNames[i], curves[i]); + } + } + + public static List ImportAnimationClip(ImporterContext ctx) + { + List animasionClips = new List(); + for (int i = 0; i < ctx.GLTF.animations.Count; ++i) + { + var clip = new AnimationClip(); + clip.ClearCurves(); + clip.legacy = true; + clip.name = ctx.GLTF.animations[i].name; + if (string.IsNullOrEmpty(clip.name)) + { + clip.name = "legacy_" + i; + } + clip.wrapMode = WrapMode.Loop; + + var animation = ctx.GLTF.animations[i]; + if (string.IsNullOrEmpty(animation.name)) + { + animation.name = string.Format("animation:{0}", i); + } + + foreach (var channel in animation.channels) + { + var targetTransform = ctx.Nodes[channel.target.node]; + var relativePath = targetTransform.RelativePathFrom(ctx.Root.transform); + switch (channel.target.path) + { + case glTFAnimationTarget.PATH_TRANSLATION: + { + var sampler = animation.samplers[channel.sampler]; + var input = ctx.GLTF.GetArrayFromAccessor(sampler.input); + var output = ctx.GLTF.GetArrayFromAccessorAsFloat(sampler.output); + + AnimationImporter.SetAnimationCurve( + clip, + relativePath, + new string[] { "localPosition.x", "localPosition.y", "localPosition.z" }, + input, + output, + sampler.interpolation, + typeof(Transform), + (values, last) => + { + Vector3 temp = new Vector3(values[0], values[1], values[2]); + return temp.ReverseZ().ToArray(); + } + ); + } + break; + + case glTFAnimationTarget.PATH_ROTATION: + { + var sampler = animation.samplers[channel.sampler]; + var input = ctx.GLTF.GetArrayFromAccessor(sampler.input); + var output = ctx.GLTF.GetArrayFromAccessorAsFloat(sampler.output); + + AnimationImporter.SetAnimationCurve( + clip, + relativePath, + new string[] { "localRotation.x", "localRotation.y", "localRotation.z", "localRotation.w" }, + input, + output, + sampler.interpolation, + typeof(Transform), + (values, last) => + { + Quaternion currentQuaternion = new Quaternion(values[0], values[1], values[2], values[3]); + Quaternion lastQuaternion = new Quaternion(last[0], last[1], last[2], last[3]); + return AnimationImporter.GetShortest(lastQuaternion, currentQuaternion.ReverseZ()).ToArray(); + } + ); + + clip.EnsureQuaternionContinuity(); + } + break; + + case glTFAnimationTarget.PATH_SCALE: + { + var sampler = animation.samplers[channel.sampler]; + var input = ctx.GLTF.GetArrayFromAccessor(sampler.input); + var output = ctx.GLTF.GetArrayFromAccessorAsFloat(sampler.output); + + AnimationImporter.SetAnimationCurve( + clip, + relativePath, + new string[] { "localScale.x", "localScale.y", "localScale.z" }, + input, + output, + sampler.interpolation, + typeof(Transform), + (values, last) => values); + } + break; + + case glTFAnimationTarget.PATH_WEIGHT: + { + var node = ctx.GLTF.nodes[channel.target.node]; + var mesh = ctx.GLTF.meshes[node.mesh]; + //var primitive = mesh.primitives.FirstOrDefault(); + //var targets = primitive.targets; + + List blendShapeNames = new List(); + var transform = ctx.Nodes[channel.target.node]; + var skinnedMeshRenderer = transform.GetComponent(); + if (skinnedMeshRenderer == null) + { + continue; + } + + for (int j = 0; j < skinnedMeshRenderer.sharedMesh.blendShapeCount; j++) + { + blendShapeNames.Add(skinnedMeshRenderer.sharedMesh.GetBlendShapeName(j)); + } + + var keyNames = blendShapeNames + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => "blendShape." + x) + .ToArray(); + + var sampler = animation.samplers[channel.sampler]; + var input = ctx.GLTF.GetArrayFromAccessor(sampler.input); + var output = ctx.GLTF.GetArrayFromAccessor(sampler.output); + AnimationImporter.SetAnimationCurve( + clip, + relativePath, + keyNames, + input, + output, + sampler.interpolation, + typeof(SkinnedMeshRenderer), + (values, last) => + { + for (int j = 0; j < values.Length; j++) + { + values[j] *= 100.0f; + } + return values; + }); + + } + break; + + default: + Debug.LogWarningFormat("unknown path: {0}", channel.target.path); + break; + } + } + animasionClips.Add(clip); + } + + return animasionClips; + } + + public static void ImportAnimation(ImporterContext ctx) + { + // animation + if (ctx.GLTF.animations != null && ctx.GLTF.animations.Any()) + { + var animation = ctx.Root.AddComponent(); + ctx.AnimationClips = ImportAnimationClip(ctx); + foreach (var clip in ctx.AnimationClips) + { + animation.AddClip(clip, clip.name); + } + if (ctx.AnimationClips.Count > 0) + { + animation.clip = ctx.AnimationClips.First(); + } + } + } + + } +} diff --git a/UniGLTF/Core/Scripts/IO/AnimationImporter.cs.meta b/UniGLTF/Core/Scripts/IO/AnimationImporter.cs.meta new file mode 100644 index 000000000..0b1497bed --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationImporter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d602384685dd4f179350052013659720 +timeCreated: 1537445972 \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/AnimationKeyframeData.cs b/UniGLTF/Core/Scripts/IO/AnimationKeyframeData.cs new file mode 100644 index 000000000..9e5997963 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationKeyframeData.cs @@ -0,0 +1,54 @@ +namespace UniGLTF +{ + class AnimationKeyframeData + { +#if UNITY_EDITOR + public float Time { get; set; } + public delegate float[] ConverterFunc(float[] values); + private ConverterFunc _converter; + private float[] _values; + public float[] Values + { + get { return _values; } + } + + private bool[] _enterValues; + public bool[] EnterValues + { + get { return _enterValues; } + } + + public AnimationKeyframeData(int elementCount, ConverterFunc converter) + { + _values = new float[elementCount]; + _enterValues = new bool[elementCount]; + for (int i = 0; i < _enterValues.Length; i++) + { + _enterValues[i] = false; + } + _converter = converter; + } + + public void SetValue(float src, int offset) + { + if (_values.Length > offset) + { + _values[offset] = src; + _enterValues[offset] = true; + } + } + + public virtual float[] GetRightHandCoordinate() + { + if (_converter != null) + { + return _converter(_values); + } + else + { + return _values; + } + } +#endif + } +} \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/AnimationKeyframeData.cs.meta b/UniGLTF/Core/Scripts/IO/AnimationKeyframeData.cs.meta new file mode 100644 index 000000000..21154c77b --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/AnimationKeyframeData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e27ef4fb768e49f591c2bb5eadd3b19b +timeCreated: 1537442737 \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/BytesReader.cs b/UniGLTF/Core/Scripts/IO/BytesReader.cs new file mode 100644 index 000000000..f0bf32cb6 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/BytesReader.cs @@ -0,0 +1,81 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + + +namespace UniGLTF +{ + public class BytesReader + { + Byte[] m_bytes; + int m_pos; + + public BytesReader(Byte[] bytes, int pos=0) + { + m_bytes = bytes; + m_pos = pos; + } + + public string ReadString(int count, Encoding encoding) + { + var s = encoding.GetString(m_bytes, m_pos, count); + m_pos += count; + return s; + } + + public float ReadSingle() + { + var n = BitConverter.ToSingle(m_bytes, m_pos); + m_pos += 4; + return n; + } + + public byte ReadUInt8() + { + return m_bytes[m_pos++]; + } + + public UInt16 ReadUInt16() + { + var n = BitConverter.ToUInt16(m_bytes, m_pos); + m_pos += 2; + return n; + } + + public sbyte ReadInt8() + { + return (sbyte)m_bytes[m_pos++]; + } + + public Int16 ReadInt16() + { + var n = BitConverter.ToInt16(m_bytes, m_pos); + m_pos += 2; + return n; + } + + public int ReadInt32() + { + var n = BitConverter.ToInt32(m_bytes, m_pos); + m_pos += 4; + return n; + } + + public void ReadToArray(T[] dst) where T : struct + { + var size = new ArraySegment(m_bytes, m_pos, m_bytes.Length - m_pos).MarshalCoyTo(dst); + m_pos += size; + } + + public T ReadStruct() where T : struct + { + var size = Marshal.SizeOf(typeof(T)); + using (var pin = Pin.Create(new ArraySegment(m_bytes, m_pos, m_bytes.Length - m_pos))) + { + var s = (T)Marshal.PtrToStructure(pin.Ptr, typeof(T)); + m_pos += size; + return s; + } + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/BytesReader.cs.meta b/UniGLTF/Core/Scripts/IO/BytesReader.cs.meta new file mode 100644 index 000000000..da1f45ddd --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/BytesReader.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: ecae7b7664269624ba8e03025a53819e +timeCreated: 1514252306 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/IStorage.cs b/UniGLTF/Core/Scripts/IO/IStorage.cs new file mode 100644 index 000000000..f61787eef --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/IStorage.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; + + +namespace UniGLTF +{ + public interface IStorage + { + ArraySegment Get(string url); + + /// + /// Get original filepath if exists + /// + /// + /// + string GetPath(string url); + } + + public class SimpleStorage : IStorage + { + ArraySegment m_bytes; + + public SimpleStorage():this(new ArraySegment()) + { + } + + public SimpleStorage(ArraySegment bytes) + { + m_bytes = bytes; + } + + public ArraySegment Get(string url) + { + return m_bytes; + } + + public string GetPath(string url) + { + return null; + } + } + + public class FileSystemStorage : IStorage + { + string m_root; + + public FileSystemStorage(string root) + { + m_root = Path.GetFullPath(root); + } + + public ArraySegment Get(string url) + { + var bytes = + (url.StartsWith("data:")) + ? UriByteBuffer.ReadEmbeded(url) + : File.ReadAllBytes(Path.Combine(m_root, url)) + ; + return new ArraySegment(bytes); + } + + public string GetPath(string url) + { + if (url.StartsWith("data:")) + { + return null; + } + else + { + return Path.Combine(m_root, url).Replace("\\", "/"); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/IStorage.cs.meta b/UniGLTF/Core/Scripts/IO/IStorage.cs.meta new file mode 100644 index 000000000..0eb53f3c4 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/IStorage.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9cb8b6f878e36a74f90d172daee60bed +timeCreated: 1529327531 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/ImporterContext.cs b/UniGLTF/Core/Scripts/IO/ImporterContext.cs new file mode 100644 index 000000000..24b4d41b8 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ImporterContext.cs @@ -0,0 +1,999 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using UnityEngine; +using System.IO; +using System.Text; +using System.Collections; +using DepthFirstScheduler; +#if UNITY_EDITOR +using UnityEditor; +#endif +#if ((NET_4_6 || NET_STANDARD_2_0) && UNITY_2017_1_OR_NEWER) +using System.Threading.Tasks; +#endif + + +namespace UniGLTF +{ + /// + /// GLTF importer + /// + public class ImporterContext: IDisposable + { + #region MeasureTime + bool m_showSpeedLog +#if UNIGLTF_DEVELOP + = true +#endif + ; + public bool ShowSpeedLog + { + set { m_showSpeedLog = value; } + } + + public struct KeyElapsed + { + public string Key; + public TimeSpan Elapsed; + public KeyElapsed(string key, TimeSpan elapsed) + { + Key = key; + Elapsed = elapsed; + } + } + + public struct MeasureScope : IDisposable + { + Action m_onDispose; + public MeasureScope(Action onDispose) + { + m_onDispose = onDispose; + } + public void Dispose() + { + m_onDispose(); + } + } + + public List m_speedReports = new List(); + + public IDisposable MeasureTime(string key) + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + return new MeasureScope(() => + { + m_speedReports.Add(new KeyElapsed(key, sw.Elapsed)); + }); + } + + public string GetSpeedLog() + { + var total = TimeSpan.Zero; + + var sb = new StringBuilder(); + sb.AppendLine("【SpeedLog】"); + foreach (var kv in m_speedReports) + { + sb.AppendLine(string.Format("{0}: {1}ms", kv.Key, (int)kv.Elapsed.TotalMilliseconds)); + total += kv.Elapsed; + } + sb.AppendLine(string.Format("total: {0}ms", (int)total.TotalMilliseconds)); + + return sb.ToString(); + } + #endregion + + IShaderStore m_shaderStore; + public IShaderStore ShaderStore + { + get + { + if (m_shaderStore == null) + { + m_shaderStore = new ShaderStore(this); + } + return m_shaderStore; + } + } + + IMaterialImporter m_materialImporter; + protected void SetMaterialImporter(IMaterialImporter importer) + { + m_materialImporter = importer; + } + public IMaterialImporter MaterialImporter + { + get + { + if (m_materialImporter == null) + { + m_materialImporter = new MaterialImporter(ShaderStore, this); + } + return m_materialImporter; + } + } + + public ImporterContext(IShaderStore shaderStore) + { + m_shaderStore = shaderStore; + } + + public ImporterContext(IMaterialImporter materialImporter) + { + m_materialImporter = materialImporter; + } + + public ImporterContext() + { + } + + #region Source + + /// + /// JSON source + /// + public String Json; + + /// + /// GLTF parsed from JSON + /// + public glTF GLTF; // parsed + + public static bool IsGeneratedUniGLTFAndOlderThan(string generatorVersion, int major, int minor) + { + if (string.IsNullOrEmpty(generatorVersion)) return false; + if (generatorVersion == "UniGLTF") return true; + if (!generatorVersion.StartsWith("UniGLTF-")) return false; + + try + { + var index = generatorVersion.IndexOf('.'); + var generatorMajor = int.Parse(generatorVersion.Substring(8, index - 8)); + var generatorMinor = int.Parse(generatorVersion.Substring(index + 1)); + + if (generatorMajor < major) + { + return true; + } + else + { + if (generatorMinor >= minor) + { + return false; + } + else + { + return true; + } + } + } + catch (Exception ex) + { + Debug.LogWarningFormat("{0}: {1}", generatorVersion, ex); + return false; + } + } + + public bool IsGeneratedUniGLTFAndOlder(int major, int minor) + { + if (GLTF == null) return false; + if (GLTF.asset == null) return false; + return IsGeneratedUniGLTFAndOlderThan(GLTF.asset.generator, major, minor); + } + + /// + /// URI access + /// + public IStorage Storage; + #endregion + + #region Parse + public void Parse(string path) + { + Parse(path, File.ReadAllBytes(path)); + } + + /// + /// Parse gltf json or Parse json chunk of glb + /// + /// + /// + public virtual void Parse(string path, Byte[] bytes) + { + var ext = Path.GetExtension(path).ToLower(); + switch (ext) + { + case ".gltf": + ParseJson(Encoding.UTF8.GetString(bytes), new FileSystemStorage(Path.GetDirectoryName(path))); + break; + + case ".zip": + { + var zipArchive = Zip.ZipArchiveStorage.Parse(bytes); + var gltf = zipArchive.Entries.FirstOrDefault(x => x.FileName.ToLower().EndsWith(".gltf")); + if (gltf == null) + { + throw new Exception("no gltf in archive"); + } + var jsonBytes = zipArchive.Extract(gltf); + var json = Encoding.UTF8.GetString(jsonBytes); + ParseJson(json, zipArchive); + } + break; + + case ".glb": + ParseGlb(bytes); + break; + + default: + throw new NotImplementedException(); + } + } + + /// + /// + /// + /// + public void ParseGlb(Byte[] bytes) + { + var chunks = glbImporter.ParseGlbChanks(bytes); + + if (chunks.Count != 2) + { + throw new Exception("unknown chunk count: " + chunks.Count); + } + + if (chunks[0].ChunkType != GlbChunkType.JSON) + { + throw new Exception("chunk 0 is not JSON"); + } + + if (chunks[1].ChunkType != GlbChunkType.BIN) + { + throw new Exception("chunk 1 is not BIN"); + } + + var jsonBytes = chunks[0].Bytes; + ParseJson(Encoding.UTF8.GetString(jsonBytes.Array, jsonBytes.Offset, jsonBytes.Count), + new SimpleStorage(chunks[1].Bytes)); + } + + public virtual void ParseJson(string json, IStorage storage) + { + Json = json; + Storage = storage; + + GLTF = JsonUtility.FromJson(Json); + if (GLTF.asset.version != "2.0") + { + throw new UniGLTFException("unknown gltf version {0}", GLTF.asset.version); + } + + // Version Compatibility + RestoreOlderVersionValues(); + + // parepare byte buffer + //GLTF.baseDir = System.IO.Path.GetDirectoryName(Path); + foreach (var buffer in GLTF.buffers) + { + buffer.OpenStorage(storage); + } + } + + void RestoreOlderVersionValues() + { + var parsed = UniJSON.JsonParser.Parse(Json); + for (int i = 0; i < GLTF.images.Count; ++i) + { + if (string.IsNullOrEmpty(GLTF.images[i].name)) + { + try + { + var extraName = parsed["images"][i]["extra"]["name"].Value.GetString(); + if (!string.IsNullOrEmpty(extraName)) + { + //Debug.LogFormat("restore texturename: {0}", extraName); + GLTF.images[i].name = extraName; + } + } + catch (Exception) + { + // do nothing + } + } + } + for (int i = 0; i < GLTF.meshes.Count; ++i) + { + var mesh = GLTF.meshes[i]; + try + { + for (int j = 0; j < mesh.primitives.Count; ++j) + { + var primitive = mesh.primitives[j]; + for (int k = 0; k < primitive.targets.Count; ++k) + { + var extraName = parsed["meshes"][i]["primitives"][j]["targets"][k]["extra"]["name"].Value.GetString(); + //Debug.LogFormat("restore morphName: {0}", extraName); + primitive.extras.targetNames.Add(extraName); + } + } + } + catch (Exception) + { + // do nothing + } + } +#if false + for (int i = 0; i < GLTF.nodes.Count; ++i) + { + var node = GLTF.nodes[i]; + try + { + var extra = parsed["nodes"][i]["extra"]["skinRootBone"].AsInt; + //Debug.LogFormat("restore extra: {0}", extra); + //node.extras.skinRootBone = extra; + } + catch (Exception) + { + // do nothing + } + } +#endif + } + #endregion + + #region Load. Build unity objects + /// + /// ReadAllBytes, Parse, Create GameObject + /// + /// allbytes + public void Load(string path) + { + var bytes = File.ReadAllBytes(path); + Load(path, bytes); + } + + /// + /// Parse, Create GameObject + /// + /// gltf or glb path + /// allbytes + public void Load(string path, byte[] bytes) + { + Parse(path, bytes); + Load(); + Root.name = Path.GetFileNameWithoutExtension(path); + } + + public void CreateTextureItems(UnityPath imageBaseDir = default(UnityPath)) + { + if (m_textures.Any()) + { + return; + } + + for (int i = 0; i < GLTF.textures.Count; ++i) + { + var image = GLTF.GetImageFromTextureIndex(i); + + TextureItem item = null; +#if UNITY_EDITOR + if (imageBaseDir.IsUnderAssetsFolder + && !string.IsNullOrEmpty(image.uri) + && !image.uri.StartsWith("data:") + ) + { + /// + /// required SaveTexturesAsPng or SetTextureBaseDir + /// + var assetPath = imageBaseDir.Child(image.uri); + var textureName = !string.IsNullOrEmpty(image.name) ? image.name : Path.GetFileNameWithoutExtension(image.uri); + item = new TextureItem(i, assetPath, textureName); + } + else +#endif + { + item = new TextureItem(i); + } + + AddTexture(item); + } + } + + /// + /// Build unity objects from parsed gltf + /// + public void Load() + { + var schedulable = LoadAsync(); + schedulable.ExecuteAll(); + } + + [Obsolete("Action to Action")] + public IEnumerator LoadCoroutine(Action onLoaded, Action onError = null) + { + return LoadCoroutine(() => onLoaded(Unit.Default), onError); + } + + public IEnumerator LoadCoroutine(Action onError = null) + { + return LoadCoroutine(() => { }, onError); + } + + public IEnumerator LoadCoroutine(Action onLoaded, Action onError = null) + { + if (onLoaded == null) + { + onLoaded = () => { }; + } + + if (onError == null) + { + onError = Debug.LogError; + } + + var schedulable = LoadAsync(); + foreach (var x in schedulable.GetRoot().Traverse()) + { + while (true) + { + var status = x.Execute(); + if (status != ExecutionStatus.Continue) + { + break; + } + yield return null; + } + } + + onLoaded(); + } + + [Obsolete("Action to Action")] + public void LoadAsync(Action onLoaded, Action onError = null) + { + LoadAsync(() => onLoaded(Unit.Default), onError); + } + + public void LoadAsync(Action onLoaded, Action onError = null) + { + if (onError == null) + { + onError = Debug.LogError; + } + + LoadAsync() + .Subscribe(Scheduler.MainThread, + _ => onLoaded(), + onError + ); + } + +#if ((NET_4_6 || NET_STANDARD_2_0) && UNITY_2017_1_OR_NEWER) + public async Task LoadAsyncTask() + { + await LoadAsync().ToTask(); + return Root; + } +#endif + + protected virtual Schedulable LoadAsync() + { + return + Schedulable.Create() + .AddTask(Scheduler.ThreadPool, () => + { + if (m_textures.Count == 0) + { + // + // runtime + // + CreateTextureItems(); + } + else + { + // + // already CreateTextures(by assetPostProcessor or editor menu) + // + } + }) + .ContinueWithCoroutine(Scheduler.ThreadPool, TexturesProcessOnAnyThread) + .ContinueWithCoroutine(Scheduler.MainThread, TexturesProcessOnMainThread) + .ContinueWithCoroutine(Scheduler.MainThread, LoadMaterials) + .OnExecute(Scheduler.ThreadPool, parent => + { + if (GLTF.meshes + .SelectMany(x => x.primitives) + .Any(x => x.extensions.KHR_draco_mesh_compression != null)) + { + throw new UniGLTFNotSupportedException("draco is not supported"); + } + + // meshes + var meshImporter = new MeshImporter(); + for (int i = 0; i < GLTF.meshes.Count; ++i) + { + var index = i; + parent.AddTask(Scheduler.ThreadPool, + () => + { + using (MeasureTime("ReadMesh")) + { + return meshImporter.ReadMesh(this, index); + } + }) + .ContinueWith(Scheduler.MainThread, x => + { + using (MeasureTime("BuildMesh")) + { + var meshWithMaterials = MeshImporter.BuildMesh(this, x); + + var mesh = meshWithMaterials.Mesh; + + // mesh name + if (string.IsNullOrEmpty(mesh.name)) + { + mesh.name = string.Format("UniGLTF import#{0}", i); + } + var originalName = mesh.name; + for (int j = 1; Meshes.Any(y => y.Mesh.name == mesh.name); ++j) + { + mesh.name = string.Format("{0}({1})", originalName, j); + } + + return meshWithMaterials; + } + }) + .ContinueWith(Scheduler.ThreadPool, x => Meshes.Add(x)) + ; + } + }) + .ContinueWithCoroutine(Scheduler.MainThread, LoadNodes) + .ContinueWithCoroutine(Scheduler.MainThread, BuildHierarchy) + .ContinueWith(Scheduler.MainThread, _ => + { + using (MeasureTime("AnimationImporter")) + { + AnimationImporter.ImportAnimation(this); + } + }) + .ContinueWith(Scheduler.CurrentThread, + _ => + { + OnLoadModel(); + if (m_showSpeedLog) + { + Debug.Log(GetSpeedLog()); + } + return Unit.Default; + }); + } + + protected virtual void OnLoadModel() + { + Root.name = "GLTF"; + } + + IEnumerator TexturesProcessOnAnyThread() + { + using (MeasureTime("TexturesProcessOnAnyThread")) + { + foreach (var x in GetTextures()) + { + x.ProcessOnAnyThread(GLTF, Storage); + yield return null; + } + } + } + + IEnumerator TexturesProcessOnMainThread() + { + using (MeasureTime("TexturesProcessOnMainThread")) + { + foreach (var x in GetTextures()) + { + yield return x.ProcessOnMainThreadCoroutine(GLTF); + } + } + } + + IEnumerator LoadMaterials() + { + using (MeasureTime("LoadMaterials")) + { + if (GLTF.materials == null || !GLTF.materials.Any()) + { + AddMaterial(MaterialImporter.CreateMaterial(0, null)); + } + else + { + for (int i = 0; i < GLTF.materials.Count; ++i) + { + AddMaterial(MaterialImporter.CreateMaterial(i, GLTF.materials[i])); + } + } + } + yield return null; + } + + IEnumerator LoadMeshes() + { + var meshImporter = new MeshImporter(); + for (int i = 0; i < GLTF.meshes.Count; ++i) + { + var meshContext = meshImporter.ReadMesh(this, i); + var meshWithMaterials = MeshImporter.BuildMesh(this, meshContext); + var mesh = meshWithMaterials.Mesh; + if (string.IsNullOrEmpty(mesh.name)) + { + mesh.name = string.Format("UniGLTF import#{0}", i); + } + Meshes.Add(meshWithMaterials); + + yield return null; + } + } + + IEnumerator LoadNodes() + { + using (MeasureTime("LoadNodes")) + { + foreach (var x in GLTF.nodes) + { + Nodes.Add(NodeImporter.ImportNode(x).transform); + } + } + + yield return null; + } + + IEnumerator BuildHierarchy() + { + using (MeasureTime("BuildHierarchy")) + { + var nodes = new List(); + for (int i = 0; i < Nodes.Count; ++i) + { + nodes.Add(NodeImporter.BuildHierarchy(this, i)); + } + + NodeImporter.FixCoordinate(this, nodes); + + // skinning + for (int i = 0; i < nodes.Count; ++i) + { + NodeImporter.SetupSkinning(this, nodes, i); + } + + // connect root + Root = new GameObject("_root_"); + foreach (var x in GLTF.rootnodes) + { + var t = nodes[x].Transform; + t.SetParent(Root.transform, false); + } + } + + yield return null; + } +#endregion + +#region Imported + public GameObject Root; + public List Nodes = new List(); + + List m_textures = new List(); + public IList GetTextures() + { + return m_textures; + } + public TextureItem GetTexture(int i) + { + if (i < 0 || i >= m_textures.Count) + { + return null; + } + return m_textures[i]; + } + public void AddTexture(TextureItem item) + { + m_textures.Add(item); + } + + List m_materials = new List(); + public void AddMaterial(Material material) + { + var originalName = material.name; + int j = 2; + while (m_materials.Any(x => x.name == material.name)) + { + material.name = string.Format("{0}({1})", originalName, j++); + } + m_materials.Add(material); + } + public IList GetMaterials() + { + return m_materials; + } + public Material GetMaterial(int index) + { + if (index < 0) return null; + if (index >= m_materials.Count) return null; + return m_materials[index]; + } + + public List Meshes = new List(); + public void ShowMeshes() + { + foreach (var x in Meshes) + { + foreach(var y in x.Renderers) + { + y.enabled = true; + } + } + } + + public void EnableUpdateWhenOffscreen() + { + foreach (var x in Meshes) + { + foreach (var r in x.Renderers) + { + var skinnedMeshRenderer = r as SkinnedMeshRenderer; + if (skinnedMeshRenderer != null) + { + skinnedMeshRenderer.updateWhenOffscreen = true; + } + } + } + } + + public List AnimationClips = new List(); + #endregion + + protected virtual IEnumerable ObjectsForSubAsset() + { + HashSet textures = new HashSet(); + foreach (var x in m_textures.SelectMany(y => y.GetTexturesForSaveAssets())) + { + if (!textures.Contains(x)) + { + textures.Add(x); + } + } + foreach (var x in textures) { yield return x; } + foreach (var x in m_materials) { yield return x; } + foreach (var x in Meshes) { yield return x.Mesh; } + foreach (var x in AnimationClips) { yield return x; } + } + +#if UNITY_EDITOR + #region Assets + public bool MeshAsSubAsset = false; + + protected virtual UnityPath GetAssetPath(UnityPath prefabPath, UnityEngine.Object o) + { + if (o is Material) + { + var materialDir = prefabPath.GetAssetFolder(".Materials"); + var materialPath = materialDir.Child(o.name.EscapeFilePath() + ".asset"); + return materialPath; + } + else if (o is Texture2D) + { + var textureDir = prefabPath.GetAssetFolder(".Textures"); + var texturePath = textureDir.Child(o.name.EscapeFilePath() + ".asset"); + return texturePath; + } + else if (o is Mesh && !MeshAsSubAsset) + { + var meshDir = prefabPath.GetAssetFolder(".Meshes"); + var meshPath = meshDir.Child(o.name.EscapeFilePath() + ".asset"); + return meshPath; + } + else + { + return default(UnityPath); + } + } + + public virtual bool IsOverwrite(UnityEngine.Object o) + { + if(o is Material) + { + return false; + } + + return true; + } + + public void SaveAsAsset(UnityPath prefabPath) + { + ShowMeshes(); + + //var prefabPath = PrefabPath; + if (prefabPath.IsFileExists) + { + // clear SubAssets + foreach (var x in prefabPath.GetSubAssets().Where(x => !(x is GameObject) && !(x is Component))) + { + GameObject.DestroyImmediate(x, true); + } + } + + // + // save sub assets + // + var paths = new List(){ + prefabPath + }; + foreach (var o in ObjectsForSubAsset()) + { + if (o == null) continue; + + var assetPath = GetAssetPath(prefabPath, o); + if (!assetPath.IsNull) + { + if (assetPath.IsFileExists) + { + if (!IsOverwrite(o)) + { + // 上書きしない + Debug.LogWarningFormat("already exists. skip {0}", assetPath); + continue; + } + } + assetPath.Parent.EnsureFolder(); + assetPath.CreateAsset(o); + paths.Add(assetPath); + } + else + { + // save as subasset + prefabPath.AddObjectToAsset(o); + } + } + + // Create or upate Main Asset + if (prefabPath.IsFileExists) + { + Debug.LogFormat("replace prefab: {0}", prefabPath); + var prefab = prefabPath.LoadAsset(); + PrefabUtility.ReplacePrefab(Root, prefab, ReplacePrefabOptions.ReplaceNameBased); + } + else + { + Debug.LogFormat("create prefab: {0}", prefabPath); + PrefabUtility.CreatePrefab(prefabPath.Value, Root); + } + foreach (var x in paths) + { + x.ImportAsset(); + } + } + + /// + /// Extract images from glb or gltf out of Assets folder. + /// + /// + public void ExtranctImages(UnityPath prefabPath) + { + var prefabParentDir = prefabPath.Parent; + + // glb buffer + var folder = prefabPath.GetAssetFolder(".Textures"); + + // + // https://answers.unity.com/questions/647615/how-to-update-import-settings-for-newly-created-as.html + // + int created = 0; + //for (int i = 0; i < GLTF.textures.Count; ++i) + for (int i = 0; i < GLTF.images.Count; ++i) + { + folder.EnsureFolder(); + + //var x = GLTF.textures[i]; + var image = GLTF.images[i]; + var src = Storage.GetPath(image.uri); + if (UnityPath.FromFullpath(src).IsUnderAssetsFolder) + { + // asset is exists. + } + else + { + string textureName; + var byteSegment = GLTF.GetImageBytes(Storage, i, out textureName); + + // path + var dst = folder.Child(textureName + image.GetExt()); + File.WriteAllBytes(dst.FullPath, byteSegment.ToArray()); + dst.ImportAsset(); + + // make relative path from PrefabParentDir + image.uri = dst.Value.Substring(prefabParentDir.Value.Length + 1); + ++created; + } + } + + if (created > 0) + { + AssetDatabase.Refresh(); + } + + CreateTextureItems(prefabParentDir); + } + #endregion +#endif + + /// + /// This function is used for clean up after create assets. + /// + /// Ambiguous arguments + [Obsolete("Use Dispose for runtime loader resource management")] + public void Destroy(bool destroySubAssets) + { + if (Root != null) GameObject.DestroyImmediate(Root); + if (destroySubAssets) + { +#if UNITY_EDITOR + foreach (var o in ObjectsForSubAsset()) + { + UnityEngine.Object.DestroyImmediate(o, true); + } +#endif + } + } + + public void Dispose() + { + DestroyRootAndResources(); + } + + /// + /// Destroy resources that created ImporterContext for runtime load. + /// + public void DestroyRootAndResources() + { + if (!Application.isPlaying) + { + Debug.LogWarningFormat("Dispose called in editor mode. This function is for runtime"); + } + + // Remove hierarchy + if (Root != null) GameObject.Destroy(Root); + + // Remove resources. materials, textures meshes etc... + foreach (var o in ObjectsForSubAsset()) + { + UnityEngine.Object.DestroyImmediate(o, true); + } + } + +#if UNITY_EDITOR + /// + /// Destroy the GameObject that became the basis of Prefab + /// + public void EditorDestroyRoot() + { + if (Root != null) GameObject.DestroyImmediate(Root); + } + + /// + /// Destroy assets that created ImporterContext. This function is clean up for imoprter error. + /// + public void EditorDestroyRootAndAssets() + { + // Remove hierarchy + if (Root != null) GameObject.DestroyImmediate(Root); + + // Remove resources. materials, textures meshes etc... + foreach (var o in ObjectsForSubAsset()) + { + UnityEngine.Object.DestroyImmediate(o, true); + } + } +#endif + } +} diff --git a/UniGLTF/Core/Scripts/IO/ImporterContext.cs.meta b/UniGLTF/Core/Scripts/IO/ImporterContext.cs.meta new file mode 100644 index 000000000..00359b62a --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ImporterContext.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 8d2d07b67753fb0489decee9926ab2da +timeCreated: 1517123236 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/MaterialExporter.cs b/UniGLTF/Core/Scripts/IO/MaterialExporter.cs new file mode 100644 index 000000000..142406809 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MaterialExporter.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using UniGLTF.UniUnlit; +using UnityEngine; +using UnityEngine.Rendering; + + +namespace UniGLTF +{ + public enum glTFBlendMode + { + OPAQUE, + MASK, + BLEND + } + + public interface IMaterialExporter + { + glTFMaterial ExportMaterial(Material m, TextureExportManager textureManager); + } + + public class MaterialExporter : IMaterialExporter + { + public virtual glTFMaterial ExportMaterial(Material m, TextureExportManager textureManager) + { + var material = CreateMaterial(m); + + // common params + material.name = m.name; + Export_Color(m, textureManager, material); + Export_Metallic(m, textureManager, material); + Export_Normal(m, textureManager, material); + Export_Occlusion(m, textureManager, material); + Export_Emission(m, textureManager, material); + + return material; + } + + static void Export_Color(Material m, TextureExportManager textureManager, glTFMaterial material) + { + if (m.HasProperty("_Color")) + { + material.pbrMetallicRoughness.baseColorFactor = m.color.ToArray(); + } + + if (m.HasProperty("_MainTex")) + { + var index = textureManager.CopyAndGetIndex(m.GetTexture("_MainTex"), RenderTextureReadWrite.sRGB); + if (index != -1) + { + material.pbrMetallicRoughness.baseColorTexture = new glTFMaterialBaseColorTextureInfo() + { + index = index, + }; + } + } + } + + static void Export_Metallic(Material m, TextureExportManager textureManager, glTFMaterial material) + { + int index = -1; + if (m.HasProperty("_MetallicGlossMap")) + { + index = textureManager.ConvertAndGetIndex(m.GetTexture("_MetallicGlossMap"), new MetallicRoughnessConverter()); + if (index != -1) + { + material.pbrMetallicRoughness.metallicRoughnessTexture = new glTFMaterialMetallicRoughnessTextureInfo() + { + index = index, + }; + } + } + + if (index != -1 && m.HasProperty("_GlossMapScale")) + { + material.pbrMetallicRoughness.metallicFactor = 1.0f; + material.pbrMetallicRoughness.roughnessFactor = 1.0f - m.GetFloat("_GlossMapScale"); + } + else + { + if (m.HasProperty("_Metallic")) + { + material.pbrMetallicRoughness.metallicFactor = m.GetFloat("_Metallic"); + } + if (m.HasProperty("_Glossiness")) + { + material.pbrMetallicRoughness.roughnessFactor = 1.0f - m.GetFloat("_Glossiness"); + } + + } + } + + static void Export_Normal(Material m, TextureExportManager textureManager, glTFMaterial material) + { + if (m.HasProperty("_BumpMap")) + { + var index = textureManager.ConvertAndGetIndex(m.GetTexture("_BumpMap"), new NormalConverter()); + if (index != -1) + { + material.normalTexture = new glTFMaterialNormalTextureInfo() + { + index = index, + }; + } + + if (index != -1 && m.HasProperty("_BumpScale")) + { + material.normalTexture.scale = m.GetFloat("_BumpScale"); + } + } + } + + static void Export_Occlusion(Material m, TextureExportManager textureManager, glTFMaterial material) + { + if (m.HasProperty("_OcclusionMap")) + { + var index = textureManager.ConvertAndGetIndex(m.GetTexture("_OcclusionMap"), new OcclusionConverter()); + if (index != -1) + { + material.occlusionTexture = new glTFMaterialOcclusionTextureInfo() + { + index = index, + }; + } + + if (index != -1 && m.HasProperty("_OcclusionStrength")) + { + material.occlusionTexture.strength = m.GetFloat("_OcclusionStrength"); + } + } + } + + static void Export_Emission(Material m, TextureExportManager textureManager, glTFMaterial material) + { + if (m.HasProperty("_EmissionColor")) + { + var color = m.GetColor("_EmissionColor"); + material.emissiveFactor = new float[] { color.r, color.g, color.b }; + } + + if (m.HasProperty("_EmissionMap")) + { + var index = textureManager.CopyAndGetIndex(m.GetTexture("_EmissionMap"), RenderTextureReadWrite.sRGB); + if (index != -1) + { + material.emissiveTexture = new glTFMaterialEmissiveTextureInfo() + { + index = index, + }; + } + } + } + + protected virtual glTFMaterial CreateMaterial(Material m) + { + switch (m.shader.name) + { + case "Unlit/Color": + return Export_UnlitColor(m); + + case "Unlit/Texture": + return Export_UnlitTexture(m); + + case "Unlit/Transparent": + return Export_UnlitTransparent(m); + + case "Unlit/Transparent Cutout": + return Export_UnlitCutout(m); + + case "UniGLTF/UniUnlit": + return Export_UniUnlit(m); + + default: + return Export_Standard(m); + } + } + + static glTFMaterial Export_UnlitColor(Material m) + { + var material = glTF_KHR_materials_unlit.CreateDefault(); + material.alphaMode = glTFBlendMode.OPAQUE.ToString(); + return material; + } + + static glTFMaterial Export_UnlitTexture(Material m) + { + var material = glTF_KHR_materials_unlit.CreateDefault(); + material.alphaMode = glTFBlendMode.OPAQUE.ToString(); + return material; + } + + static glTFMaterial Export_UnlitTransparent(Material m) + { + var material = glTF_KHR_materials_unlit.CreateDefault(); + material.alphaMode = glTFBlendMode.BLEND.ToString(); + return material; + } + + static glTFMaterial Export_UnlitCutout(Material m) + { + var material = glTF_KHR_materials_unlit.CreateDefault(); + material.alphaMode = glTFBlendMode.MASK.ToString(); + material.alphaCutoff = m.GetFloat("_Cutoff"); + return material; + } + + private glTFMaterial Export_UniUnlit(Material m) + { + var material = glTF_KHR_materials_unlit.CreateDefault(); + + var renderMode = UniUnlit.Utils.GetRenderMode(m); + if (renderMode == UniUnlitRenderMode.Opaque) + { + material.alphaMode = glTFBlendMode.OPAQUE.ToString(); + } + else if (renderMode == UniUnlitRenderMode.Transparent) + { + material.alphaMode = glTFBlendMode.BLEND.ToString(); + } + else if (renderMode == UniUnlitRenderMode.Cutout) + { + material.alphaMode = glTFBlendMode.MASK.ToString(); + } + else + { + material.alphaMode = glTFBlendMode.OPAQUE.ToString(); + } + + var cullMode = UniUnlit.Utils.GetCullMode(m); + if (cullMode == UniUnlitCullMode.Off) + { + material.doubleSided = true; + } + else + { + material.doubleSided = false; + } + + return material; + } + + static glTFMaterial Export_Standard(Material m) + { + var material = new glTFMaterial + { + pbrMetallicRoughness = new glTFPbrMetallicRoughness(), + }; + + switch (m.GetTag("RenderType", true)) + { + case "Transparent": + material.alphaMode = glTFBlendMode.BLEND.ToString(); + break; + + case "TransparentCutout": + material.alphaMode = glTFBlendMode.MASK.ToString(); + material.alphaCutoff = m.GetFloat("_Cutoff"); + break; + + default: + material.alphaMode = glTFBlendMode.OPAQUE.ToString(); + break; + } + + return material; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/MaterialExporter.cs.meta b/UniGLTF/Core/Scripts/IO/MaterialExporter.cs.meta new file mode 100644 index 000000000..e441123c7 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MaterialExporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 99662edfbf59e8f458bcba3b62b13050 +timeCreated: 1533622882 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/MaterialImporter.cs b/UniGLTF/Core/Scripts/IO/MaterialImporter.cs new file mode 100644 index 000000000..96f061f4b --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MaterialImporter.cs @@ -0,0 +1,262 @@ +using UnityEngine; +using System; +using UnityEngine.Rendering; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public interface IMaterialImporter + { + Material CreateMaterial(int i, glTFMaterial src); + } + + public class MaterialImporter : IMaterialImporter + { + IShaderStore m_shaderStore; + + ImporterContext m_context; + protected ImporterContext Context + { + get { return m_context; } + } + + public MaterialImporter(IShaderStore shaderStore, ImporterContext context) + { + m_shaderStore = shaderStore; + m_context = context; + } + + private enum BlendMode + { + Opaque, + Cutout, + Fade, + Transparent + } + + /// StandardShader vaiables + /// + /// _Color + /// _MainTex + /// _Cutoff + /// _Glossiness + /// _Metallic + /// _MetallicGlossMap + /// _BumpScale + /// _BumpMap + /// _Parallax + /// _ParallaxMap + /// _OcclusionStrength + /// _OcclusionMap + /// _EmissionColor + /// _EmissionMap + /// _DetailMask + /// _DetailAlbedoMap + /// _DetailNormalMapScale + /// _DetailNormalMap + /// _UVSec + /// _EmissionScaleUI + /// _EmissionColorUI + /// _Mode + /// _SrcBlend + /// _DstBlend + /// _ZWrite + public virtual Material CreateMaterial(int i, glTFMaterial x) + { + var shader = m_shaderStore.GetShader(x); + //Debug.LogFormat("[{0}]{1}", i, shader.name); + var material = new Material(shader); +#if UNITY_EDITOR + // textureImporter.SaveAndReimport(); may destory this material + material.hideFlags = HideFlags.DontUnloadUnusedAsset; +#endif + + material.name = (x == null || string.IsNullOrEmpty(x.name)) + ? string.Format("material_{0:00}", i) + : x.name + ; + + if (x == null) + { + Debug.LogWarning("glTFMaterial is empty"); + return material; + } + + // unlit material + if (x.extensions != null && x.extensions.KHR_materials_unlit != null) + { + // texture + var texture = m_context.GetTexture(x.pbrMetallicRoughness.baseColorTexture.index); + if (texture != null) + { + material.mainTexture = texture.Texture; + } + + // color + if (x.pbrMetallicRoughness.baseColorFactor != null && x.pbrMetallicRoughness.baseColorFactor.Length == 4) + { + var color = x.pbrMetallicRoughness.baseColorFactor; + material.color = new Color(color[0], color[1], color[2], color[3]); + } + + //renderMode + if (x.alphaMode == "OPAQUE") + { + UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Opaque); + } + else if (x.alphaMode == "BLEND") + { + UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Transparent); + } + else if(x.alphaMode == "MASK") + { + UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Cutout); + } + else + { + // default OPAQUE + UniUnlit.Utils.SetRenderMode(material, UniUnlit.UniUnlitRenderMode.Opaque); + } + + // culling + if (x.doubleSided) + { + UniUnlit.Utils.SetCullMode(material, UniUnlit.UniUnlitCullMode.Off); + } + else + { + UniUnlit.Utils.SetCullMode(material, UniUnlit.UniUnlitCullMode.Back); + } + + UniUnlit.Utils.ValidateProperties(material, true); + + return material; + } + + // PBR material + if (x.pbrMetallicRoughness != null) + { + if (x.pbrMetallicRoughness.baseColorFactor != null && x.pbrMetallicRoughness.baseColorFactor.Length == 4) + { + var color = x.pbrMetallicRoughness.baseColorFactor; + material.color = new Color(color[0], color[1], color[2], color[3]); + } + + if (x.pbrMetallicRoughness.baseColorTexture != null && x.pbrMetallicRoughness.baseColorTexture.index != -1) + { + var texture = m_context.GetTexture(x.pbrMetallicRoughness.baseColorTexture.index); + if (texture != null) + { + material.mainTexture = texture.Texture; + } + } + + if (x.pbrMetallicRoughness.metallicRoughnessTexture != null && x.pbrMetallicRoughness.metallicRoughnessTexture.index != -1) + { + material.EnableKeyword("_METALLICGLOSSMAP"); + var texture = Context.GetTexture(x.pbrMetallicRoughness.metallicRoughnessTexture.index); + if (texture != null) + { + var prop = "_MetallicGlossMap"; + material.SetTexture(prop, texture.ConvertTexture(prop)); + } + } + + material.SetFloat("_Metallic", x.pbrMetallicRoughness.metallicFactor); + material.SetFloat("_Glossiness", 1.0f - x.pbrMetallicRoughness.roughnessFactor); + } + + if (x.normalTexture != null && x.normalTexture.index != -1) + { + material.EnableKeyword("_NORMALMAP"); + var texture = Context.GetTexture(x.normalTexture.index); + if (texture != null) + { + var prop = "_BumpMap"; + material.SetTexture(prop, texture.ConvertTexture(prop)); + material.SetFloat("_BumpScale", x.normalTexture.scale); + } + } + + if (x.occlusionTexture != null && x.occlusionTexture.index != -1) + { + var texture = Context.GetTexture(x.occlusionTexture.index); + if (texture != null) + { + var prop = "_OcclusionMap"; + material.SetTexture(prop, texture.ConvertTexture(prop)); + material.SetFloat("_OcclusionStrength", x.occlusionTexture.strength); + } + } + + if (x.emissiveFactor != null + || (x.emissiveTexture != null && x.emissiveTexture.index != -1)) + { + material.EnableKeyword("_EMISSION"); + material.globalIlluminationFlags &= ~MaterialGlobalIlluminationFlags.EmissiveIsBlack; + + if (x.emissiveFactor != null && x.emissiveFactor.Length == 3) + { + material.SetColor("_EmissionColor", new Color(x.emissiveFactor[0], x.emissiveFactor[1], x.emissiveFactor[2])); + } + + if (x.emissiveTexture.index != -1) + { + var texture = Context.GetTexture(x.emissiveTexture.index); + if (texture != null) + { + material.SetTexture("_EmissionMap", texture.Texture); + } + } + } + + BlendMode blendMode = BlendMode.Opaque; + // https://forum.unity.com/threads/standard-material-shader-ignoring-setfloat-property-_mode.344557/#post-2229980 + switch (x.alphaMode) + { + case "BLEND": + blendMode = BlendMode.Fade; + material.SetOverrideTag("RenderType", "Transparent"); + material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + material.SetInt("_ZWrite", 0); + material.DisableKeyword("_ALPHATEST_ON"); + material.EnableKeyword("_ALPHABLEND_ON"); + material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + material.renderQueue = 3000; + break; + + case "MASK": + blendMode = BlendMode.Cutout; + material.SetOverrideTag("RenderType", "TransparentCutout"); + material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); + material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); + material.SetInt("_ZWrite", 1); + material.EnableKeyword("_ALPHATEST_ON"); + material.DisableKeyword("_ALPHABLEND_ON"); + material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + material.renderQueue = 2450; + + break; + + default: // OPAQUE + blendMode = BlendMode.Opaque; + material.SetOverrideTag("RenderType", ""); + material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); + material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); + material.SetInt("_ZWrite", 1); + material.DisableKeyword("_ALPHATEST_ON"); + material.DisableKeyword("_ALPHABLEND_ON"); + material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + material.renderQueue = -1; + break; + } + + material.SetFloat("_Mode", (float)blendMode); + return material; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/MaterialImporter.cs.meta b/UniGLTF/Core/Scripts/IO/MaterialImporter.cs.meta new file mode 100644 index 000000000..20f93ed00 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MaterialImporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7ce22d10dc7cdc647b05c0449173ce71 +timeCreated: 1533624185 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/MeshExporter.cs b/UniGLTF/Core/Scripts/IO/MeshExporter.cs new file mode 100644 index 000000000..a7e01769d --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MeshExporter.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UniGLTF +{ + public struct MeshWithRenderer + { + public Mesh Mesh; + public Renderer Rendererer; + } + + public static class MeshExporter + { + static glTFMesh ExportPrimitives(glTF gltf, int bufferIndex, + string rendererName, + Mesh mesh, Material[] materials, + List unityMaterials) + { + var positions = mesh.vertices.Select(y => y.ReverseZ()).ToArray(); + var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, positions, glBufferTarget.ARRAY_BUFFER); + gltf.accessors[positionAccessorIndex].min = positions.Aggregate(positions[0], (a, b) => new Vector3(Mathf.Min(a.x, b.x), Math.Min(a.y, b.y), Mathf.Min(a.z, b.z))).ToArray(); + gltf.accessors[positionAccessorIndex].max = positions.Aggregate(positions[0], (a, b) => new Vector3(Mathf.Max(a.x, b.x), Math.Max(a.y, b.y), Mathf.Max(a.z, b.z))).ToArray(); + + var normalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.normals.Select(y => y.ReverseZ()).ToArray(), glBufferTarget.ARRAY_BUFFER); +#if GLTF_EXPORT_TANGENTS + var tangentAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.tangents.Select(y => y.ReverseZ()).ToArray(), glBufferTarget.ARRAY_BUFFER); +#endif + var uvAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.uv.Select(y => y.ReverseUV()).ToArray(), glBufferTarget.ARRAY_BUFFER); + var colorAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.colors, glBufferTarget.ARRAY_BUFFER); + + var boneweights = mesh.boneWeights; + var weightAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, boneweights.Select(y => new Vector4(y.weight0, y.weight1, y.weight2, y.weight3)).ToArray(), glBufferTarget.ARRAY_BUFFER); + var jointsAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, boneweights.Select(y => new UShort4((ushort)y.boneIndex0, (ushort)y.boneIndex1, (ushort)y.boneIndex2, (ushort)y.boneIndex3)).ToArray(), glBufferTarget.ARRAY_BUFFER); + + var attributes = new glTFAttributes + { + POSITION = positionAccessorIndex, + }; + if (normalAccessorIndex != -1) + { + attributes.NORMAL = normalAccessorIndex; + } +#if GLTF_EXPORT_TANGENTS + if (tangentAccessorIndex != -1) + { + attributes.TANGENT = tangentAccessorIndex; + } +#endif + if (uvAccessorIndex != -1) + { + attributes.TEXCOORD_0 = uvAccessorIndex; + } + if (colorAccessorIndex != -1) + { + attributes.COLOR_0 = colorAccessorIndex; + } + if (weightAccessorIndex != -1) + { + attributes.WEIGHTS_0 = weightAccessorIndex; + } + if (jointsAccessorIndex != -1) + { + attributes.JOINTS_0 = jointsAccessorIndex; + } + + var gltfMesh = new glTFMesh(mesh.name); + for (int j = 0; j < mesh.subMeshCount; ++j) + { + var indices = TriangleUtil.FlipTriangle(mesh.GetIndices(j)).Select(y => (uint)y).ToArray(); + var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices, glBufferTarget.ELEMENT_ARRAY_BUFFER); + + if (j >= materials.Length) + { + Debug.LogWarningFormat("{0}.materials is not enough", rendererName); + break; + } + + gltfMesh.primitives.Add(new glTFPrimitives + { + attributes = attributes, + indices = indicesAccessorIndex, + mode = 4, // triangels ? + material = unityMaterials.IndexOf(materials[j]) + }); + } + return gltfMesh; + } + + static bool UseSparse( + bool usePosition, Vector3 position, + bool useNormal, Vector3 normal, + bool useTangent, Vector3 tangent + ) + { + var useSparse = + (usePosition && position != Vector3.zero) + || (useNormal && normal != Vector3.zero) + || (useTangent && tangent != Vector3.zero) + ; + return useSparse; + } + + static gltfMorphTarget ExportMorphTarget(glTF gltf, int bufferIndex, + Mesh mesh, int j, + bool useSparseAccessorForMorphTarget) + { + var blendShapeVertices = mesh.vertices; + var usePosition = blendShapeVertices != null && blendShapeVertices.Length > 0; + + var blendShapeNormals = mesh.normals; + var useNormal = usePosition && blendShapeNormals != null && blendShapeNormals.Length == blendShapeVertices.Length; + + var blendShapeTangents = mesh.tangents.Select(y => (Vector3)y).ToArray(); + //var useTangent = usePosition && blendShapeTangents != null && blendShapeTangents.Length == blendShapeVertices.Length; + var useTangent = false; + + var frameCount = mesh.GetBlendShapeFrameCount(j); + mesh.GetBlendShapeFrameVertices(j, frameCount - 1, blendShapeVertices, blendShapeNormals, null); + + var blendShapePositionAccessorIndex = -1; + var blendShapeNormalAccessorIndex = -1; + var blendShapeTangentAccessorIndex = -1; + if (useSparseAccessorForMorphTarget) + { + var accessorCount = blendShapeVertices.Length; + var sparseIndices = Enumerable.Range(0, blendShapeVertices.Length) + .Where(x => UseSparse( + usePosition, blendShapeVertices[x], + useNormal, blendShapeNormals[x], + useTangent, blendShapeTangents[x])) + .ToArray() + ; + + if (sparseIndices.Length == 0) + { + usePosition = false; + useNormal = false; + useTangent = false; + } + else + { + Debug.LogFormat("Sparse {0}/{1}", sparseIndices.Length, mesh.vertexCount); + } + /* + var vertexSize = 12; + if (useNormal) vertexSize += 12; + if (useTangent) vertexSize += 24; + var sparseBytes = (4 + vertexSize) * sparseIndices.Length; + var fullBytes = (vertexSize) * blendShapeVertices.Length; + Debug.LogFormat("Export sparse: {0}/{1}bytes({2}%)", + sparseBytes, fullBytes, (int)((float)sparseBytes / fullBytes) + ); + */ + + var sparseIndicesViewIndex = -1; + if (usePosition) + { + sparseIndicesViewIndex = gltf.ExtendBufferAndGetViewIndex(bufferIndex, sparseIndices); + + blendShapeVertices = sparseIndices.Select(x => blendShapeVertices[x].ReverseZ()).ToArray(); + blendShapePositionAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(bufferIndex, accessorCount, + blendShapeVertices, + sparseIndices, sparseIndicesViewIndex, + glBufferTarget.ARRAY_BUFFER); + } + + if (useNormal) + { + blendShapeNormals = sparseIndices.Select(x => blendShapeNormals[x].ReverseZ()).ToArray(); + blendShapeNormalAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(bufferIndex, accessorCount, + blendShapeNormals, + sparseIndices, sparseIndicesViewIndex, + glBufferTarget.ARRAY_BUFFER); + } + + if (useTangent) + { + blendShapeTangents = sparseIndices.Select(x => blendShapeTangents[x].ReverseZ()).ToArray(); + blendShapeTangentAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(bufferIndex, accessorCount, + blendShapeTangents, sparseIndices, sparseIndicesViewIndex, + glBufferTarget.ARRAY_BUFFER); + } + } + else + { + for (int i = 0; i < blendShapeVertices.Length; ++i) blendShapeVertices[i] = blendShapeVertices[i].ReverseZ(); + if (usePosition) + { + blendShapePositionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, + blendShapeVertices, + glBufferTarget.ARRAY_BUFFER); + } + + if (useNormal) + { + for (int i = 0; i < blendShapeNormals.Length; ++i) blendShapeNormals[i] = blendShapeNormals[i].ReverseZ(); + blendShapeNormalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, + blendShapeNormals, + glBufferTarget.ARRAY_BUFFER); + } + + if (useTangent) + { + for (int i = 0; i < blendShapeTangents.Length; ++i) blendShapeTangents[i] = blendShapeTangents[i].ReverseZ(); + blendShapeTangentAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, + blendShapeTangents, + glBufferTarget.ARRAY_BUFFER); + } + } + + if (blendShapePositionAccessorIndex != -1) + { + gltf.accessors[blendShapePositionAccessorIndex].min = blendShapeVertices.Aggregate(blendShapeVertices[0], (a, b) => new Vector3(Mathf.Min(a.x, b.x), Math.Min(a.y, b.y), Mathf.Min(a.z, b.z))).ToArray(); + gltf.accessors[blendShapePositionAccessorIndex].max = blendShapeVertices.Aggregate(blendShapeVertices[0], (a, b) => new Vector3(Mathf.Max(a.x, b.x), Math.Max(a.y, b.y), Mathf.Max(a.z, b.z))).ToArray(); + } + + return new gltfMorphTarget + { + POSITION = blendShapePositionAccessorIndex, + NORMAL = blendShapeNormalAccessorIndex, + TANGENT = blendShapeTangentAccessorIndex, + }; + } + + public static void ExportMeshes(glTF gltf, int bufferIndex, + List unityMeshes, List unityMaterials, + bool useSparseAccessorForMorphTarget) + { + for (int i = 0; i < unityMeshes.Count; ++i) + { + var x = unityMeshes[i]; + var mesh = x.Mesh; + var materials = x.Rendererer.sharedMaterials; + + var gltfMesh = ExportPrimitives(gltf, bufferIndex, + x.Rendererer.name, + mesh, materials, unityMaterials); + + for (int j = 0; j < mesh.blendShapeCount; ++j) + { + var morphTarget = ExportMorphTarget(gltf, bufferIndex, + mesh, j, + useSparseAccessorForMorphTarget); + + // + // all primitive has same blendShape + // + for (int k = 0; k < gltfMesh.primitives.Count; ++k) + { + gltfMesh.primitives[k].targets.Add(morphTarget); + gltfMesh.primitives[k].extras.targetNames.Add(mesh.GetBlendShapeName(j)); + } + } + + gltf.meshes.Add(gltfMesh); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/MeshExporter.cs.meta b/UniGLTF/Core/Scripts/IO/MeshExporter.cs.meta new file mode 100644 index 000000000..7ca52c723 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MeshExporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9b5d31a97e5221e43b16cfb4213a4a79 +timeCreated: 1545139997 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/MeshImporter.cs b/UniGLTF/Core/Scripts/IO/MeshImporter.cs new file mode 100644 index 000000000..e9dbcf16c --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MeshImporter.cs @@ -0,0 +1,561 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using UnityEngine; + + +namespace UniGLTF +{ + public class MeshImporter + { + const float FRAME_WEIGHT = 100.0f; + + + // multiple submMesh is not sharing a VertexBuffer. + // each subMesh use a independent VertexBuffer. + private static MeshContext _ImportMeshIndependentVertexBuffer(ImporterContext ctx, glTFMesh gltfMesh) + { + //Debug.LogWarning("_ImportMeshIndependentVertexBuffer"); + + var targets = gltfMesh.primitives[0].targets; + for (int i = 1; i < gltfMesh.primitives.Count; ++i) + { + if (!gltfMesh.primitives[i].targets.SequenceEqual(targets)) + { + throw new NotImplementedException(string.Format("diffirent targets: {0} with {1}", + gltfMesh.primitives[i], + targets)); + } + } + + var positions = new List(); + var normals = new List(); + var tangents = new List(); + var uv = new List(); + var colors = new List(); + var meshContext = new MeshContext(); + foreach (var prim in gltfMesh.primitives) + { + var indexOffset = positions.Count; + var indexBuffer = prim.indices; + + var positionCount = positions.Count; + positions.AddRange(ctx.GLTF.GetArrayFromAccessor(prim.attributes.POSITION).Select(x => x.ReverseZ())); + positionCount = positions.Count - positionCount; + + // normal + if (prim.attributes.NORMAL != -1) + { + normals.AddRange(ctx.GLTF.GetArrayFromAccessor(prim.attributes.NORMAL).Select(x => x.ReverseZ())); + } + + if (prim.attributes.TANGENT != -1) + { + tangents.AddRange(ctx.GLTF.GetArrayFromAccessor(prim.attributes.TANGENT).Select(x => x.ReverseZ())); + } + + // uv + if (prim.attributes.TEXCOORD_0 != -1) + { + if (ctx.IsGeneratedUniGLTFAndOlder(1, 16)) + { +#pragma warning disable 0612 + // backward compatibility + uv.AddRange(ctx.GLTF.GetArrayFromAccessor(prim.attributes.TEXCOORD_0).Select(x => x.ReverseY())); +#pragma warning restore 0612 + } + else + { + uv.AddRange(ctx.GLTF.GetArrayFromAccessor(prim.attributes.TEXCOORD_0).Select(x => x.ReverseUV())); + } + } + else + { + // for inconsistent attributes in primitives + uv.AddRange(new Vector2[positionCount]); + } + + // color + if (prim.attributes.COLOR_0 != -1) + { + colors.AddRange(ctx.GLTF.GetArrayFromAccessor(prim.attributes.COLOR_0)); + } + + // skin + if (prim.attributes.JOINTS_0 != -1 && prim.attributes.WEIGHTS_0 != -1) + { + var joints0 = ctx.GLTF.GetArrayFromAccessor(prim.attributes.JOINTS_0); // uint4 + var weights0 = ctx.GLTF.GetArrayFromAccessor(prim.attributes.WEIGHTS_0).Select(x => x.One()).ToArray(); + + for (int j = 0; j < joints0.Length; ++j) + { + var bw = new BoneWeight(); + + bw.boneIndex0 = joints0[j].x; + bw.weight0 = weights0[j].x; + + bw.boneIndex1 = joints0[j].y; + bw.weight1 = weights0[j].y; + + bw.boneIndex2 = joints0[j].z; + bw.weight2 = weights0[j].z; + + bw.boneIndex3 = joints0[j].w; + bw.weight3 = weights0[j].w; + + meshContext.boneWeights.Add(bw); + } + } + + // blendshape + if (prim.targets != null && prim.targets.Count > 0) + { + for (int i = 0; i < prim.targets.Count; ++i) + { + //var name = string.Format("target{0}", i++); + var primTarget = prim.targets[i]; + var blendShape = new BlendShape(!string.IsNullOrEmpty(prim.extras.targetNames[i]) + ? prim.extras.targetNames[i] + : i.ToString()) + ; + if (primTarget.POSITION != -1) + { + blendShape.Positions.AddRange( + ctx.GLTF.GetArrayFromAccessor(primTarget.POSITION).Select(x => x.ReverseZ()).ToArray()); + } + if (primTarget.NORMAL != -1) + { + blendShape.Normals.AddRange( + ctx.GLTF.GetArrayFromAccessor(primTarget.NORMAL).Select(x => x.ReverseZ()).ToArray()); + } + if (primTarget.TANGENT != -1) + { + blendShape.Tangents.AddRange( + ctx.GLTF.GetArrayFromAccessor(primTarget.TANGENT).Select(x => x.ReverseZ()).ToArray()); + } + meshContext.blendShapes.Add(blendShape); + } + } + + var indices = + (indexBuffer >= 0) + ? ctx.GLTF.GetIndices(indexBuffer) + : TriangleUtil.FlipTriangle(Enumerable.Range(0, meshContext.positions.Length)).ToArray() // without index array + ; + for (int i = 0; i < indices.Length; ++i) + { + indices[i] += indexOffset; + } + + meshContext.subMeshes.Add(indices); + + // material + meshContext.materialIndices.Add(prim.material); + } + + meshContext.positions = positions.ToArray(); + meshContext.normals = normals.ToArray(); + meshContext.tangents = tangents.ToArray(); + meshContext.uv = uv.ToArray(); + + return meshContext; + } + + + // multiple submesh sharing same VertexBuffer + private static MeshContext _ImportMeshSharingVertexBuffer(ImporterContext ctx, glTFMesh gltfMesh) + { + var context = new MeshContext(); + + { + var prim = gltfMesh.primitives.First(); + context.positions = ctx.GLTF.GetArrayFromAccessor(prim.attributes.POSITION).SelectInplace(x => x.ReverseZ()); + + // normal + if (prim.attributes.NORMAL != -1) + { + context.normals = ctx.GLTF.GetArrayFromAccessor(prim.attributes.NORMAL).SelectInplace(x => x.ReverseZ()); + } + + // tangent + if (prim.attributes.TANGENT != -1) + { + context.tangents = ctx.GLTF.GetArrayFromAccessor(prim.attributes.TANGENT).SelectInplace(x => x.ReverseZ()); + } + + // uv + if (prim.attributes.TEXCOORD_0 != -1) + { + if (ctx.IsGeneratedUniGLTFAndOlder(1, 16)) + { +#pragma warning disable 0612 + // backward compatibility + context.uv = ctx.GLTF.GetArrayFromAccessor(prim.attributes.TEXCOORD_0).SelectInplace(x => x.ReverseY()); +#pragma warning restore 0612 + } + else + { + context.uv = ctx.GLTF.GetArrayFromAccessor(prim.attributes.TEXCOORD_0).SelectInplace(x => x.ReverseUV()); + } + } + else + { + // for inconsistent attributes in primitives + context.uv = new Vector2[context.positions.Length]; + } + + // color + if (prim.attributes.COLOR_0 != -1) + { + context.colors = ctx.GLTF.GetArrayFromAccessor(prim.attributes.COLOR_0); + } + + // skin + if (prim.attributes.JOINTS_0 != -1 && prim.attributes.WEIGHTS_0 != -1) + { + var joints0 = ctx.GLTF.GetArrayFromAccessor(prim.attributes.JOINTS_0); // uint4 + var weights0 = ctx.GLTF.GetArrayFromAccessor(prim.attributes.WEIGHTS_0); + for (int i = 0; i < weights0.Length; ++i) + { + weights0[i] = weights0[i].One(); + } + + for (int j = 0; j < joints0.Length; ++j) + { + var bw = new BoneWeight(); + + bw.boneIndex0 = joints0[j].x; + bw.weight0 = weights0[j].x; + + bw.boneIndex1 = joints0[j].y; + bw.weight1 = weights0[j].y; + + bw.boneIndex2 = joints0[j].z; + bw.weight2 = weights0[j].z; + + bw.boneIndex3 = joints0[j].w; + bw.weight3 = weights0[j].w; + + context.boneWeights.Add(bw); + } + } + + // blendshape + if (prim.targets != null && prim.targets.Count > 0) + { + context.blendShapes.AddRange(prim.targets.Select((x, i) => new BlendShape( + i < prim.extras.targetNames.Count && !string.IsNullOrEmpty(prim.extras.targetNames[i]) + ? prim.extras.targetNames[i] + : i.ToString()))); + for (int i = 0; i < prim.targets.Count; ++i) + { + //var name = string.Format("target{0}", i++); + var primTarget = prim.targets[i]; + var blendShape = context.blendShapes[i]; + + if (primTarget.POSITION != -1) + { + blendShape.Positions.Assign( + ctx.GLTF.GetArrayFromAccessor(primTarget.POSITION), x => x.ReverseZ()); + } + if (primTarget.NORMAL != -1) + { + blendShape.Normals.Assign( + ctx.GLTF.GetArrayFromAccessor(primTarget.NORMAL), x => x.ReverseZ()); + } + if (primTarget.TANGENT != -1) + { + blendShape.Tangents.Assign( + ctx.GLTF.GetArrayFromAccessor(primTarget.TANGENT), x => x.ReverseZ()); + } + } + } + } + + foreach (var prim in gltfMesh.primitives) + { + if (prim.indices == -1) + { + context.subMeshes.Add(TriangleUtil.FlipTriangle(Enumerable.Range(0, context.positions.Length)).ToArray()); + } + else + { + var indices = ctx.GLTF.GetIndices(prim.indices); + context.subMeshes.Add(indices); + } + + // material + context.materialIndices.Add(prim.material); + } + + return context; + } + + + [Serializable, StructLayout(LayoutKind.Sequential, Pack = 1)] + struct Float4 + { + public float x; + public float y; + public float z; + public float w; + + public Float4 One() + { + var sum = x + y + z + w; + var f = 1.0f / sum; + return new Float4 + { + x = x * f, + y = y * f, + z = z * f, + w = w * f, + }; + } + } + + + public class MeshContext + { + public string name; + public Vector3[] positions; + public Vector3[] normals; + public Vector4[] tangents; + public Vector2[] uv; + public Color[] colors; + public List boneWeights = new List(); + public List subMeshes = new List(); + public List materialIndices = new List(); + public List blendShapes = new List(); + } + + + public MeshContext ReadMesh(ImporterContext ctx, int meshIndex) + { + var gltfMesh = ctx.GLTF.meshes[meshIndex]; + glTFAttributes lastAttributes = null; + var sharedAttributes = true; + foreach (var prim in gltfMesh.primitives) + { + if (lastAttributes != null && !prim.attributes.Equals(lastAttributes)) + { + sharedAttributes = false; + break; + } + lastAttributes = prim.attributes; + } + + var meshContext = sharedAttributes + ? _ImportMeshSharingVertexBuffer(ctx, gltfMesh) + : _ImportMeshIndependentVertexBuffer(ctx, gltfMesh) + ; + meshContext.name = gltfMesh.name; + if (string.IsNullOrEmpty(meshContext.name)) + { + meshContext.name = string.Format("UniGLTF import#{0}", meshIndex); + } + + return meshContext; + } + + + public static MeshWithMaterials BuildMesh(ImporterContext ctx, MeshImporter.MeshContext meshContext) + { + if (!meshContext.materialIndices.Any()) + { + meshContext.materialIndices.Add(0); + } + + //Debug.Log(prims.ToJson()); + var mesh = new Mesh(); + mesh.name = meshContext.name; + + if (meshContext.positions.Length > UInt16.MaxValue) + { +#if UNITY_2017_3_OR_NEWER + mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; +#else + Debug.LogWarningFormat("vertices {0} exceed 65535. not implemented. Unity2017.3 supports large mesh", + meshContext.positions.Length); +#endif + } + + mesh.vertices = meshContext.positions; + bool recalculateNormals = false; + if (meshContext.normals != null && meshContext.normals.Length > 0) + { + mesh.normals = meshContext.normals; + } + else + { + recalculateNormals = true; + } + + if (meshContext.uv != null && meshContext.uv.Length > 0) + { + mesh.uv = meshContext.uv; + } + + bool recalculateTangents = true; +#if UNIGLTF_IMPORT_TANGENTS + if (meshContext.tangents != null && meshContext.tangents.Length > 0) + { + mesh.tangents = meshContext.tangents; + recalculateTangents = false; + } +#endif + + if (meshContext.colors != null && meshContext.colors.Length > 0) + { + mesh.colors = meshContext.colors; + } + if (meshContext.boneWeights != null && meshContext.boneWeights.Count > 0) + { + mesh.boneWeights = meshContext.boneWeights.ToArray(); + } + mesh.subMeshCount = meshContext.subMeshes.Count; + for (int i = 0; i < meshContext.subMeshes.Count; ++i) + { + mesh.SetTriangles(meshContext.subMeshes[i], i); + } + + if (recalculateNormals) + { + mesh.RecalculateNormals(); + } + if (recalculateTangents) + { +#if UNITY_5_6_OR_NEWER + mesh.RecalculateTangents(); +#else + CalcTangents(mesh); +#endif + } + + var result = new MeshWithMaterials + { + Mesh = mesh, + Materials = meshContext.materialIndices.Select(x => ctx.GetMaterial(x)).ToArray() + }; + + if (meshContext.blendShapes != null) + { + Vector3[] emptyVertices = null; + foreach (var blendShape in meshContext.blendShapes) + { + if (blendShape.Positions.Count > 0) + { + if (blendShape.Positions.Count == mesh.vertexCount) + { + mesh.AddBlendShapeFrame(blendShape.Name, FRAME_WEIGHT, + blendShape.Positions.ToArray(), + (meshContext.normals != null && meshContext.normals.Length == mesh.vertexCount) ? blendShape.Normals.ToArray() : null, + null + ); + } + else + { + Debug.LogWarningFormat("May be partial primitive has blendShape. Rquire separete mesh or extend blend shape, but not implemented: {0}", blendShape.Name); + } + } + else + { + if (emptyVertices == null) + { + emptyVertices = new Vector3[mesh.vertexCount]; + } + // Debug.LogFormat("empty blendshape: {0}.{1}", mesh.name, blendShape.Name); + // add empty blend shape for keep blend shape index + mesh.AddBlendShapeFrame(blendShape.Name, FRAME_WEIGHT, + emptyVertices, + null, + null + ); + } + } + } + + return result; + } + + /// + /// Meshの法線を元にタンジェントを計算する。 + /// + /// メッシュ + /// タンジェント + public static void CalcTangents(Mesh mesh) + { + int vertexCount = mesh.vertexCount; + Vector3[] vertices = mesh.vertices; + Vector3[] normals = mesh.normals; + Vector2[] texcoords = mesh.uv; + int[] triangles = mesh.triangles; + int triangleCount = triangles.Length / 3; + + Vector4[] tangents = new Vector4[vertexCount]; + Vector3[] tan1 = new Vector3[vertexCount]; + Vector3[] tan2 = new Vector3[vertexCount]; + + int tri = 0; + + for (int i = 0; i < (triangleCount); i++) + { + int i1 = triangles[tri]; + int i2 = triangles[tri + 1]; + int i3 = triangles[tri + 2]; + + Vector3 v1 = vertices[i1]; + Vector3 v2 = vertices[i2]; + Vector3 v3 = vertices[i3]; + + Vector2 w1 = texcoords[i1]; + Vector2 w2 = texcoords[i2]; + Vector2 w3 = texcoords[i3]; + + float x1 = v2.x - v1.x; + float x2 = v3.x - v1.x; + float y1 = v2.y - v1.y; + float y2 = v3.y - v1.y; + float z1 = v2.z - v1.z; + float z2 = v3.z - v1.z; + + float s1 = w2.x - w1.x; + float s2 = w3.x - w1.x; + float t1 = w2.y - w1.y; + float t2 = w3.y - w1.y; + + float r = 1.0f / (s1 * t2 - s2 * t1); + Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); + Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); + + tan1[i1] += sdir; + tan1[i2] += sdir; + tan1[i3] += sdir; + + tan2[i1] += tdir; + tan2[i2] += tdir; + tan2[i3] += tdir; + + tri += 3; + } + + for (int i = 0; i < (vertexCount); i++) + { + Vector3 n = normals[i]; + Vector3 t = tan1[i]; + + // Gram-Schmidt orthogonalize + Vector3.OrthoNormalize(ref n, ref t); + tangents[i].x = t.x; + tangents[i].y = t.y; + tangents[i].z = t.z; + + // Calculate handedness + tangents[i].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[i]) < 0.0f) ? -1.0f : 1.0f; + } + + mesh.tangents = tangents; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/MeshImporter.cs.meta b/UniGLTF/Core/Scripts/IO/MeshImporter.cs.meta new file mode 100644 index 000000000..e01d5bdb5 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MeshImporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cbd062b14ca5eba4da4a5653b85e2151 +timeCreated: 1535177327 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/MeshWithMaterials.cs b/UniGLTF/Core/Scripts/IO/MeshWithMaterials.cs new file mode 100644 index 000000000..fb0a72b1a --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MeshWithMaterials.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using UnityEngine; + + +namespace UniGLTF +{ + public class MeshWithMaterials + { + public Mesh Mesh; + public Material[] Materials; + public List Renderers=new List(); // SkinnedMeshRenderer or MeshRenderer + } +} diff --git a/UniGLTF/Core/Scripts/IO/MeshWithMaterials.cs.meta b/UniGLTF/Core/Scripts/IO/MeshWithMaterials.cs.meta new file mode 100644 index 000000000..7c8538119 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/MeshWithMaterials.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 04f2758551c076e4e9060e3117817e29 +timeCreated: 1517335903 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/NodeImporter.cs b/UniGLTF/Core/Scripts/IO/NodeImporter.cs new file mode 100644 index 000000000..a0df48d6c --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/NodeImporter.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + + +namespace UniGLTF +{ + public static class NodeImporter + { + public static GameObject ImportNode(glTFNode node) + { + var nodeName = node.name; + if (!string.IsNullOrEmpty(nodeName) && nodeName.Contains("/")) + { + Debug.LogWarningFormat("node {0} contains /. replace _", node.name); + nodeName = nodeName.Replace("/", "_"); + } + var go = new GameObject(nodeName); + + // + // transform + // + if (node.translation != null && node.translation.Length > 0) + { + go.transform.localPosition = new Vector3( + node.translation[0], + node.translation[1], + node.translation[2]); + } + if (node.rotation != null && node.rotation.Length > 0) + { + go.transform.localRotation = new Quaternion( + node.rotation[0], + node.rotation[1], + node.rotation[2], + node.rotation[3]); + } + if (node.scale != null && node.scale.Length > 0) + { + go.transform.localScale = new Vector3( + node.scale[0], + node.scale[1], + node.scale[2]); + } + if (node.matrix != null && node.matrix.Length > 0) + { + var m = UnityExtensions.MatrixFromArray(node.matrix); + go.transform.localRotation = m.ExtractRotation(); + go.transform.localPosition = m.ExtractPosition(); + go.transform.localScale = m.ExtractScale(); + } + return go; + } + + public class TransformWithSkin + { + public Transform Transform; + public GameObject GameObject { get { return Transform.gameObject; } } + public int? SkinIndex; + } + + public static TransformWithSkin BuildHierarchy(ImporterContext context, int i) + { + var go = context.Nodes[i].gameObject; + if (string.IsNullOrEmpty(go.name)) + { + go.name = string.Format("node{0:000}", i); + } + + var nodeWithSkin = new TransformWithSkin + { + Transform = go.transform, + }; + + // + // build hierachy + // + var node = context.GLTF.nodes[i]; + if (node.children != null) + { + foreach (var child in node.children) + { + context.Nodes[child].transform.SetParent(context.Nodes[i].transform, + false // node has local transform + ); + } + } + + // + // attach mesh + // + if (node.mesh != -1) + { + var mesh = context.Meshes[node.mesh]; + if (mesh.Mesh.blendShapeCount == 0 && node.skin == -1) + { + // without blendshape and bone skinning + var filter = go.AddComponent(); + filter.sharedMesh = mesh.Mesh; + var renderer = go.AddComponent(); + renderer.sharedMaterials = mesh.Materials; + // invisible in loading + renderer.enabled = false; + mesh.Renderers.Add(renderer); + } + else + { + var renderer = go.AddComponent(); + + if (node.skin != -1) + { + nodeWithSkin.SkinIndex = node.skin; + } + + renderer.sharedMesh = mesh.Mesh; + renderer.sharedMaterials = mesh.Materials; + // invisible in loading + renderer.enabled = false; + mesh.Renderers.Add(renderer); + } + } + + return nodeWithSkin; + } + + // + // fix node's coordinate. z-back to z-forward + // + public static void FixCoordinate(ImporterContext context, List nodes) + { + var globalTransformMap = nodes.ToDictionary(x => x.Transform, x => new PosRot + { + Position = x.Transform.position, + Rotation = x.Transform.rotation, + }); + foreach (var x in context.GLTF.rootnodes) + { + // fix nodes coordinate + // reverse Z in global + var t = nodes[x].Transform; + //t.SetParent(root.transform, false); + + foreach (var transform in t.Traverse()) + { + var g = globalTransformMap[transform]; + transform.position = g.Position.ReverseZ(); + transform.rotation = g.Rotation.ReverseZ(); + } + } + } + + public static void SetupSkinning(ImporterContext context, List nodes, int i) + { + var x = nodes[i]; + var skinnedMeshRenderer = x.Transform.GetComponent(); + if (skinnedMeshRenderer != null) + { + var mesh = skinnedMeshRenderer.sharedMesh; + if (x.SkinIndex.HasValue) + { + if (mesh == null) throw new Exception(); + if (skinnedMeshRenderer == null) throw new Exception(); + + if (x.SkinIndex.Value < context.GLTF.skins.Count) + { + var skin = context.GLTF.skins[x.SkinIndex.Value]; + + skinnedMeshRenderer.sharedMesh = null; + + var joints = skin.joints.Select(y => nodes[y].Transform).ToArray(); + skinnedMeshRenderer.bones = joints; + + if (skin.skeleton >= 0 && skin.skeleton < nodes.Count) + { + skinnedMeshRenderer.rootBone = nodes[skin.skeleton].Transform; + } + + if (skin.inverseBindMatrices != -1) + { + // BlendShape only ? +#if false + // https://docs.unity3d.com/ScriptReference/Mesh-bindposes.html + var hipsParent = nodes[0].Transform; + var calculatedBindPoses = joints.Select(y => y.worldToLocalMatrix * hipsParent.localToWorldMatrix).ToArray(); + mesh.bindposes = calculatedBindPoses; +#else + var bindPoses = context.GLTF.GetArrayFromAccessor(skin.inverseBindMatrices) + .Select(y => y.ReverseZ()) + .ToArray() + ; + mesh.bindposes = bindPoses; +#endif + } + + skinnedMeshRenderer.sharedMesh = mesh; + } + } + } + } + + } +} diff --git a/UniGLTF/Core/Scripts/IO/NodeImporter.cs.meta b/UniGLTF/Core/Scripts/IO/NodeImporter.cs.meta new file mode 100644 index 000000000..6ceb399a7 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/NodeImporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: dcc3725a84dcf7f49960c89f1f68534c +timeCreated: 1537534443 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/ShaderPropExporter.meta b/UniGLTF/Core/Scripts/IO/ShaderPropExporter.meta new file mode 100644 index 000000000..85372c3fb --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ShaderPropExporter.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f877649750df3814aa5272250b22678c +folderAsset: yes +timeCreated: 1533542717 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/ShaderPropExporter/PreShaderPropExporter.cs b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/PreShaderPropExporter.cs new file mode 100644 index 000000000..a529320ca --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/PreShaderPropExporter.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF.ShaderPropExporter +{ + public class PreExportShadersAttribute : Attribute { } + public class PreExportShaderAttribute : Attribute { } + + public struct SupportedShader + { + public string TargetFolder; + public string ShaderName; + + public SupportedShader(string targetFolder, string shaderName) + { + TargetFolder = targetFolder; + ShaderName = shaderName; + } + } + + public static partial class PreShaderPropExporter + { + const string TARGET_FOLDER = "UniGLTF/Core/Scripts"; + +#pragma warning disable 414 + [PreExportShaders] + static SupportedShader[] SupportedShaders = new SupportedShader[] + { + new SupportedShader(TARGET_FOLDER, "Standard"), + new SupportedShader(TARGET_FOLDER, "Unlit/Color"), + new SupportedShader(TARGET_FOLDER, "Unlit/Texture"), + new SupportedShader(TARGET_FOLDER, "Unlit/Transparent"), + new SupportedShader(TARGET_FOLDER, "Unlit/Transparent Cutout"), + new SupportedShader(TARGET_FOLDER, "UniGLTF/UniUnlit"), + }; +#pragma warning restore 414 + +#if UNITY_EDITOR + [MenuItem(UniGLTFVersion.UNIGLTF_VERSION + "/PreExport ShaderProps")] + public static void PreExport() + { + foreach (var fi in typeof(PreShaderPropExporter).GetFields( + BindingFlags.Static + | BindingFlags.Public + | BindingFlags.NonPublic)) + { + var attr = fi.GetCustomAttributes(true).FirstOrDefault(y => y is PreExportShadersAttribute); + if (attr != null) + { + var supportedShaders = fi.GetValue(null) as SupportedShader[]; + foreach (var supported in supportedShaders) + { + PreExport(supported); + } + } + } + } + + static string EscapeShaderName(string name) + { + return name.Replace("/", "_").Replace(" ", "_"); + } + + static UnityPath GetExportDir(string target) + { + foreach (var x in UnityPath.FromUnityPath("Assets").TravserseDir()) + { + if (x.Value.EndsWith(target)) + { + var dir = x.Child("PreExportShaderProps"); + dir.EnsureFolder(); + return dir; + } + } + throw new Exception(target + " not found"); + } + + static void PreExport(SupportedShader supportedShader) + { + var path = GetExportDir(supportedShader.TargetFolder).Child(EscapeShaderName(supportedShader.ShaderName) + ".cs"); + Debug.LogFormat("PreExport: {0}", path.FullPath); + + var shader = Shader.Find(supportedShader.ShaderName); + var props = ShaderProps.FromShader(shader); + + File.WriteAllText(path.FullPath, props.ToString(shader.name)); + } +#endif + + #region Runtime + static Dictionary m_shaderPropMap; + + public static ShaderProps GetPropsForSupportedShader(string shaderName) + { + if (m_shaderPropMap == null) + { + m_shaderPropMap = new Dictionary(); + foreach (var prop in typeof(PreShaderPropExporter).GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (prop.GetCustomAttributes(typeof(PreExportShaderAttribute), true).Any()) + { + var kv = (KeyValuePair)prop.GetValue(null, null); + m_shaderPropMap.Add(kv.Key, kv.Value); + } + } + } + + ShaderProps props; + if (m_shaderPropMap.TryGetValue(shaderName, out props)) + { + return props; + } + +#if UNITY_EDITOR + // fallback + Debug.LogWarningFormat("{0} is not predefined shader. Use ShaderUtil", shaderName); + var shader = Shader.Find(shaderName); + return ShaderProps.FromShader(shader); +#else + return null; +#endif + } + #endregion + } +} diff --git a/UniGLTF/Core/Scripts/IO/ShaderPropExporter/PreShaderPropExporter.cs.meta b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/PreShaderPropExporter.cs.meta new file mode 100644 index 000000000..431e9cca2 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/PreShaderPropExporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 50935dd2f9f3fa445a687f30d4dd663b +timeCreated: 1533035131 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/ShaderPropExporter/ShaderProps.cs b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/ShaderProps.cs new file mode 100644 index 000000000..d5debaba5 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/ShaderProps.cs @@ -0,0 +1,111 @@ +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +#endif + + +namespace UniGLTF.ShaderPropExporter +{ + public enum ShaderPropertyType + { + TexEnv, + Color, + Range, + Float, + Vector, + } + + public struct ShaderProperty + { + public string Key; + public ShaderPropertyType ShaderPropertyType; + + public ShaderProperty(string key, ShaderPropertyType propType) + { + Key = key; + ShaderPropertyType = propType; + } + } + + public class ShaderProps + { + public ShaderProperty[] Properties; + +#if UNITY_EDITOR + static ShaderPropertyType ConvType(ShaderUtil.ShaderPropertyType src) + { + switch (src) + { + case ShaderUtil.ShaderPropertyType.TexEnv: return ShaderPropertyType.TexEnv; + case ShaderUtil.ShaderPropertyType.Color: return ShaderPropertyType.Color; + case ShaderUtil.ShaderPropertyType.Float: return ShaderPropertyType.Float; + case ShaderUtil.ShaderPropertyType.Range: return ShaderPropertyType.Range; + case ShaderUtil.ShaderPropertyType.Vector: return ShaderPropertyType.Vector; + default: throw new NotImplementedException(); + } + } + + public static ShaderProps FromShader(Shader shader) + { + var properties = new List(); + for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); ++i) + { + var name = ShaderUtil.GetPropertyName(shader, i); + var propType = ShaderUtil.GetPropertyType(shader, i); + properties.Add(new ShaderProperty(name, ConvType(propType))); + } + + return new ShaderProps + { + Properties = properties.ToArray(), + }; + } + + static string EscapeShaderName(string name) + { + return name.Replace("/", "_").Replace(" ", "_"); + } + + public string ToString(string shaderName) + { + var list = new List(); + foreach (var prop in Properties) + { + list.Add(string.Format("new ShaderProperty(\"{0}\", ShaderPropertyType.{1})\r\n", prop.Key, prop.ShaderPropertyType)); + } + + return string.Format(@"using System.Collections.Generic; + + +namespace UniGLTF.ShaderPropExporter +{{ + public static partial class PreShaderPropExporter + {{ + [PreExportShader] + static KeyValuePair {0} + {{ + get + {{ + return new KeyValuePair( + ""{1}"", + new ShaderProps + {{ + Properties = new ShaderProperty[]{{ +{2} + }} + }} + ); + }} + }} + }} +}} +" +, EscapeShaderName(shaderName) +, shaderName +, String.Join(",", list.ToArray())); + } +#endif + } +} diff --git a/UniGLTF/Core/Scripts/IO/ShaderPropExporter/ShaderProps.cs.meta b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/ShaderProps.cs.meta new file mode 100644 index 000000000..b078bfe66 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ShaderPropExporter/ShaderProps.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 279964035c950b24cb745511298855dd +timeCreated: 1533539330 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/ShaderStore.cs b/UniGLTF/Core/Scripts/IO/ShaderStore.cs new file mode 100644 index 000000000..dc767a57a --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ShaderStore.cs @@ -0,0 +1,123 @@ +using UnityEngine; + + +namespace UniGLTF +{ + public interface IShaderStore + { + Shader GetShader(glTFMaterial material); + } + + public class ShaderStore : IShaderStore + { + readonly string m_defaultShaderName = "Standard"; + Shader m_default; + Shader Default + { + get + { + if (m_default == null) + { + m_default = Shader.Find(m_defaultShaderName); + } + return m_default; + } + } + + Shader m_vcolor; + Shader VColor + { + get + { + if (m_vcolor == null) m_vcolor = Shader.Find("UniGLTF/StandardVColor"); + return m_vcolor; + } + } + + Shader m_uniUnlit; + Shader UniUnlit + { + get + { + if (m_uniUnlit == null) m_uniUnlit = Shader.Find("UniGLTF/UniUnlit"); + return m_uniUnlit; + } + } + + Shader m_unlitTexture; + Shader UnlitTexture + { + get + { + if (m_unlitTexture == null) m_unlitTexture = Shader.Find("Unlit/Texture"); + return m_unlitTexture; + } + } + + Shader m_unlitColor; + Shader UnlitColor + { + get + { + if (m_unlitColor == null) m_unlitColor = Shader.Find("Unlit/Color"); + return m_unlitColor; + } + } + + Shader m_unlitTransparent; + Shader UnlitTransparent + { + get + { + if (m_unlitTransparent == null) m_unlitTransparent = Shader.Find("Unlit/Transparent"); + return m_unlitTransparent; + } + } + + Shader m_unlitCoutout; + Shader UnlitCutout + { + get + { + if (m_unlitCoutout == null) m_unlitCoutout = Shader.Find("Unlit/Transparent Cutout"); + return m_unlitCoutout; + } + } + + //ImporterContext m_context; + public ShaderStore(ImporterContext _) + { + //m_context = context; + } + + public static bool IsWhite(float[] color) + { + if (color == null) return false; + if(color.Length!=4)return false; + if(color[0]!=1 + || color[1]!=1 + || color[2]!=1 + || color[3] != 1) + { + return false; + } + return true; + } + + public Shader GetShader(glTFMaterial material) + { + if (material == null) + { + return Default; + } + + if (material.extensions != null && material.extensions.KHR_materials_unlit != null) + { + return UniUnlit; + } + + // standard + return Default; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/ShaderStore.cs.meta b/UniGLTF/Core/Scripts/IO/ShaderStore.cs.meta new file mode 100644 index 000000000..def6e6602 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ShaderStore.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 75aae8e866422d24180458afaacb2c1d +timeCreated: 1529327427 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/StaticMeshIntegrator.cs b/UniGLTF/Core/Scripts/IO/StaticMeshIntegrator.cs new file mode 100644 index 000000000..44dd3051d --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/StaticMeshIntegrator.cs @@ -0,0 +1,186 @@ +using System.Collections.Generic; +using UnityEngine; +using System.IO; +using System.Linq; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public static class StaticMeshIntegrator + { + const string ASSET_SUFFIX = ".mesh.asset"; + + class Integrator + { + List m_positions = new List(); + List m_normals = new List(); + List m_uv = new List(); + /* + List m_uv2 = new List(); // ToDo + List m_uv3 = new List(); // ToDo + List m_uv4 = new List(); // ToDo + List m_colors = new List(); // ToDo + */ + + List m_subMeshes = new List(); + + List m_materials = new List(); + public List Materials + { + get { return m_materials; } + } + + public void Push(Matrix4x4 localToRoot, Mesh mesh, Material[] materials) + { + var offset = m_positions.Count; + + var hasNormal = m_normals.Count == m_positions.Count; + var hasUv = m_uv.Count == m_positions.Count; + + // attributes + m_positions.AddRange(mesh.vertices.Select(x => localToRoot.MultiplyPoint(x))); + if(mesh.normals!=null && mesh.normals.Length == mesh.vertexCount) + { + if (!hasNormal) for (int i = m_normals.Count; i < m_positions.Count; ++i) m_normals.Add(Vector3.zero); + m_normals.AddRange(mesh.normals.Select(x => localToRoot.MultiplyVector(x))); + } + if (mesh.uv != null && mesh.uv.Length == mesh.vertexCount) + { + if (!hasUv) for (int i = m_uv.Count; i < m_positions.Count; ++i) m_uv.Add(Vector2.zero); + m_uv.AddRange(mesh.uv); + } + + // indices + for (int i = 0; i < mesh.subMeshCount; ++i) + { + m_subMeshes.Add(mesh.GetIndices(i).Select(x => offset + x).ToArray()); + } + + // materials + m_materials.AddRange(materials); + } + + public Mesh ToMesh() + { + var mesh = new Mesh(); + mesh.name = "integrated"; + + mesh.vertices = m_positions.ToArray(); + if (m_normals.Count > 0) + { + if (m_normals.Count < m_positions.Count) for (int i = m_normals.Count; i < m_positions.Count; ++i) m_normals.Add(Vector3.zero); + mesh.normals = m_normals.ToArray(); + } + if (m_uv.Count > 0) + { + if (m_uv.Count < m_positions.Count) for (int i = m_uv.Count; i < m_positions.Count; ++i) m_uv.Add(Vector2.zero); + mesh.uv = m_uv.ToArray(); + } + + mesh.subMeshCount = m_subMeshes.Count; + for(int i=0; i(); + var filter = t.GetComponent(); + if (renderer != null && filter != null && filter.sharedMesh != null + && renderer.sharedMaterials!=null && renderer.sharedMaterials.Length == filter.sharedMesh.subMeshCount) + { + integrator.Push(root.worldToLocalMatrix * t.localToWorldMatrix, filter.sharedMesh, renderer.sharedMaterials); + } + } + + return new MeshWithMaterials + { + Mesh = integrator.ToMesh(), + Materials = integrator.Materials.ToArray(), + }; + } + +#if UNITY_EDITOR + [MenuItem(UniGLTFVersion.UNIGLTF_VERSION + "/Integrate static mesh", validate = true)] + public static bool CanIntegrateSelected() + { + return Selection.activeObject != null && Selection.activeObject is GameObject; + } + + [MenuItem(UniGLTFVersion.UNIGLTF_VERSION + "/Integrate static mesh")] + public static void IntegrateSelected() + { + var go = Selection.activeObject as GameObject; + var meshWithMaterials = Integrate(go.transform); + + // save as asset + var assetPath = ""; +#if UNITY_2018_2_OR_NEWER + var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go); +#else + var prefab = PrefabUtility.GetPrefabParent(go); +#endif + if (prefab != null) + { + var prefabPath = AssetDatabase.GetAssetPath(prefab); + assetPath = string.Format("{0}/{1}_{2}{3}", + Path.GetDirectoryName(prefabPath), + Path.GetFileNameWithoutExtension(prefabPath), + go.name, + ASSET_SUFFIX + ); + } + else + { + var path = EditorUtility.SaveFilePanel( + "Save mesh", + "Assets", + go.name+".asset", + "asset"); + if (string.IsNullOrEmpty(path)) + { + return; + } + assetPath = UnityPath.FromFullpath(path).Value; + } + + assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath); + Debug.LogFormat("CreateAsset: {0}", assetPath); + AssetDatabase.CreateAsset(meshWithMaterials.Mesh, assetPath); + + // add component + var meshObject = new GameObject(go.name + ".integrated"); + if (go.transform.parent != null) + { + meshObject.transform.SetParent(go.transform.parent, false); + } + meshObject.transform.localPosition = go.transform.localPosition; + meshObject.transform.localRotation = go.transform.localRotation; + meshObject.transform.localScale = go.transform.localScale; + + var filter = meshObject.AddComponent(); + filter.sharedMesh = meshWithMaterials.Mesh; + var renderer = meshObject.AddComponent(); + renderer.sharedMaterials = meshWithMaterials.Materials; + } +#endif + } +} diff --git a/UniGLTF/Core/Scripts/IO/StaticMeshIntegrator.cs.meta b/UniGLTF/Core/Scripts/IO/StaticMeshIntegrator.cs.meta new file mode 100644 index 000000000..b9b730e00 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/StaticMeshIntegrator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d049a3daed1255499aaec8b6d1a2afe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/TextureConverter.cs b/UniGLTF/Core/Scripts/IO/TextureConverter.cs new file mode 100644 index 000000000..b7fba39cc --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureConverter.cs @@ -0,0 +1,177 @@ +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public interface ITextureConverter + { + Texture2D GetImportTexture(Texture2D texture); + Texture2D GetExportTexture(Texture2D texture); + } + + public static class TextureConverter + { + public delegate Color32 ColorConversion(Color32 color); + + public static Texture2D Convert(Texture2D texture, glTFTextureTypes textureType, ColorConversion colorConversion, Material convertMaterial) + { + var copyTexture = TextureItem.CopyTexture(texture, TextureIO.GetColorSpace(textureType), convertMaterial); + if (colorConversion != null) + { + copyTexture.SetPixels32(copyTexture.GetPixels32().Select(x => colorConversion(x)).ToArray()); + copyTexture.Apply(); + } + copyTexture.name = texture.name; + return copyTexture; + } + + public static void AppendTextureExtension(Texture texture, string extension) + { + if (!texture.name.EndsWith(extension)) + { + texture.name = texture.name + extension; + } + } + + public static void RemoveTextureExtension(Texture texture, string extension) + { + if (texture.name.EndsWith(extension)) + { + texture.name = texture.name.Replace(extension, ""); + } + } + } + + class MetallicRoughnessConverter : ITextureConverter + { + private const string m_extension = ".metallicRoughness"; + + public Texture2D GetImportTexture(Texture2D texture) + { + var converted = TextureConverter.Convert(texture, glTFTextureTypes.Metallic, Import, null); + TextureConverter.AppendTextureExtension(converted, m_extension); + return converted; + } + + public Texture2D GetExportTexture(Texture2D texture) + { + var converted = TextureConverter.Convert(texture, glTFTextureTypes.Metallic, Export, null); + TextureConverter.RemoveTextureExtension(converted, m_extension); + return converted; + } + + public Color32 Import(Color32 src) + { + return new Color32 + { + r = src.b, + g = 0, + b = 0, + a = (byte)(255 - src.g), + }; + } + + public Color32 Export(Color32 src) + { + return new Color32 + { + r = 0, + g = (byte)(255 - src.a), + b = src.r, + a = 255, + }; + } + } + + class NormalConverter : ITextureConverter + { + private const string m_extension = ".normal"; + + private Material m_decoder; + private Material GetDecoder() + { + if (m_decoder == null) + { + m_decoder = new Material(Shader.Find("UniGLTF/NormalMapDecoder")); + } + return m_decoder; + } + + private Material m_encoder; + private Material GetEncoder() + { + if (m_encoder == null) + { + m_encoder = new Material(Shader.Find("UniGLTF/NormalMapEncoder")); + } + return m_encoder; + } + + public Texture2D GetImportTexture(Texture2D texture) + { +#if UNITY_WEBGL && !UNITY_EDITOR + return texture; +#endif + var mat = GetEncoder(); + var converted = TextureConverter.Convert(texture, glTFTextureTypes.Normal, null, mat); + TextureConverter.AppendTextureExtension(converted, m_extension); + return converted; + } + + public Texture2D GetExportTexture(Texture2D texture) + { +#if UNITY_WEBGL && !UNITY_EDITOR + return texture; +#endif + var mat = GetDecoder(); + var converted = TextureConverter.Convert(texture, glTFTextureTypes.Normal, null, mat); + TextureConverter.RemoveTextureExtension(converted, m_extension); + return converted; + } + } + + class OcclusionConverter : ITextureConverter + { + private const string m_extension = ".occlusion"; + + public Texture2D GetImportTexture(Texture2D texture) + { + var converted = TextureConverter.Convert(texture, glTFTextureTypes.Occlusion, Import, null); + TextureConverter.AppendTextureExtension(converted, m_extension); + return converted; + } + + public Texture2D GetExportTexture(Texture2D texture) + { + var converted = TextureConverter.Convert(texture, glTFTextureTypes.Occlusion, Export, null); + TextureConverter.RemoveTextureExtension(converted, m_extension); + return converted; + } + + public Color32 Import(Color32 src) + { + return new Color32 + { + r = 0, + g = src.r, + b = 0, + a = 255, + }; + } + + public Color32 Export(Color32 src) + { + return new Color32 + { + r = src.g, + g = 0, + b = 0, + a = 255, + }; + } + } +} \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/TextureConverter.cs.meta b/UniGLTF/Core/Scripts/IO/TextureConverter.cs.meta new file mode 100644 index 000000000..7f44191b9 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureConverter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5d2d27884d6c4780b08c745e5cc5c5b5 +timeCreated: 1537261040 \ No newline at end of file diff --git a/UniGLTF/Core/Scripts/IO/TextureExportManager.cs b/UniGLTF/Core/Scripts/IO/TextureExportManager.cs new file mode 100644 index 000000000..2137af572 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureExportManager.cs @@ -0,0 +1,85 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + + +namespace UniGLTF +{ + public class TextureExportManager + { + List m_textures; + public List Textures + { + get { return m_textures; } + } + + List m_exportTextures; + public Texture GetExportTexture(int index) + { + if (index < 0 || index >= m_exportTextures.Count) + { + return null; + } + if (m_exportTextures[index] != null) + { + // コピー変換済み + return m_exportTextures[index]; + } + + // オリジナル + return m_textures[index]; + } + + public TextureExportManager(IEnumerable textures) + { + /* + if (textures == null) + { + throw new System.ArgumentNullException(); + } + */ + m_textures = textures.ToList(); + m_exportTextures = new List(Enumerable.Repeat(null, m_textures.Count)); + } + + public int CopyAndGetIndex(Texture texture, RenderTextureReadWrite readWrite) + { + if (texture == null) + { + return -1; + } + + var index = m_textures.IndexOf(texture); + if (index == -1) + { + // ありえない? + return -1; + } + + // ToDo: may already exists + m_exportTextures[index] = TextureItem.CopyTexture(texture, readWrite, null); + + return index; + } + + public int ConvertAndGetIndex(Texture texture, ITextureConverter converter) + { + if (texture == null) + { + return -1; + } + + var index = m_textures.IndexOf(texture); + if (index == -1) + { + // ありえない? + return -1; + } + + m_exportTextures[index] = converter.GetExportTexture(texture as Texture2D); + + return index; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/TextureExportManager.cs.meta b/UniGLTF/Core/Scripts/IO/TextureExportManager.cs.meta new file mode 100644 index 000000000..5048e0474 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureExportManager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 435499f173753ac418331fe0f967b789 +timeCreated: 1541561421 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/TextureIO.cs b/UniGLTF/Core/Scripts/IO/TextureIO.cs new file mode 100644 index 000000000..b46fee5ab --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureIO.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public static class TextureIO + { + public static RenderTextureReadWrite GetColorSpace(glTFTextureTypes textureType) + { + switch (textureType) + { + case glTFTextureTypes.Metallic: + case glTFTextureTypes.Normal: + case glTFTextureTypes.Occlusion: + return RenderTextureReadWrite.Linear; + case glTFTextureTypes.BaseColor: + case glTFTextureTypes.Emissive: + return RenderTextureReadWrite.sRGB; + default: + return RenderTextureReadWrite.sRGB; + } + } + + public static glTFTextureTypes GetglTFTextureType(string shaderName, string propName) + { + switch (propName) + { + case "_Color": + return glTFTextureTypes.BaseColor; + case "_MetallicGlossMap": + return glTFTextureTypes.Metallic; + case "_BumpMap": + return glTFTextureTypes.Normal; + case "_OcclusionMap": + return glTFTextureTypes.Occlusion; + case "_EmissionMap": + return glTFTextureTypes.Emissive; + default: + return glTFTextureTypes.Unknown; + } + } + + public static glTFTextureTypes GetglTFTextureType(glTF glTf, int textureIndex) + { + foreach (var material in glTf.materials) + { + var textureInfo = material.GetTextures().FirstOrDefault(x => (x!=null) && x.index == textureIndex); + if (textureInfo != null) + { + return textureInfo.TextreType; + } + } + return glTFTextureTypes.Unknown; + } + +#if UNITY_EDITOR + public static void MarkTextureAssetAsNormalMap(string assetPath) + { + if (string.IsNullOrEmpty(assetPath)) + { + return; + } + + var textureImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter; + if (null == textureImporter) + { + return; + } + + //Debug.LogFormat("[MarkTextureAssetAsNormalMap] {0}", assetPath); + textureImporter.textureType = TextureImporterType.NormalMap; + textureImporter.SaveAndReimport(); + } +#endif + + public struct TextureExportItem + { + public Texture Texture; + public glTFTextureTypes TextureType; + + public TextureExportItem(Texture texture, glTFTextureTypes textureType) + { + Texture = texture; + TextureType = textureType; + } + } + + public static IEnumerable GetTextures(Material m) + { + var props = ShaderPropExporter.PreShaderPropExporter.GetPropsForSupportedShader(m.shader.name); + if (props == null) + { + yield return new TextureExportItem(m.mainTexture, glTFTextureTypes.BaseColor); + } + + foreach (var prop in props.Properties) + { + + if (prop.ShaderPropertyType == ShaderPropExporter.ShaderPropertyType.TexEnv) + { + yield return new TextureExportItem(m.GetTexture(prop.Key), GetglTFTextureType(m.shader.name, prop.Key)); + } + } + } + + + struct BytesWithMime + { + public Byte[] Bytes; + public string Mime; + } + + static BytesWithMime GetBytesWithMime(Texture texture, glTFTextureTypes textureType) + { +#if UNITY_EDITOR + var path = UnityPath.FromAsset(texture); + if (path.IsUnderAssetsFolder) + { + if (path.Extension == ".png") + { + return new BytesWithMime + { + Bytes = System.IO.File.ReadAllBytes(path.FullPath), + Mime = "image/png", + }; + } + } +#endif + + return new BytesWithMime + { + Bytes = TextureItem.CopyTexture(texture, TextureIO.GetColorSpace(textureType), null).EncodeToPNG(), + Mime = "image/png", + }; + } + + public static int ExportTexture(glTF gltf, int bufferIndex, Texture texture, glTFTextureTypes textureType) + { + var bytesWithMime = GetBytesWithMime(texture, textureType); ; + + // add view + var view = gltf.buffers[bufferIndex].Append(bytesWithMime.Bytes, glBufferTarget.NONE); + var viewIndex = gltf.AddBufferView(view); + + // add image + var imageIndex = gltf.images.Count; + gltf.images.Add(new glTFImage + { + name = texture.name, + bufferView = viewIndex, + mimeType = bytesWithMime.Mime, + }); + + // add sampler + var samplerIndex = gltf.samplers.Count; + var sampler = TextureSamplerUtil.Export(texture); + gltf.samplers.Add(sampler); + + // add texture + gltf.textures.Add(new glTFTexture + { + sampler = samplerIndex, + source = imageIndex, + }); + + return imageIndex; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/TextureIO.cs.meta b/UniGLTF/Core/Scripts/IO/TextureIO.cs.meta new file mode 100644 index 000000000..2b85d8e5d --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureIO.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: eca1330a83e17a14eb99fc6ea1697922 +timeCreated: 1533533316 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/TextureItem.cs b/UniGLTF/Core/Scripts/IO/TextureItem.cs new file mode 100644 index 000000000..d77699e03 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureItem.cs @@ -0,0 +1,300 @@ +using UnityEngine; +using System.Linq; +using System.Collections.Generic; +using System.Collections; +using System.IO; +using System; +using DepthFirstScheduler; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public class TextureItem + { + private int m_textureIndex; + public Texture2D Texture + { + get + { + return m_textureLoader.Texture; + } + } + + #region Texture converter + private Dictionary m_converts = new Dictionary(); + public Dictionary Converts + { + get { return m_converts; } + } + + public Texture2D ConvertTexture(string prop) + { + var convertedTexture = Converts.FirstOrDefault(x => x.Key == prop); + if (convertedTexture.Value != null) + return convertedTexture.Value; + + if (prop == "_BumpMap") + { + if (Application.isPlaying) + { + var converted = new NormalConverter().GetImportTexture(Texture); + m_converts.Add(prop, converted); + return converted; + } + else + { +#if UNITY_EDITOR + var textureAssetPath = AssetDatabase.GetAssetPath(Texture); + if (!string.IsNullOrEmpty(textureAssetPath)) + { + TextureIO.MarkTextureAssetAsNormalMap(textureAssetPath); + } + else + { + Debug.LogWarningFormat("no asset for {0}", Texture); + } +#endif + return Texture; + } + } + + if (prop == "_MetallicGlossMap") + { + var converted = new MetallicRoughnessConverter().GetImportTexture(Texture); + m_converts.Add(prop, converted); + return converted; + } + + if (prop == "_OcclusionMap") + { + var converted = new OcclusionConverter().GetImportTexture(Texture); + m_converts.Add(prop, converted); + return converted; + } + + return null; + } + #endregion + + public bool IsAsset + { + private set; + get; + } + + public IEnumerable GetTexturesForSaveAssets() + { + if (!IsAsset) + { + yield return Texture; + } + if (m_converts.Any()) + { + foreach (var texture in m_converts) + { + yield return texture.Value; + } + } + } + + /// + /// Texture from buffer + /// + /// + public TextureItem(int index) + { + m_textureIndex = index; +#if UNIGLTF_USE_WEBREQUEST_TEXTURELOADER + m_textureLoader = new UnityWebRequestTextureLoader(m_textureIndex); +#else + m_textureLoader = new TextureLoader(m_textureIndex); +#endif + } + +#if UNITY_EDITOR + /// + /// Texture from asset + /// + /// + /// + /// + public TextureItem(int index, UnityPath assetPath, string textureName) + { + m_textureIndex = index; + IsAsset = true; + m_textureLoader = new AssetTextureLoader(assetPath, textureName); + } +#endif + + #region Process + ITextureLoader m_textureLoader; + + public void Process(glTF gltf, IStorage storage) + { + ProcessOnAnyThread(gltf, storage); + ProcessOnMainThreadCoroutine(gltf).CoroutinetoEnd(); + } + + public IEnumerator ProcessCoroutine(glTF gltf, IStorage storage) + { + ProcessOnAnyThread(gltf, storage); + yield return ProcessOnMainThreadCoroutine(gltf); + } + + public void ProcessOnAnyThread(glTF gltf, IStorage storage) + { + m_textureLoader.ProcessOnAnyThread(gltf, storage); + } + + public IEnumerator ProcessOnMainThreadCoroutine(glTF gltf) + { + using (m_textureLoader) + { + var textureType = TextureIO.GetglTFTextureType(gltf, m_textureIndex); + var colorSpace = TextureIO.GetColorSpace(textureType); + var isLinear = colorSpace == RenderTextureReadWrite.Linear; + yield return m_textureLoader.ProcessOnMainThread(isLinear); + TextureSamplerUtil.SetSampler(Texture, gltf.GetSamplerFromTextureIndex(m_textureIndex)); + } + } + #endregion + + struct ColorSpaceScope : IDisposable + { + bool m_sRGBWrite; + + public ColorSpaceScope(RenderTextureReadWrite colorSpace) + { + m_sRGBWrite = GL.sRGBWrite; + switch (colorSpace) + { + case RenderTextureReadWrite.Linear: + GL.sRGBWrite = false; + break; + + case RenderTextureReadWrite.sRGB: + default: + GL.sRGBWrite = true; + break; + } + } + public ColorSpaceScope(bool sRGBWrite) + { + m_sRGBWrite = GL.sRGBWrite; + GL.sRGBWrite = sRGBWrite; + } + + public void Dispose() + { + GL.sRGBWrite = m_sRGBWrite; + } + } + +#if UNITY_EDITOR && VRM_DEVELOP + [MenuItem("Assets/CopySRGBWrite", true)] + static bool CopySRGBWriteIsEnable() + { + return Selection.activeObject is Texture; + } + + [MenuItem("Assets/CopySRGBWrite")] + static void CopySRGBWrite() + { + CopySRGBWrite(true); + } + + [MenuItem("Assets/CopyNotSRGBWrite", true)] + static bool CopyNotSRGBWriteIsEnable() + { + return Selection.activeObject is Texture; + } + + [MenuItem("Assets/CopyNotSRGBWrite")] + static void CopyNotSRGBWrite() + { + CopySRGBWrite(false); + } + + static string AddPath(string path, string add) + { + return string.Format("{0}/{1}{2}{3}", + Path.GetDirectoryName(path), + Path.GetFileNameWithoutExtension(path), + add, + Path.GetExtension(path)); + } + + static void CopySRGBWrite(bool isSRGB) + { + var src = Selection.activeObject as Texture; + var texturePath = UnityPath.FromAsset(src); + + var path = EditorUtility.SaveFilePanel("save prefab", "Assets", + Path.GetFileNameWithoutExtension(AddPath(texturePath.FullPath, ".sRGB")), "prefab"); + var assetPath = UnityPath.FromFullpath(path); + if (!assetPath.IsUnderAssetsFolder) + { + return; + } + Debug.LogFormat("[CopySRGBWrite] {0} => {1}", texturePath, assetPath); + + var renderTexture = new RenderTexture(src.width, src.height, 0, + RenderTextureFormat.ARGB32, + RenderTextureReadWrite.sRGB); + using (var scope = new ColorSpaceScope(isSRGB)) + { + Graphics.Blit(src, renderTexture); + } + + var dst = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false, + RenderTextureReadWrite.sRGB == RenderTextureReadWrite.Linear); + dst.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0); + dst.Apply(); + + RenderTexture.active = null; + + assetPath.CreateAsset(dst); + + GameObject.DestroyImmediate(renderTexture); + } +#endif + + public static Texture2D CopyTexture(Texture src, RenderTextureReadWrite colorSpace, Material material) + { + Texture2D dst = null; + + var renderTexture = new RenderTexture(src.width, src.height, 0, RenderTextureFormat.ARGB32, colorSpace); + + using (var scope = new ColorSpaceScope(colorSpace)) + { + if (material != null) + { + Graphics.Blit(src, renderTexture, material); + } + else + { + Graphics.Blit(src, renderTexture); + } + } + + dst = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false, colorSpace == RenderTextureReadWrite.Linear); + dst.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0); + dst.name = src.name; + dst.Apply(); + + RenderTexture.active = null; + if (Application.isEditor) + { + GameObject.DestroyImmediate(renderTexture); + } + else + { + GameObject.Destroy(renderTexture); + } + return dst; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/TextureItem.cs.meta b/UniGLTF/Core/Scripts/IO/TextureItem.cs.meta new file mode 100644 index 000000000..880411948 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureItem.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 09cdeb503b1e8234faf528b6ae134e7a +timeCreated: 1519799583 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/TextureLoader.cs b/UniGLTF/Core/Scripts/IO/TextureLoader.cs new file mode 100644 index 000000000..e8e10fa71 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureLoader.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections; +using System.IO; +using UnityEngine; +using UnityEngine.Networking; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public interface ITextureLoader : IDisposable + { + Texture2D Texture { get; } + + /// + /// Call from any thread + /// + /// + /// + void ProcessOnAnyThread(glTF gltf, IStorage storage); + + /// + /// Call from unity main thread + /// + /// + /// + IEnumerator ProcessOnMainThread(bool isLinear); + } + +#if UNITY_EDITOR + public class AssetTextureLoader : ITextureLoader + { + public Texture2D Texture + { + private set; + get; + } + + UnityPath m_assetPath; + + public AssetTextureLoader(UnityPath assetPath, string _) + { + m_assetPath = assetPath; + } + + public void Dispose() + { + } + + public void ProcessOnAnyThread(glTF gltf, IStorage storage) + { + } + + public IEnumerator ProcessOnMainThread(bool isLinear) + { + // + // texture from assets + // + m_assetPath.ImportAsset(); + var importer = m_assetPath.GetImporter(); + if (importer == null) + { + Debug.LogWarningFormat("fail to get TextureImporter: {0}", m_assetPath); + } + importer.sRGBTexture = !isLinear; + importer.SaveAndReimport(); + + Texture = m_assetPath.LoadAsset(); + //Texture.name = m_textureName; + if (Texture == null) + { + Debug.LogWarningFormat("fail to Load Texture2D: {0}", m_assetPath); + } + + yield break; + } + } +#endif + + public class TextureLoader : ITextureLoader + { + int m_textureIndex; + public TextureLoader(int textureIndex) + { + m_textureIndex = textureIndex; + } + + public Texture2D Texture + { + private set; + get; + } + + public void Dispose() + { + } + + static Byte[] ToArray(ArraySegment bytes) + { + if (bytes.Array == null) + { + return new byte[] { }; + } + else if (bytes.Offset == 0 && bytes.Count == bytes.Array.Length) + { + return bytes.Array; + } + else + { + Byte[] result = new byte[bytes.Count]; + Buffer.BlockCopy(bytes.Array, bytes.Offset, result, 0, result.Length); + return result; + } + } + + Byte[] m_imageBytes; + string m_textureName; + public void ProcessOnAnyThread(glTF gltf, IStorage storage) + { + var imageIndex = gltf.GetImageIndexFromTextureIndex(m_textureIndex); + var segments = gltf.GetImageBytes(storage, imageIndex, out m_textureName); + m_imageBytes = ToArray(segments); + } + + public IEnumerator ProcessOnMainThread(bool isLinear) + { + // + // texture from image(png etc) bytes + // + Texture = new Texture2D(2, 2, TextureFormat.ARGB32, false, isLinear); + Texture.name = m_textureName; + if (m_imageBytes != null) + { + Texture.LoadImage(m_imageBytes); + } + yield break; + } + } + + public class UnityWebRequestTextureLoader : ITextureLoader + { + public Texture2D Texture + { + private set; + get; + } + + int m_textureIndex; + + public UnityWebRequestTextureLoader(int textureIndex) + { + m_textureIndex = textureIndex; + } + + UnityWebRequest m_uwr; + public void Dispose() + { + if (m_uwr != null) + { + m_uwr.Dispose(); + m_uwr = null; + } + } + + ArraySegment m_segments; + string m_textureName; + public void ProcessOnAnyThread(glTF gltf, IStorage storage) + { + var imageIndex = gltf.GetImageIndexFromTextureIndex(m_textureIndex); + m_segments = gltf.GetImageBytes(storage, imageIndex, out m_textureName); + } + +#if false + HttpHost m_http; + class HttpHost : IDisposable + { + TcpListener m_listener; + Socket m_connection; + + public HttpHost(int port) + { + m_listener = new TcpListener(IPAddress.Loopback, port); + m_listener.Start(); + m_listener.BeginAcceptSocket(OnAccepted, m_listener); + } + + void OnAccepted(IAsyncResult ar) + { + var l = ar.AsyncState as TcpListener; + if (l == null) return; + m_connection = l.EndAcceptSocket(ar); + // 次の接続受付はしない + + BeginRead(m_connection, new byte[8192]); + } + + void BeginRead(Socket c, byte[] buffer) + { + AsyncCallback callback = ar => + { + var s = ar.AsyncState as Socket; + if (s == null) return; + var size = s.EndReceive(ar); + if (size > 0) + { + OnRead(buffer, size); + } + BeginRead(s, buffer); + }; + m_connection.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, callback, m_connection); + } + + List m_buffer = new List(); + void OnRead(byte[] buffer, int len) + { + m_buffer.AddRange(buffer.Take(len)); + } + + public string Url + { + get + { + + } + } + + public void Dispose() + { + if (m_connection != null) + { + m_connection.Dispose(); + m_connection = null; + } + if(m_listener != null) + { + m_listener.Stop(); + m_listener = null; + } + } + } +#endif + + class Deleter : IDisposable + { + string m_path; + public Deleter(string path) + { + m_path = path; + } + public void Dispose() + { + if (File.Exists(m_path)) + { + File.Delete(m_path); + } + } + } + + public IEnumerator ProcessOnMainThread(bool isLinear) + { + // tmp file + var tmp = Path.GetTempFileName(); + using (var f = new FileStream(tmp, FileMode.Create)) + { + f.Write(m_segments.Array, m_segments.Offset, m_segments.Count); + } + + using (var d = new Deleter(tmp)) + { + var url = "file:///" + tmp.Replace("\\", "/"); + Debug.LogFormat("UnityWebRequest: {0}", url); + using (var m_uwr = new WWW(url)) + { + yield return m_uwr; + + // wait for request + while (!m_uwr.isDone) + { + yield return null; + } + + if (!string.IsNullOrEmpty(m_uwr.error)) + { + Debug.Log(m_uwr.error); + yield break; + } + + // Get downloaded asset bundle + Texture = m_uwr.textureNonReadable; + Texture.name = m_textureName; + } + } + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/TextureLoader.cs.meta b/UniGLTF/Core/Scripts/IO/TextureLoader.cs.meta new file mode 100644 index 000000000..a1aa6bbfe --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4ffd8b31d371e024593b9aff2cf2495b +timeCreated: 1540300073 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/TextureSamplerUtil.cs b/UniGLTF/Core/Scripts/IO/TextureSamplerUtil.cs new file mode 100644 index 000000000..00b3beea1 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureSamplerUtil.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + + +namespace UniGLTF +{ + public static class TextureSamplerUtil + { + #region WrapMode + public enum TextureWrapType + { + All, +#if UNITY_2017_1_OR_NEWER + U, + V, + W, +#endif + } + + public static KeyValuePair TypeWithMode(TextureWrapType type, TextureWrapMode mode) + { + return new KeyValuePair(type, mode); + } + + public static IEnumerable> GetUnityWrapMode(glTFTextureSampler sampler) + { +#if UNITY_2017_1_OR_NEWER + if (sampler.wrapS == sampler.wrapT) + { + switch (sampler.wrapS) + { + case glWrap.NONE: // default + yield return TypeWithMode(TextureWrapType.All, TextureWrapMode.Repeat); + break; + + case glWrap.CLAMP_TO_EDGE: + yield return TypeWithMode(TextureWrapType.All, TextureWrapMode.Clamp); + break; + + case glWrap.REPEAT: + yield return TypeWithMode(TextureWrapType.All, TextureWrapMode.Repeat); + break; + + case glWrap.MIRRORED_REPEAT: + yield return TypeWithMode(TextureWrapType.All, TextureWrapMode.Mirror); + break; + + default: + throw new NotImplementedException(); + } + } + else + { + switch (sampler.wrapS) + { + case glWrap.NONE: // default + yield return TypeWithMode(TextureWrapType.U, TextureWrapMode.Repeat); + break; + + case glWrap.CLAMP_TO_EDGE: + yield return TypeWithMode(TextureWrapType.U, TextureWrapMode.Clamp); + break; + + case glWrap.REPEAT: + yield return TypeWithMode(TextureWrapType.U, TextureWrapMode.Repeat); + break; + + case glWrap.MIRRORED_REPEAT: + yield return TypeWithMode(TextureWrapType.U, TextureWrapMode.Mirror); + break; + + default: + throw new NotImplementedException(); + } + switch (sampler.wrapT) + { + case glWrap.NONE: // default + yield return TypeWithMode(TextureWrapType.V, TextureWrapMode.Repeat); + break; + + case glWrap.CLAMP_TO_EDGE: + yield return TypeWithMode(TextureWrapType.V, TextureWrapMode.Clamp); + break; + + case glWrap.REPEAT: + yield return TypeWithMode(TextureWrapType.V, TextureWrapMode.Repeat); + break; + + case glWrap.MIRRORED_REPEAT: + yield return TypeWithMode(TextureWrapType.V, TextureWrapMode.Mirror); + break; + + default: + throw new NotImplementedException(); + } +#else + // Unity2017.1より前 + // * wrapSとwrapTの区別が無くてwrapしかない + // * Mirrorが無い + + switch (sampler.wrapS) + { + case glWrap.NONE: // default + yield return TypeWithMode(TextureWrapType.All, TextureWrapMode.Repeat); + break; + + case glWrap.CLAMP_TO_EDGE: + case glWrap.MIRRORED_REPEAT: + yield return TypeWithMode(TextureWrapType.All, TextureWrapMode.Clamp); + break; + + case glWrap.REPEAT: + yield return TypeWithMode(TextureWrapType.All, TextureWrapMode.Repeat); + break; + + default: + throw new NotImplementedException(); +#endif + } + } + #endregion + + public static FilterMode ImportFilterMode(glFilter filterMode) + { + switch (filterMode) + { + case glFilter.NEAREST: + case glFilter.NEAREST_MIPMAP_LINEAR: + case glFilter.NEAREST_MIPMAP_NEAREST: + return FilterMode.Point; + + case glFilter.NONE: + case glFilter.LINEAR: + case glFilter.LINEAR_MIPMAP_NEAREST: + return FilterMode.Bilinear; + + case glFilter.LINEAR_MIPMAP_LINEAR: + return FilterMode.Trilinear; + + default: + throw new NotImplementedException(); + + } + } + + public static void SetSampler(Texture2D texture, glTFTextureSampler sampler) + { + if (texture == null) + { + return; + } + + foreach (var kv in GetUnityWrapMode(sampler)) + { + switch (kv.Key) + { + case TextureWrapType.All: + texture.wrapMode = kv.Value; + break; + +#if UNITY_2017_1_OR_NEWER + case TextureWrapType.U: + texture.wrapModeU = kv.Value; + break; + + case TextureWrapType.V: + texture.wrapModeV = kv.Value; + break; + + case TextureWrapType.W: + texture.wrapModeW = kv.Value; + break; +#endif + + default: + throw new NotImplementedException(); + } + } + + texture.filterMode = ImportFilterMode(sampler.minFilter); + } + + #region Export + public static glFilter ExportFilterMode(Texture texture) + { + switch (texture.filterMode) + { + case FilterMode.Point: + return glFilter.NEAREST; + + case FilterMode.Bilinear: + return glFilter.LINEAR; + + case FilterMode.Trilinear: + return glFilter.LINEAR_MIPMAP_LINEAR; + + default: + throw new NotImplementedException(); + } + } + + public static TextureWrapMode GetWrapS(Texture texture) + { +#if UNITY_2017_1_OR_NEWER + return texture.wrapModeU; +#else + return texture.wrapMode; +#endif + } + + public static TextureWrapMode GetWrapT(Texture texture) + { +#if UNITY_2017_1_OR_NEWER + return texture.wrapModeV; +#else + return texture.wrapMode; +#endif + } + + public static glWrap ExportWrapMode(TextureWrapMode wrapMode) + { + switch (wrapMode) + { + case TextureWrapMode.Clamp: + return glWrap.CLAMP_TO_EDGE; + + case TextureWrapMode.Repeat: + return glWrap.REPEAT; + +#if UNITY_2017_1_OR_NEWER + case TextureWrapMode.Mirror: + case TextureWrapMode.MirrorOnce: + return glWrap.MIRRORED_REPEAT; +#endif + + default: + throw new NotImplementedException(); + } + } + + public static glTFTextureSampler Export(Texture texture) + { + var filter = ExportFilterMode(texture); + var wrapS = ExportWrapMode(GetWrapS(texture)); + var wrapT = ExportWrapMode(GetWrapT(texture)); + return new glTFTextureSampler + { + magFilter = filter, + minFilter = filter, + wrapS = wrapS, + wrapT = wrapT, + }; + } + #endregion + } +} diff --git a/UniGLTF/Core/Scripts/IO/TextureSamplerUtil.cs.meta b/UniGLTF/Core/Scripts/IO/TextureSamplerUtil.cs.meta new file mode 100644 index 000000000..f30e2fb67 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TextureSamplerUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3929edbda61f9346906bfab93411b98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/TriangleUtil.cs b/UniGLTF/Core/Scripts/IO/TriangleUtil.cs new file mode 100644 index 000000000..c77e806ac --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TriangleUtil.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using System.Collections.Generic; + + +public static class TriangleUtil +{ + public static IEnumerable FlipTriangle(IEnumerable src) + { + return FlipTriangle(src.Select(x => (Int32)x)); + } + + public static IEnumerable FlipTriangle(IEnumerable src) + { + return FlipTriangle(src.Select(x => (Int32)x)); + } + + public static IEnumerable FlipTriangle(IEnumerable src) + { + return FlipTriangle(src.Select(x => (Int32)x)); + } + + public static IEnumerable FlipTriangle(IEnumerable src) + { + var it = src.GetEnumerator(); + while (true) + { + if (!it.MoveNext()) + { + yield break; + } + var i0 = it.Current; + + if (!it.MoveNext()) + { + yield break; + } + var i1 = it.Current; + + if (!it.MoveNext()) + { + yield break; + } + var i2 = it.Current; + + yield return i2; + yield return i1; + yield return i0; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/TriangleUtil.cs.meta b/UniGLTF/Core/Scripts/IO/TriangleUtil.cs.meta new file mode 100644 index 000000000..5505bdb21 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/TriangleUtil.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 3d3209540c7379646b36b389bea94f6f +timeCreated: 1517037932 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/UnityPath.cs b/UniGLTF/Core/Scripts/IO/UnityPath.cs new file mode 100644 index 000000000..e54290920 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/UnityPath.cs @@ -0,0 +1,411 @@ +using System; +using System.IO; +using UnityEngine; +using System.Collections.Generic; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + /// + /// relative path from Unity project root. + /// For AssetDatabase. + /// + public struct UnityPath + { + #region UnityPath + public string Value + { + get; + private set; + } + + public override string ToString() + { + return string.Format("unity://{0}", Value); + } + + public bool IsNull + { + get { return Value == null; } + } + + public bool IsUnderAssetsFolder + { + get + { + if (IsNull) + { + return false; + } + return Value == "Assets" || Value.StartsWith("Assets/"); + } + } + + public string FileNameWithoutExtension + { + get { return Path.GetFileNameWithoutExtension(Value); } + } + + public string Extension + { + get { return Path.GetExtension(Value); } + } + + public UnityPath Parent + { + get + { + if (IsNull) + { + return default(UnityPath); + } + + return new UnityPath(Path.GetDirectoryName(Value)); + } + } + + public bool HasParent + { + get + { + return !string.IsNullOrEmpty(Value); + } + } + + static readonly char[] EscapeChars = new char[] + { + '\\', + '/', + ':', + '*', + '?', + '"', + '<', + '>', + '|', + }; + + static string EscapeFilePath(string path) + { + foreach (var x in EscapeChars) + { + path = path.Replace(x, '+'); + } + return path; + } + + public UnityPath Child(string name) + { + if (IsNull) + { + throw new NotImplementedException(); + } + else if (Value == "") + { + return new UnityPath(name); + } + else + { + return new UnityPath(Value + "/" + name); + } + } + + public override int GetHashCode() + { + if (IsNull) + { + return 0; + } + return Value.GetHashCode(); + } + + public override bool Equals(object obj) + { + if(obj is UnityPath) + { + var rhs = (UnityPath)obj; + if(Value==null && rhs.Value == null) + { + return true; + } + else if (Value == null) + { + return false; + } + else if (rhs.Value == null) + { + return false; + } + else + { + return Value == rhs.Value; + } + } + else + { + return false; + } + } + + /// + /// Remove extension and add suffix + /// + /// + /// + /// + public UnityPath GetAssetFolder(string suffix) + { + if (!IsUnderAssetsFolder) + { + throw new NotImplementedException(); + } + + return new UnityPath( + string.Format("{0}/{1}{2}", + Parent.Value, + FileNameWithoutExtension, + suffix + )); + } + + UnityPath(string value) + { + Value = value.Replace("\\", "/"); + } + + /// + /// + /// + /// Relative from unity current path. GetParent(Application.dataPath) + /// + public static UnityPath FromUnityPath(string unityPath) + { + if (String.IsNullOrEmpty(unityPath)) + { + return new UnityPath + { + Value="" + }; + } + return FromFullpath(Path.GetFullPath(unityPath)); + } + #endregion + + #region FullPath + static string s_basePath; + static string BaseFullPath + { + get + { + if (string.IsNullOrEmpty(s_basePath)) + { + s_basePath = Path.GetFullPath(Application.dataPath + "/..").Replace("\\", "/"); + } + return s_basePath; + } + } + + static string AssetFullPath + { + get + { + return BaseFullPath + "/Assets"; + } + } + + public string FullPath + { + get + { + if (IsNull) + { + throw new NotImplementedException(); + } + return Path.Combine(BaseFullPath, Value).Replace("\\", "/"); + } + } + + public bool IsFileExists + { + get { return File.Exists(FullPath); } + } + + public bool IsDirectoryExists + { + get { return Directory.Exists(FullPath); } + } + + /// + /// + /// + /// C:/path/to/file + /// + public static UnityPath FromFullpath(string fullPath) + { + if(fullPath == null) + { + fullPath = ""; + } + fullPath = fullPath.Replace("\\", "/"); + + if (fullPath == BaseFullPath) { + return new UnityPath + { + Value="" + }; + } + else if(fullPath.StartsWith(BaseFullPath + "/")) + { + return new UnityPath(fullPath.Substring(BaseFullPath.Length + 1)); + } + else + { + return default(UnityPath); + } + } + + public static bool IsUnderAssetFolder(string fullPath) + { + return fullPath.Replace("\\", "/").StartsWith(AssetFullPath); + } + #endregion + + public IEnumerable TravserseDir() + { + if (IsDirectoryExists) + { + yield return this; + + foreach(var child in ChildDirs) + { + foreach(var x in child.TravserseDir()) + { + yield return x; + } + } + } + } + + public IEnumerable ChildDirs + { + get + { + foreach(var x in Directory.GetDirectories(FullPath)) + { + yield return UnityPath.FromFullpath(x); + } + } + } + + public IEnumerable ChildFiles + { + get + { + foreach (var x in Directory.GetFiles(FullPath)) + { + yield return UnityPath.FromFullpath(x); + } + } + } + +#if UNITY_EDITOR + public T GetImporter() where T : AssetImporter + { + return AssetImporter.GetAtPath(Value) as T; + } + + public static UnityPath FromAsset(UnityEngine.Object asset) + { + return new UnityPath(AssetDatabase.GetAssetPath(asset)); + } + + public void ImportAsset() + { + if (!IsUnderAssetsFolder) + { + throw new NotImplementedException(); + } + AssetDatabase.ImportAsset(Value); + } + + public void EnsureFolder() + { + if (IsNull) + { + throw new NotImplementedException(); + } + + if (HasParent) + { + Parent.EnsureFolder(); + } + + if (!IsDirectoryExists) + { + var parent = Parent; + // ensure parent + parent.ImportAsset(); + // create + AssetDatabase.CreateFolder( + parent.Value, + Path.GetFileName(Value) + ); + ImportAsset(); + } + } + + public UnityEngine.Object[] GetSubAssets() + { + if (!IsUnderAssetsFolder) + { + throw new NotImplementedException(); + } + + return AssetDatabase.LoadAllAssetsAtPath(Value); + } + + public void CreateAsset(UnityEngine.Object o) + { + if (!IsUnderAssetsFolder) + { + throw new NotImplementedException(); + } + + AssetDatabase.CreateAsset(o, Value); + } + + public void AddObjectToAsset(UnityEngine.Object o) + { + if (!IsUnderAssetsFolder) + { + throw new NotImplementedException(); + } + + AssetDatabase.AddObjectToAsset(o, Value); + } + + public T LoadAsset() where T : UnityEngine.Object + { + if (!IsUnderAssetsFolder) + { + throw new NotImplementedException(); + } + + return AssetDatabase.LoadAssetAtPath(Value); + } + + public UnityPath GenerateUniqueAssetPath() + { + if (!IsUnderAssetsFolder) + { + throw new NotImplementedException(); + } + + return new UnityPath(AssetDatabase.GenerateUniqueAssetPath(Value)); + } + #endif + } +} diff --git a/UniGLTF/Core/Scripts/IO/UnityPath.cs.meta b/UniGLTF/Core/Scripts/IO/UnityPath.cs.meta new file mode 100644 index 000000000..f64923d8b --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/UnityPath.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7b7af908694806c469d62ce0b5b2f06a +timeCreated: 1532326996 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/ZipArchiveStorage.cs b/UniGLTF/Core/Scripts/IO/ZipArchiveStorage.cs new file mode 100644 index 000000000..317f745bd --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ZipArchiveStorage.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Linq; +using System.IO.Compression; +using System.Runtime.InteropServices; + +/// +/// https://en.wikipedia.org/wiki/Zip_(file_format) +/// +namespace UniGLTF.Zip +{ + + enum CompressionMethod : ushort + { + Stored = 0, // The file is stored (no compression) + Shrink = 1, // The file is Shrunk + Reduced1 = 2, // The file is Reduced with compression factor 1 + Reduced2 = 3, // The file is Reduced with compression factor 2 + Reduced3 = 4, // The file is Reduced with compression factor 3 + Reduced4 = 5, // The file is Reduced with compression factor 4 + Imploded = 6, // The file is Imploded + Reserved = 7, // Reserved for Tokenizing compression algorithm + Deflated = 8, // The file is Deflated + } + + class ZipParseException : Exception + { + public ZipParseException(string msg) : base(msg) + { } + } + + class EOCD + { + public ushort NumberOfThisDisk; + public ushort DiskWhereCentralDirectoryStarts; + public ushort NumberOfCentralDirectoryRecordsOnThisDisk; + public ushort TotalNumberOfCentralDirectoryRecords; + public int SizeOfCentralDirectoryBytes; + public int OffsetOfStartOfCentralDirectory; + public string Comment; + + public override string ToString() + { + return string.Format("", + NumberOfCentralDirectoryRecordsOnThisDisk, + OffsetOfStartOfCentralDirectory, + Comment + ); + } + + static int FindEOCD(byte[] bytes) + { + for (int i = bytes.Length - 22; i >= 0; --i) + { + if (bytes[i] == 0x50 + && bytes[i + 1] == 0x4b + && bytes[i + 2] == 0x05 + && bytes[i + 3] == 0x06) + { + return i; + } + } + + throw new ZipParseException("EOCD is not found"); + } + + public static EOCD Parse(Byte[] bytes) + { + var pos = FindEOCD(bytes); + using (var ms = new MemoryStream(bytes, pos, bytes.Length - pos, false)) + using (var r = new BinaryReader(ms)) + { + var sig = r.ReadInt32(); + if (sig != 0x06054b50) throw new ZipParseException("invalid eocd signature: " + sig); + + var eocd = new EOCD + { + NumberOfThisDisk = r.ReadUInt16(), + DiskWhereCentralDirectoryStarts = r.ReadUInt16(), + NumberOfCentralDirectoryRecordsOnThisDisk = r.ReadUInt16(), + TotalNumberOfCentralDirectoryRecords = r.ReadUInt16(), + SizeOfCentralDirectoryBytes = r.ReadInt32(), + OffsetOfStartOfCentralDirectory = r.ReadInt32(), + }; + + var commentLength = r.ReadUInt16(); + var commentBytes = r.ReadBytes(commentLength); + eocd.Comment = Encoding.ASCII.GetString(commentBytes); + + return eocd; + } + } + } + + abstract class CommonHeader + { + public Encoding Encoding = Encoding.UTF8; + public Byte[] Bytes; + public int Offset; + public abstract int Signature + { + get; + } + protected CommonHeader(Byte[] bytes, int offset) + { + var sig = BitConverter.ToInt32(bytes, offset); + if (sig != Signature) + { + throw new ZipParseException("invalid central directory file signature: " + sig); + } + Bytes = bytes; + Offset = offset; + + var start = offset + 4; + using (var ms = new MemoryStream(bytes, start, bytes.Length - start, false)) + using (var r = new BinaryReader(ms)) + { + ReadBefore(r); + Read(r); + ReadAfter(r); + } + } + + public UInt16 VersionNeededToExtract; + public UInt16 GeneralPurposeBitFlag; + public CompressionMethod CompressionMethod; + public UInt16 FileLastModificationTime; + public UInt16 FileLastModificationDate; + public Int32 CRC32; + public Int32 CompressedSize; + public Int32 UncompressedSize; + public UInt16 FileNameLength; + public UInt16 ExtraFieldLength; + + public abstract int FixedFieldLength + { + get; + } + + public abstract int Length + { + get; + } + + public string FileName + { + get + { + return Encoding.GetString(Bytes, + Offset + FixedFieldLength, + FileNameLength); + } + } + + public ArraySegment ExtraField + { + get + { + return new ArraySegment(Bytes, + Offset + FixedFieldLength + FileNameLength, + ExtraFieldLength); + } + } + + public override string ToString() + { + return string.Format("", + FileName, + CompressedSize, + UncompressedSize, + CompressionMethod + ); + } + + public abstract void ReadBefore(BinaryReader r); + + public void Read(BinaryReader r) + { + VersionNeededToExtract = r.ReadUInt16(); + GeneralPurposeBitFlag = r.ReadUInt16(); + CompressionMethod = (CompressionMethod)r.ReadUInt16(); + FileLastModificationTime = r.ReadUInt16(); + FileLastModificationDate = r.ReadUInt16(); + CRC32 = r.ReadInt32(); + CompressedSize = r.ReadInt32(); + UncompressedSize = r.ReadInt32(); + FileNameLength = r.ReadUInt16(); + ExtraFieldLength = r.ReadUInt16(); + } + + public abstract void ReadAfter(BinaryReader r); + } + + class CentralDirectoryFileHeader : CommonHeader + { + public override int Signature + { + get + { + return 0x02014b50; + } + } + + public CentralDirectoryFileHeader(Byte[] bytes, int offset) : base(bytes, offset) { } + + public UInt16 VersionMadeBy; + public UInt16 FileCommentLength; + public UInt16 DiskNumberWhereFileStarts; + public UInt16 InternalFileAttributes; + public Int32 ExternalFileAttributes; + public Int32 RelativeOffsetOfLocalFileHeader; + + public override int FixedFieldLength + { + get + { + return 46; + } + } + + public string FileComment + { + get + { + return Encoding.GetString(Bytes, + Offset + 46 + FileNameLength + ExtraFieldLength, + FileCommentLength); + } + } + + public override int Length + { + get + { + return FixedFieldLength + FileNameLength + ExtraFieldLength + FileCommentLength; + } + } + + public override void ReadBefore(BinaryReader r) + { + VersionMadeBy = r.ReadUInt16(); + } + + public override void ReadAfter(BinaryReader r) + { + FileCommentLength = r.ReadUInt16(); + DiskNumberWhereFileStarts = r.ReadUInt16(); + InternalFileAttributes = r.ReadUInt16(); + ExternalFileAttributes = r.ReadInt32(); + RelativeOffsetOfLocalFileHeader = r.ReadInt32(); + } + } + + class LocalFileHeader : CommonHeader + { + public override int FixedFieldLength + { + get + { + return 30; + } + } + + public override int Signature + { + get + { + return 0x04034b50; + } + } + + public override int Length + { + get + { + return FixedFieldLength + FileNameLength + ExtraFieldLength; + } + } + + public LocalFileHeader(Byte[] bytes, int offset) : base(bytes, offset) + { + } + + public override void ReadBefore(BinaryReader r) + { + } + + public override void ReadAfter(BinaryReader r) + { + } + } + + class ZipArchiveStorage : IStorage + { + public override string ToString() + { + return string.Format("", String.Join("", Entries.Select(x => x.ToString() + "\n").ToArray())); + } + + public List Entries = new List(); + + public static ZipArchiveStorage Parse(byte[] bytes) + { + var eocd = EOCD.Parse(bytes); + var archive = new ZipArchiveStorage(); + + var pos = eocd.OffsetOfStartOfCentralDirectory; + for (int i = 0; i < eocd.NumberOfCentralDirectoryRecordsOnThisDisk; ++i) + { + var file = new CentralDirectoryFileHeader(bytes, pos); + archive.Entries.Add(file); + pos += file.Length; + } + + return archive; + } + + public Byte[] Extract(CentralDirectoryFileHeader header) + { + var local = new LocalFileHeader(header.Bytes, header.RelativeOffsetOfLocalFileHeader); + var pos = local.Offset + local.Length; + + var dst = new Byte[local.UncompressedSize]; + +#if true + using (var s = new MemoryStream(header.Bytes, pos, local.CompressedSize, false)) + using (var deflateStream = new DeflateStream(s, CompressionMode.Decompress)) + { + int dst_pos = 0; + for (int remain = dst.Length; remain > 0;) + { + var readSize = deflateStream.Read(dst, dst_pos, remain); + dst_pos += readSize; + remain -= readSize; + } + } +#else + var size=RawInflate.RawInflateImport.RawInflate(dst, 0, dst.Length, + header.Bytes, pos, header.CompressedSize); +#endif + + return dst; + } + + public string ExtractToString(CentralDirectoryFileHeader header, Encoding encoding) + { + var local = new LocalFileHeader(header.Bytes, header.RelativeOffsetOfLocalFileHeader); + var pos = local.Offset + local.Length; + + using (var s = new MemoryStream(header.Bytes, pos, local.CompressedSize, false)) + using (var deflateStream = new DeflateStream(s, CompressionMode.Decompress)) + using (var r = new StreamReader(deflateStream, encoding)) + { + return r.ReadToEnd(); + } + } + + public ArraySegment Get(string url) + { + var found = Entries.FirstOrDefault(x => x.FileName == url); + if (found == null) + { + throw new FileNotFoundException("[ZipArchive]" + url); + } + + switch (found.CompressionMethod) + { + case CompressionMethod.Deflated: + return new ArraySegment(Extract(found)); + + case CompressionMethod.Stored: + return new ArraySegment(found.Bytes, found.RelativeOffsetOfLocalFileHeader, found.CompressedSize); + } + + throw new NotImplementedException(found.CompressionMethod.ToString()); + } + + public string GetPath(string url) + { + return null; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/ZipArchiveStorage.cs.meta b/UniGLTF/Core/Scripts/IO/ZipArchiveStorage.cs.meta new file mode 100644 index 000000000..303fb3c2b --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/ZipArchiveStorage.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b5aadac20fc53d04abe0492399479ce5 +timeCreated: 1528580594 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/glbImporter.cs b/UniGLTF/Core/Scripts/IO/glbImporter.cs new file mode 100644 index 000000000..68acce1bf --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/glbImporter.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + + +namespace UniGLTF +{ + public static class glbImporter + { + public const string GLB_MAGIC = "glTF"; + public const float GLB_VERSION = 2.0f; + + public static GlbChunkType ToChunkType(string src) + { + switch(src) + { + case "BIN": + return GlbChunkType.BIN; + + case "JSON": + return GlbChunkType.JSON; + + default: + throw new FormatException("unknown chunk type: " + src); + } + } + + public static List ParseGlbChanks(Byte[] bytes) + { + if (bytes.Length == 0) + { + throw new Exception("empty bytes"); + } + + int pos = 0; + if (Encoding.ASCII.GetString(bytes, 0, 4) != GLB_MAGIC) + { + throw new Exception("invalid magic"); + } + pos += 4; + + var version = BitConverter.ToUInt32(bytes, pos); + if (version != GLB_VERSION) + { + Debug.LogWarningFormat("unknown version: {0}", version); + return null; + } + pos += 4; + + //var totalLength = BitConverter.ToUInt32(bytes, pos); + pos += 4; + + var chunks = new List(); + while (pos < bytes.Length) + { + var chunkDataSize = BitConverter.ToInt32(bytes, pos); + pos += 4; + + //var type = (GlbChunkType)BitConverter.ToUInt32(bytes, pos); + var chunkTypeBytes = bytes.Skip(pos).Take(4).Where(x => x != 0).ToArray(); + var chunkTypeStr = Encoding.ASCII.GetString(chunkTypeBytes); + var type = ToChunkType(chunkTypeStr); + pos += 4; + + chunks.Add(new GlbChunk + { + ChunkType = type, + Bytes = new ArraySegment(bytes, (int)pos, (int)chunkDataSize) + }); + + pos += chunkDataSize; + } + + return chunks; + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/glbImporter.cs.meta b/UniGLTF/Core/Scripts/IO/glbImporter.cs.meta new file mode 100644 index 000000000..4246fe475 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/glbImporter.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 4a51e78bfa249be4c88e7612ed05eb6f +timeCreated: 1514252170 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/gltfExporter.cs b/UniGLTF/Core/Scripts/IO/gltfExporter.cs new file mode 100644 index 000000000..ec1a28474 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/gltfExporter.cs @@ -0,0 +1,365 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UniGLTF +{ + public class gltfExporter : IDisposable + { + const string CONVERT_HUMANOID_KEY = UniGLTFVersion.UNIGLTF_VERSION + "/Export"; + +#if UNITY_EDITOR + [MenuItem(CONVERT_HUMANOID_KEY, true, 1)] + private static bool ExportValidate() + { + return Selection.activeObject != null && Selection.activeObject is GameObject; + } + + [MenuItem(CONVERT_HUMANOID_KEY, false, 1)] + private static void ExportFromMenu() + { + var go = Selection.activeObject as GameObject; + var path = EditorUtility.SaveFilePanel( + "Save glb", + "", + go.name + ".glb", + "glb"); + if (string.IsNullOrEmpty(path)) + { + return; + } + + var gltf = new glTF(); + using (var exporter = new gltfExporter(gltf)) + { + exporter.Prepare(go); + exporter.Export(); + } + var bytes = gltf.ToGlbBytes(); + File.WriteAllBytes(path, bytes); + + if (path.StartsWithUnityAssetPath()) + { + AssetDatabase.ImportAsset(path.ToUnityRelativePath()); + AssetDatabase.Refresh(); + } + } +#endif + + glTF glTF; + + public bool UseSparseAccessorForBlendShape + { + get; + set; + } + + public GameObject Copy + { + get; + protected set; + } + + public List Meshes + { + get; + private set; + } + + public List Nodes + { + get; + private set; + } + + public List Materials + { + get; + private set; + } + + public TextureExportManager TextureManager; + + protected virtual IMaterialExporter CreateMaterialExporter() + { + return new MaterialExporter(); + } + + /// + /// このエクスポーターがサポートするExtension + /// + protected virtual IEnumerable ExtensionUsed + { + get + { + yield return glTF_KHR_materials_unlit.ExtensionName; + } + } + + public gltfExporter(glTF gltf) + { + glTF = gltf; + + glTF.extensionsUsed.AddRange(ExtensionUsed); + + glTF.asset = new glTFAssets + { + generator = "UniGLTF-" + UniGLTFVersion.VERSION, + version = "2.0", + }; + } + + public static glTF Export(GameObject go) + { + var gltf = new glTF(); + using (var exporter = new gltfExporter(gltf)) + { + exporter.Prepare(go); + exporter.Export(); + } + return gltf; + } + + public virtual void Prepare(GameObject go) + { + // コピーを作って、Z軸を反転することで左手系を右手系に変換する + Copy = GameObject.Instantiate(go); + Copy.transform.ReverseZRecursive(); + } + + public void Export() + { + FromGameObject(glTF, Copy, UseSparseAccessorForBlendShape); + } + + public void Dispose() + { + if (Application.isEditor) + { + GameObject.DestroyImmediate(Copy); + } + else + { + GameObject.Destroy(Copy); + } + } + + #region Export + static glTFNode ExportNode(Transform x, List nodes, List meshes, List skins) + { + var node = new glTFNode + { + name = x.name, + children = x.transform.GetChildren().Select(y => nodes.IndexOf(y)).ToArray(), + rotation = x.transform.localRotation.ToArray(), + translation = x.transform.localPosition.ToArray(), + scale = x.transform.localScale.ToArray(), + }; + + if (x.gameObject.activeInHierarchy) + { + var meshFilter = x.GetComponent(); + if (meshFilter != null) + { + node.mesh = meshes.IndexOf(meshFilter.sharedMesh); + } + + var skinnredMeshRenderer = x.GetComponent(); + if (skinnredMeshRenderer != null) + { + node.mesh = meshes.IndexOf(skinnredMeshRenderer.sharedMesh); + node.skin = skins.IndexOf(skinnredMeshRenderer); + } + } + + return node; + } + + void FromGameObject(glTF gltf, GameObject go, bool useSparseAccessorForMorphTarget = false) + { + var bytesBuffer = new ArrayByteBuffer(new byte[50 * 1024 * 1024]); + var bufferIndex = gltf.AddBuffer(bytesBuffer); + + GameObject tmpParent = null; + if (go.transform.childCount == 0) + { + tmpParent = new GameObject("tmpParent"); + go.transform.SetParent(tmpParent.transform, true); + go = tmpParent; + } + + try + { + + Nodes = go.transform.Traverse() + .Skip(1) // exclude root object for the symmetry with the importer + .ToList(); + + #region Materials and Textures + Materials = Nodes.SelectMany(x => x.GetSharedMaterials()).Where(x => x != null).Distinct().ToList(); + var unityTextures = Materials.SelectMany(x => TextureIO.GetTextures(x)).Where(x => x.Texture != null).Distinct().ToList(); + + TextureManager = new TextureExportManager(unityTextures.Select(x => x.Texture)); + + var materialExporter = CreateMaterialExporter(); + gltf.materials = Materials.Select(x => materialExporter.ExportMaterial(x, TextureManager)).ToList(); + + for (int i = 0; i < unityTextures.Count; ++i) + { + var unityTexture = unityTextures[i]; + TextureIO.ExportTexture(gltf, bufferIndex, TextureManager.GetExportTexture(i), unityTexture.TextureType); + } + #endregion + + + #region Meshes + var unityMeshes = Nodes + .Select(x => new MeshWithRenderer + { + Mesh = x.GetSharedMesh(), + Rendererer = x.GetComponent(), + }) + .Where(x => + { + if (x.Mesh == null) + { + return false; + } + if (x.Rendererer.sharedMaterials == null + || x.Rendererer.sharedMaterials.Length == 0) + { + return false; + } + + return true; + }) + .ToList(); + MeshExporter.ExportMeshes(gltf, bufferIndex, unityMeshes, Materials, useSparseAccessorForMorphTarget); + Meshes = unityMeshes.Select(x => x.Mesh).ToList(); + #endregion + + #region Skins + var unitySkins = Nodes + .Select(x => x.GetComponent()).Where(x => + x != null + && x.bones != null + && x.bones.Length > 0) + .ToList(); + gltf.nodes = Nodes.Select(x => ExportNode(x, Nodes, unityMeshes.Select(y => y.Mesh).ToList(), unitySkins)).ToList(); + gltf.scenes = new List + { + new gltfScene + { + nodes = go.transform.GetChildren().Select(x => Nodes.IndexOf(x)).ToArray(), + } + }; + + foreach (var x in unitySkins) + { + var matrices = x.sharedMesh.bindposes.Select(y => y.ReverseZ()).ToArray(); + var accessor = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, matrices, glBufferTarget.NONE); + + var skin = new glTFSkin + { + inverseBindMatrices = accessor, + joints = x.bones.Select(y => Nodes.IndexOf(y)).ToArray(), + skeleton = Nodes.IndexOf(x.rootBone), + }; + var skinIndex = gltf.skins.Count; + gltf.skins.Add(skin); + + foreach (var z in Nodes.Where(y => y.Has(x))) + { + var nodeIndex = Nodes.IndexOf(z); + var node = gltf.nodes[nodeIndex]; + node.skin = skinIndex; + } + } + #endregion + +#if UNITY_EDITOR + #region Animations + + var clips = new List(); + var animator = go.GetComponent(); + var animation = go.GetComponent(); + if (animator != null) + { + clips = AnimationExporter.GetAnimationClips(animator); + } + else if (animation != null) + { + clips = AnimationExporter.GetAnimationClips(animation); + } + + if (clips.Any()) + { + foreach (AnimationClip clip in clips) + { + var animationWithCurve = AnimationExporter.Export(clip, go.transform, Nodes); + + foreach (var kv in animationWithCurve.SamplerMap) + { + var sampler = animationWithCurve.Animation.samplers[kv.Key]; + + var inputAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, kv.Value.Input); + sampler.input = inputAccessorIndex; + + var outputAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, kv.Value.Output); + sampler.output = outputAccessorIndex; + + // modify accessors + var outputAccessor = gltf.accessors[outputAccessorIndex]; + var channel = animationWithCurve.Animation.channels.First(x => x.sampler == kv.Key); + switch (glTFAnimationTarget.GetElementCount(channel.target.path)) + { + case 1: + outputAccessor.type = "SCALAR"; + //outputAccessor.count = ; + break; + case 3: + outputAccessor.type = "VEC3"; + outputAccessor.count /= 3; + break; + + case 4: + outputAccessor.type = "VEC4"; + outputAccessor.count /= 4; + break; + + default: + throw new NotImplementedException(); + } + } + animationWithCurve.Animation.name = clip.name; + gltf.animations.Add(animationWithCurve.Animation); + } + } + #endregion +#endif + } + finally + { + if (tmpParent != null) + { + tmpParent.transform.GetChild(0).SetParent(null); + if (Application.isPlaying) + { + GameObject.Destroy(tmpParent); + } + else + { + GameObject.DestroyImmediate(tmpParent); + } + } + } + } + #endregion + } +} diff --git a/UniGLTF/Core/Scripts/IO/gltfExporter.cs.meta b/UniGLTF/Core/Scripts/IO/gltfExporter.cs.meta new file mode 100644 index 000000000..5b972c8f4 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/gltfExporter.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: a82bf73539bdd2d4c81595b5108819ce +timeCreated: 1516612766 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/IO/gltfImporter.cs b/UniGLTF/Core/Scripts/IO/gltfImporter.cs new file mode 100644 index 000000000..84f4d183a --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/gltfImporter.cs @@ -0,0 +1,52 @@ +using System; +using UnityEngine; + + +namespace UniGLTF +{ + public static class gltfImporter + { + [Obsolete("Use ImporterContext.Load(path)")] + public static ImporterContext Load(string path) + { + var context = new ImporterContext(); + context.Load(path); + context.ShowMeshes(); + context.EnableUpdateWhenOffscreen(); + return context; + } + + [Obsolete("Use ImporterContext.Parse(path, bytes)")] + public static ImporterContext Parse(string path, Byte[] bytes) + { + var context = new ImporterContext(); + context.Load(path); + context.ShowMeshes(); + context.EnableUpdateWhenOffscreen(); + return context; + } + + [Obsolete("use ImporterContext.Load()")] + public static void Load(ImporterContext context) + { + context.Load(); + context.ShowMeshes(); + context.EnableUpdateWhenOffscreen(); + } + + public static void LoadVrmAsync(string path, Byte[] bytes, Action onLoaded, Action onError = null, bool show = true) + { + var context = new ImporterContext(); + context.Parse(path, bytes); + context.LoadAsync(() => + { + if (show) + { + context.ShowMeshes(); + } + onLoaded(context.Root); + }, + onError); + } + } +} diff --git a/UniGLTF/Core/Scripts/IO/gltfImporter.cs.meta b/UniGLTF/Core/Scripts/IO/gltfImporter.cs.meta new file mode 100644 index 000000000..c8cc5daf1 --- /dev/null +++ b/UniGLTF/Core/Scripts/IO/gltfImporter.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 31723631f70a88f47ba90c044e220849 +timeCreated: 1514252170 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps.meta b/UniGLTF/Core/Scripts/PreExportShaderProps.meta new file mode 100644 index 000000000..30855e0fe --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d9c97ad7f5bbcac489a47a2f34dfff00 +folderAsset: yes +timeCreated: 1533538930 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Standard.cs b/UniGLTF/Core/Scripts/PreExportShaderProps/Standard.cs new file mode 100644 index 000000000..bd2d5d122 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Standard.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + + +namespace UniGLTF.ShaderPropExporter +{ + public static partial class PreShaderPropExporter + { + [PreExportShader] + static KeyValuePair Standard + { + get + { + return new KeyValuePair( + "Standard", + new ShaderProps + { + Properties = new ShaderProperty[]{ +new ShaderProperty("_Color", ShaderPropertyType.Color) +,new ShaderProperty("_MainTex", ShaderPropertyType.TexEnv) +,new ShaderProperty("_Cutoff", ShaderPropertyType.Range) +,new ShaderProperty("_Glossiness", ShaderPropertyType.Range) +,new ShaderProperty("_GlossMapScale", ShaderPropertyType.Range) +,new ShaderProperty("_SmoothnessTextureChannel", ShaderPropertyType.Float) +,new ShaderProperty("_Metallic", ShaderPropertyType.Range) +,new ShaderProperty("_MetallicGlossMap", ShaderPropertyType.TexEnv) +,new ShaderProperty("_SpecularHighlights", ShaderPropertyType.Float) +,new ShaderProperty("_GlossyReflections", ShaderPropertyType.Float) +,new ShaderProperty("_BumpScale", ShaderPropertyType.Float) +,new ShaderProperty("_BumpMap", ShaderPropertyType.TexEnv) +,new ShaderProperty("_Parallax", ShaderPropertyType.Range) +,new ShaderProperty("_ParallaxMap", ShaderPropertyType.TexEnv) +,new ShaderProperty("_OcclusionStrength", ShaderPropertyType.Range) +,new ShaderProperty("_OcclusionMap", ShaderPropertyType.TexEnv) +,new ShaderProperty("_EmissionColor", ShaderPropertyType.Color) +,new ShaderProperty("_EmissionMap", ShaderPropertyType.TexEnv) +,new ShaderProperty("_DetailMask", ShaderPropertyType.TexEnv) +,new ShaderProperty("_DetailAlbedoMap", ShaderPropertyType.TexEnv) +,new ShaderProperty("_DetailNormalMapScale", ShaderPropertyType.Float) +,new ShaderProperty("_DetailNormalMap", ShaderPropertyType.TexEnv) +,new ShaderProperty("_UVSec", ShaderPropertyType.Float) +,new ShaderProperty("_Mode", ShaderPropertyType.Float) +,new ShaderProperty("_SrcBlend", ShaderPropertyType.Float) +,new ShaderProperty("_DstBlend", ShaderPropertyType.Float) +,new ShaderProperty("_ZWrite", ShaderPropertyType.Float) + + } + } + ); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Standard.cs.meta b/UniGLTF/Core/Scripts/PreExportShaderProps/Standard.cs.meta new file mode 100644 index 000000000..abf6b6c83 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Standard.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 54043e349b047bf4b8f127cd919a757d +timeCreated: 1533542890 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/UniGLTF_UniUnlit.cs b/UniGLTF/Core/Scripts/PreExportShaderProps/UniGLTF_UniUnlit.cs new file mode 100644 index 000000000..2e1731f57 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/UniGLTF_UniUnlit.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + + +namespace UniGLTF.ShaderPropExporter +{ + public static partial class PreShaderPropExporter + { + [PreExportShader] + static KeyValuePair UniGLTF_UniUnlit + { + get + { + return new KeyValuePair( + "UniGLTF/UniUnlit", + new ShaderProps + { + Properties = new ShaderProperty[]{ +new ShaderProperty("_MainTex", ShaderPropertyType.TexEnv) +,new ShaderProperty("_Color", ShaderPropertyType.Color) +,new ShaderProperty("_Cutoff", ShaderPropertyType.Range) +,new ShaderProperty("_BlendMode", ShaderPropertyType.Float) +,new ShaderProperty("_CullMode", ShaderPropertyType.Float) +,new ShaderProperty("_VColBlendMode", ShaderPropertyType.Float) +,new ShaderProperty("_SrcBlend", ShaderPropertyType.Float) +,new ShaderProperty("_DstBlend", ShaderPropertyType.Float) +,new ShaderProperty("_ZWrite", ShaderPropertyType.Float) + + } + } + ); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/UniGLTF_UniUnlit.cs.meta b/UniGLTF/Core/Scripts/PreExportShaderProps/UniGLTF_UniUnlit.cs.meta new file mode 100644 index 000000000..c1315f41d --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/UniGLTF_UniUnlit.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ad4b6b115b83ecd48a513f697afc95f0 +timeCreated: 1537860074 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Color.cs b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Color.cs new file mode 100644 index 000000000..25c5fbc92 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Color.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + + +namespace UniGLTF.ShaderPropExporter +{ + public static partial class PreShaderPropExporter + { + [PreExportShader] + static KeyValuePair Unlit_Color + { + get + { + return new KeyValuePair( + "Unlit/Color", + new ShaderProps + { + Properties = new ShaderProperty[]{ +new ShaderProperty("_Color", ShaderPropertyType.Color) + + } + } + ); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Color.cs.meta b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Color.cs.meta new file mode 100644 index 000000000..4f97a3c69 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Color.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4f91421c5846d5d48933d2ee4ffeeceb +timeCreated: 1535186213 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Texture.cs b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Texture.cs new file mode 100644 index 000000000..f969aca80 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Texture.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + + +namespace UniGLTF.ShaderPropExporter +{ + public static partial class PreShaderPropExporter + { + [PreExportShader] + static KeyValuePair Unlit_Texture + { + get + { + return new KeyValuePair( + "Unlit/Texture", + new ShaderProps + { + Properties = new ShaderProperty[]{ +new ShaderProperty("_MainTex", ShaderPropertyType.TexEnv) + + } + } + ); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Texture.cs.meta b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Texture.cs.meta new file mode 100644 index 000000000..83320688d --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Texture.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8273e1e61ad8e914baae94d06836f2ad +timeCreated: 1535186213 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent.cs b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent.cs new file mode 100644 index 000000000..d01e6a773 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + + +namespace UniGLTF.ShaderPropExporter +{ + public static partial class PreShaderPropExporter + { + [PreExportShader] + static KeyValuePair Unlit_Transparent + { + get + { + return new KeyValuePair( + "Unlit/Transparent", + new ShaderProps + { + Properties = new ShaderProperty[]{ +new ShaderProperty("_MainTex", ShaderPropertyType.TexEnv) + + } + } + ); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent.cs.meta b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent.cs.meta new file mode 100644 index 000000000..f64c65586 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b98a8ee8ca13abb43809305cc4e5571a +timeCreated: 1535186213 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent_Cutout.cs b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent_Cutout.cs new file mode 100644 index 000000000..d54b96ecb --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent_Cutout.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + + +namespace UniGLTF.ShaderPropExporter +{ + public static partial class PreShaderPropExporter + { + [PreExportShader] + static KeyValuePair Unlit_Transparent_Cutout + { + get + { + return new KeyValuePair( + "Unlit/Transparent Cutout", + new ShaderProps + { + Properties = new ShaderProperty[]{ +new ShaderProperty("_MainTex", ShaderPropertyType.TexEnv) +,new ShaderProperty("_Cutoff", ShaderPropertyType.Range) + + } + } + ); + } + } + } +} diff --git a/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent_Cutout.cs.meta b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent_Cutout.cs.meta new file mode 100644 index 000000000..5ffeb3f48 --- /dev/null +++ b/UniGLTF/Core/Scripts/PreExportShaderProps/Unlit_Transparent_Cutout.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 844490f13976543478d82efe28251941 +timeCreated: 1535186301 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/UniGLTFException.cs b/UniGLTF/Core/Scripts/UniGLTFException.cs new file mode 100644 index 000000000..732152c19 --- /dev/null +++ b/UniGLTF/Core/Scripts/UniGLTFException.cs @@ -0,0 +1,17 @@ +using System; + + +namespace UniGLTF +{ + public class UniGLTFException : Exception + { + public UniGLTFException(string fmt, params object[] args) : this(string.Format(fmt, args)) { } + public UniGLTFException(string msg) : base(msg) { } + } + + public class UniGLTFNotSupportedException : UniGLTFException + { + public UniGLTFNotSupportedException(string fmt, params object[] args) : this(string.Format(fmt, args)) { } + public UniGLTFNotSupportedException(string msg) : base(msg) { } + } +} diff --git a/UniGLTF/Core/Scripts/UniGLTFException.cs.meta b/UniGLTF/Core/Scripts/UniGLTFException.cs.meta new file mode 100644 index 000000000..2e76c7bab --- /dev/null +++ b/UniGLTF/Core/Scripts/UniGLTFException.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 34c865b003bc1f44abcdb95dc99979cf +timeCreated: 1520500184 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/UniGLTFVersion.cs b/UniGLTF/Core/Scripts/UniGLTFVersion.cs new file mode 100644 index 000000000..4ba87b3e9 --- /dev/null +++ b/UniGLTF/Core/Scripts/UniGLTFVersion.cs @@ -0,0 +1,11 @@ + +namespace UniGLTF +{ + public static partial class UniGLTFVersion + { + public const int MAJOR = 1; + public const int MINOR = 28; + + public const string VERSION = "1.28"; + } +} diff --git a/UniGLTF/Core/Scripts/UniGLTFVersion.cs.meta b/UniGLTF/Core/Scripts/UniGLTFVersion.cs.meta new file mode 100644 index 000000000..edbe6d45d --- /dev/null +++ b/UniGLTF/Core/Scripts/UniGLTFVersion.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: caf06572635c12c4cb85d6ee09ab8f5e +timeCreated: 1524584066 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Core/Scripts/UniGLTFVersion_partial.cs b/UniGLTF/Core/Scripts/UniGLTFVersion_partial.cs new file mode 100644 index 000000000..c67c54be7 --- /dev/null +++ b/UniGLTF/Core/Scripts/UniGLTFVersion_partial.cs @@ -0,0 +1,8 @@ + +namespace UniGLTF +{ + public static partial class UniGLTFVersion + { + public const string UNIGLTF_VERSION = "UniGLTF-" + VERSION; + } +} diff --git a/UniGLTF/Core/Scripts/UniGLTFVersion_partial.cs.meta b/UniGLTF/Core/Scripts/UniGLTFVersion_partial.cs.meta new file mode 100644 index 000000000..448a601c5 --- /dev/null +++ b/UniGLTF/Core/Scripts/UniGLTFVersion_partial.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 40d707af2901e3f4781e20d8326723e8 +timeCreated: 1524584066 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/DepthFirstScheduler b/UniGLTF/DepthFirstScheduler new file mode 160000 index 000000000..d0c62ba0f --- /dev/null +++ b/UniGLTF/DepthFirstScheduler @@ -0,0 +1 @@ +Subproject commit d0c62ba0fbef280af0ef5ec41963bef847185efb diff --git a/UniGLTF/DepthFirstScheduler.meta b/UniGLTF/DepthFirstScheduler.meta new file mode 100644 index 000000000..9b42ed2e9 --- /dev/null +++ b/UniGLTF/DepthFirstScheduler.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6a7b54da70f565d4e8b45f843a6ba139 +folderAsset: yes +timeCreated: 1535290899 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/LICENSE.md b/UniGLTF/LICENSE.md new file mode 100644 index 000000000..3299d454f --- /dev/null +++ b/UniGLTF/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 ousttrue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/UniGLTF/LICENSE.md.meta b/UniGLTF/LICENSE.md.meta new file mode 100644 index 000000000..fa87894c9 --- /dev/null +++ b/UniGLTF/LICENSE.md.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 43de50058826c4b42857c7eadcc32794 +timeCreated: 1517138576 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/README.md b/UniGLTF/README.md new file mode 100644 index 000000000..e6c712d03 --- /dev/null +++ b/UniGLTF/README.md @@ -0,0 +1,56 @@ +# UniGLTF + +[glTF](https://github.com/KhronosGroup/glTF) 2.0 importer and exporter for Unity 5.6 or later + +Improved material importer(UniGLTF-1.21) ! + +Below is imported from [DamagedHelmet](https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/DamagedHelmet). Using unity standard shader. + +![standard shader](doc/pbr_to_standard.png) + + +![duck](doc/duck.png) +![animation](doc/animation.gif) + +# License + +* [MIT license](LICENSE) + +# See also + +* https://github.com/ousttrue/UniGLTF/wiki + +# Sample Models + +* https://github.com/KhronosGroup/glTF-Sample-Models + +## Huge model required Unity2017.3 or later + +* [Mesh.IndexFormat(from 2017.3)](https://docs.unity3d.com/ScriptReference/Mesh-indexFormat.html) is required + +example. SciFiHelmet(70074vertices) + +![SciFiHelmet](doc/SciFiHelmet.png) + +# Download + +* https://github.com/ousttrue/UniGLTF/releases + +# Usage + +## Import as prefab + +* drop gltf folder or glb file into Assets folder + +![duck_prefab](doc/duck_prefab.png) + +or + +* editor mode +* menu [UniGLTF] - [Import] +* open gltf file(gltf, glb, zip) from out of Asset Folder + +## API + +* https://github.com/ousttrue/UniGLTF/wiki/Rutime-API + diff --git a/UniGLTF/README.md.meta b/UniGLTF/README.md.meta new file mode 100644 index 000000000..d2802f0df --- /dev/null +++ b/UniGLTF/README.md.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c97bd2ee06a25534f9947292d562aecf +timeCreated: 1517138583 +licenseType: Free +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Resources.meta b/UniGLTF/Resources.meta new file mode 100644 index 000000000..e07979f1d --- /dev/null +++ b/UniGLTF/Resources.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: afa37238f2d665b4aba57a3e1714150d +folderAsset: yes +timeCreated: 1528270766 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Resources/Shaders.meta b/UniGLTF/Resources/Shaders.meta new file mode 100644 index 000000000..0f72a33c5 --- /dev/null +++ b/UniGLTF/Resources/Shaders.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: cbb764710ae3737458696a3ab5a85d14 +folderAsset: yes +timeCreated: 1528269714 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Resources/Shaders/NormalMapDecoder.shader b/UniGLTF/Resources/Shaders/NormalMapDecoder.shader new file mode 100644 index 000000000..76b76f285 --- /dev/null +++ b/UniGLTF/Resources/Shaders/NormalMapDecoder.shader @@ -0,0 +1,54 @@ +Shader "UniGLTF/NormalMapDecoder" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + + fixed4 frag (v2f i) : SV_Target + { + half4 col = tex2D(_MainTex, i.uv); + + col.xyz = (UnpackNormal(col) + 1) * 0.5; + col.w = 1; + + return col; + } + ENDCG + } + } +} diff --git a/UniGLTF/Resources/Shaders/NormalMapDecoder.shader.meta b/UniGLTF/Resources/Shaders/NormalMapDecoder.shader.meta new file mode 100644 index 000000000..d3b21087a --- /dev/null +++ b/UniGLTF/Resources/Shaders/NormalMapDecoder.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 53762a37d0a403e42a4921e3e3b84915 +timeCreated: 1533558728 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Resources/Shaders/NormalMapEncoder.shader b/UniGLTF/Resources/Shaders/NormalMapEncoder.shader new file mode 100644 index 000000000..f46d324ff --- /dev/null +++ b/UniGLTF/Resources/Shaders/NormalMapEncoder.shader @@ -0,0 +1,57 @@ +Shader "UniGLTF/NormalMapEncoder" +{ + Properties + { + _MainTex("Texture", 2D) = "white" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + v2f vert(appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + sampler2D _MainTex; + + fixed4 frag(v2f i) : SV_Target + { + half4 col = tex2D(_MainTex, i.uv); + half4 normal; + normal.x = 1.0; + normal.y = col.y; + normal.z = 1.0; + normal.w = col.x; + + return normal; + } + ENDCG + } + } +} + diff --git a/UniGLTF/Resources/Shaders/NormalMapEncoder.shader.meta b/UniGLTF/Resources/Shaders/NormalMapEncoder.shader.meta new file mode 100644 index 000000000..d0ef10022 --- /dev/null +++ b/UniGLTF/Resources/Shaders/NormalMapEncoder.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 3e39586253f31b34f87fa7e133449b1e +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/Resources/Shaders/StandardVColor.shader b/UniGLTF/Resources/Shaders/StandardVColor.shader new file mode 100644 index 000000000..dc565e38b --- /dev/null +++ b/UniGLTF/Resources/Shaders/StandardVColor.shader @@ -0,0 +1,54 @@ +Shader "UniGLTF/StandardVColor" { + Properties { + _Color ("Color", Color) = (1,1,1,1) + _MainTex ("Albedo (RGB)", 2D) = "white" {} + _Glossiness ("Smoothness", Range(0,1)) = 0.5 + _Metallic ("Metallic", Range(0,1)) = 0.0 + } + SubShader { + Tags { "RenderType"="Opaque" } + LOD 200 + + CGPROGRAM + // Physically based Standard lighting model, and enable shadows on all light types + #pragma surface surf Standard fullforwardshadows vertex:vert + + // Use shader model 3.0 target, to get nicer looking lighting + #pragma target 3.0 + + sampler2D _MainTex; + + struct Input { + float2 uv_MainTex; + float4 v_Color; + }; + + half _Glossiness; + half _Metallic; + fixed4 _Color; + + // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. + // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. + // #pragma instancing_options assumeuniformscaling + UNITY_INSTANCING_CBUFFER_START(Props) + // put more per-instance properties here + UNITY_INSTANCING_CBUFFER_END + + void vert(inout appdata_full v, out Input o){ + UNITY_INITIALIZE_OUTPUT(Input, o); + o.v_Color = v.color; + } + + void surf (Input IN, inout SurfaceOutputStandard o) { + // Albedo comes from a texture tinted by color + fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; + o.Albedo = c.rgb * IN.v_Color.rgb; + // Metallic and smoothness come from slider variables + o.Metallic = _Metallic; + o.Smoothness = _Glossiness; + o.Alpha = c.a; + } + ENDCG + } + FallBack "Diffuse" +} diff --git a/UniGLTF/Resources/Shaders/StandardVColor.shader.meta b/UniGLTF/Resources/Shaders/StandardVColor.shader.meta new file mode 100644 index 000000000..2efb7376e --- /dev/null +++ b/UniGLTF/Resources/Shaders/StandardVColor.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5ef7bdb14a8f23043805e41692d10787 +timeCreated: 1528269709 +licenseType: Pro +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniHumanoid b/UniGLTF/UniHumanoid new file mode 160000 index 000000000..9af6c0d39 --- /dev/null +++ b/UniGLTF/UniHumanoid @@ -0,0 +1 @@ +Subproject commit 9af6c0d3995ca8886c1ad87429dd9f28c7b4375d diff --git a/UniGLTF/UniHumanoid.meta b/UniGLTF/UniHumanoid.meta new file mode 100644 index 000000000..faa303402 --- /dev/null +++ b/UniGLTF/UniHumanoid.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4a2d5bd651268e54b94357a8b49592b3 +folderAsset: yes +timeCreated: 1535290568 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniJSON b/UniGLTF/UniJSON new file mode 160000 index 000000000..c9b6606ac --- /dev/null +++ b/UniGLTF/UniJSON @@ -0,0 +1 @@ +Subproject commit c9b6606ac29e9bd890c32788cbea3e512a5ba6f6 diff --git a/UniGLTF/UniJSON.meta b/UniGLTF/UniJSON.meta new file mode 100644 index 000000000..90abbe9d3 --- /dev/null +++ b/UniGLTF/UniJSON.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a84b9d2b5106bf442973f7d3030ff4cc +folderAsset: yes +timeCreated: 1531503046 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniUnlit.meta b/UniGLTF/UniUnlit.meta new file mode 100644 index 000000000..7506b5211 --- /dev/null +++ b/UniGLTF/UniUnlit.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: efc9c56620a6dea44a8cd83c5897e058 +folderAsset: yes +timeCreated: 1535618674 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniUnlit/Editor.meta b/UniGLTF/UniUnlit/Editor.meta new file mode 100644 index 000000000..6d77c0824 --- /dev/null +++ b/UniGLTF/UniUnlit/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c1ce4da7404d095459d80006a8609a1c +folderAsset: yes +timeCreated: 1535618691 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniUnlit/Editor/UniUnlitEditor.cs b/UniGLTF/UniUnlit/Editor/UniUnlitEditor.cs new file mode 100644 index 000000000..afd11b718 --- /dev/null +++ b/UniGLTF/UniUnlit/Editor/UniUnlitEditor.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + + +namespace UniGLTF.UniUnlit +{ + public class UniUnlitEditor : ShaderGUI + { + private MaterialProperty _mainTex; + private MaterialProperty _color; + private MaterialProperty _cutoff; + private MaterialProperty _blendMode; + private MaterialProperty _cullMode; + private MaterialProperty _vColBlendMode; +// private MaterialProperty _srcBlend; +// private MaterialProperty _dstBlend; +// private MaterialProperty _zWrite; + + public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) + { + _mainTex = FindProperty(Utils.PropNameMainTex, properties); + _color = FindProperty(Utils.PropNameColor, properties); + _cutoff = FindProperty(Utils.PropNameCutoff, properties); + _blendMode = FindProperty(Utils.PropNameBlendMode, properties); + _cullMode = FindProperty(Utils.PropNameCullMode, properties); + _vColBlendMode = FindProperty(Utils.PropeNameVColBlendMode, properties); +// _srcBlend = FindProperty(PropNameSrcBlend, properties); +// _dstBlend = FindProperty(PropNameDstBlend, properties); +// _zWrite = FindProperty(PropNameZWrite, properties); + + var materials = materialEditor.targets.Select(x => x as Material).ToArray(); + + EditorGUI.BeginChangeCheck(); + { + DrawRenderingBox(materialEditor, materials); + DrawColorBox(materialEditor, materials); + DrawOptionsBox(materialEditor, materials); + } + EditorGUI.EndChangeCheck(); + } + + public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) + { + var blendMode = UniUnlitRenderMode.Opaque; + if (material.HasProperty(Utils.PropNameStandardShadersRenderMode)) // from Standard shader + { + blendMode = (UniUnlitRenderMode) Math.Min(2f, material.GetFloat(Utils.PropNameStandardShadersRenderMode)); + } + + // assigns UniUnlit's properties... + base.AssignNewShaderToMaterial(material, oldShader, newShader); + + // take over old value + material.SetFloat(Utils.PropNameBlendMode, (float) blendMode); + + Utils.ValidateProperties(material, isRenderModeChangedByUser: true); + } + + private void DrawRenderingBox(MaterialEditor materialEditor, Material[] materials) + { + EditorGUILayout.LabelField("Rendering", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical(GUI.skin.box); + { + if (PopupEnum("Rendering Type", _blendMode, materialEditor)) + { + ModeChanged(materials, isRenderModeChangedByUser: true); + } + if (PopupEnum("Cull Mode", _cullMode, materialEditor)) + { + ModeChanged(materials, isRenderModeChangedByUser: true); + } + EditorGUILayout.Space(); + + switch ((UniUnlitRenderMode) _blendMode.floatValue) + { + case UniUnlitRenderMode.Cutout: + materialEditor.ShaderProperty(_cutoff, "Cutoff"); + break; + case UniUnlitRenderMode.Opaque: + case UniUnlitRenderMode.Transparent: + break; + } + } + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + } + + private void DrawColorBox(MaterialEditor materialEditor, Material[] materials) + { + EditorGUILayout.LabelField("Color", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical(GUI.skin.box); + { + materialEditor.TexturePropertySingleLine(new GUIContent("Main Tex", "(RGBA)"), _mainTex, _color); + materialEditor.TextureScaleOffsetProperty(_mainTex); + EditorGUILayout.Space(); + + if (PopupEnum("Vertex Color Blend Mode", _vColBlendMode, materialEditor)) + { + ModeChanged(materials, isRenderModeChangedByUser: true); + } + } + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + } + + private void DrawOptionsBox(MaterialEditor materialEditor, Material[] materials) + { + EditorGUILayout.LabelField("Options", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical(GUI.skin.box); + { + #if UNITY_5_6_OR_NEWER +// materialEditor.EnableInstancingField(); + materialEditor.DoubleSidedGIField(); + #endif + materialEditor.RenderQueueField(); + } + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + } + + private static bool PopupEnum(string name, MaterialProperty property, MaterialEditor editor) where T : struct + { + EditorGUI.showMixedValue = property.hasMixedValue; + EditorGUI.BeginChangeCheck(); + var ret = EditorGUILayout.Popup(name, (int) property.floatValue, Enum.GetNames(typeof(T))); + var changed = EditorGUI.EndChangeCheck(); + if (changed) + { + editor.RegisterPropertyChangeUndo("EnumPopUp"); + property.floatValue = ret; + } + EditorGUI.showMixedValue = false; + return changed; + } + + + private static void ModeChanged(Material[] materials, bool isRenderModeChangedByUser = false) + { + foreach (var material in materials) + { + Utils.ValidateProperties(material, isRenderModeChangedByUser); + } + } + } +} diff --git a/UniGLTF/UniUnlit/Editor/UniUnlitEditor.cs.meta b/UniGLTF/UniUnlit/Editor/UniUnlitEditor.cs.meta new file mode 100644 index 000000000..7d35d3e62 --- /dev/null +++ b/UniGLTF/UniUnlit/Editor/UniUnlitEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4c70714358bb2fb4fa96ef08640763fd +timeCreated: 1514224771 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniUnlit/Resources.meta b/UniGLTF/UniUnlit/Resources.meta new file mode 100644 index 000000000..61659788a --- /dev/null +++ b/UniGLTF/UniUnlit/Resources.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9e133c6453ae1c54c931227ff64edd52 +folderAsset: yes +timeCreated: 1535618682 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniUnlit/Resources/UniUnlit.shader b/UniGLTF/UniUnlit/Resources/UniUnlit.shader new file mode 100644 index 000000000..b820efe4e --- /dev/null +++ b/UniGLTF/UniUnlit/Resources/UniUnlit.shader @@ -0,0 +1,97 @@ +Shader "UniGLTF/UniUnlit" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _Color ("Main Color", COLOR) = (1,1,1,1) + _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5 + + [HideInInspector] _BlendMode ("_BlendMode", Float) = 0.0 + [HideInInspector] _CullMode ("_CullMode", Float) = 2.0 + [HideInInspector] _VColBlendMode ("_VColBlendMode", Float) = 0.0 + [HideInInspector] _SrcBlend ("_SrcBlend", Float) = 1.0 + [HideInInspector] _DstBlend ("_DstBlend", Float) = 0.0 + [HideInInspector] _ZWrite ("_ZWrite", Float) = 1.0 + + // VertexColor + } + SubShader + { + Tags { "RenderType"="Opaque" } + LOD 100 + + Pass + { + Cull [_CullMode] + Blend [_SrcBlend] [_DstBlend] + ZWrite [_ZWrite] + ZTest LEqual + BlendOp Add, Max + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma multi_compile_fog + #pragma multi_compile _ _ALPHATEST_ON _ALPHABLEND_ON + #pragma multi_compile _ _VERTEXCOL_MUL + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + #if defined(_VERTEXCOL_MUL) + fixed4 color : COLOR; + #endif + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float2 uv : TEXCOORD0; + UNITY_FOG_COORDS(1) + #if defined(_VERTEXCOL_MUL) + fixed4 color : COLOR; + #endif + }; + + sampler2D _MainTex; + float4 _MainTex_ST; + half4 _Color; + half _Cutoff; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = TRANSFORM_TEX(v.uv, _MainTex); + UNITY_TRANSFER_FOG(o,o.vertex); + + #if defined(_VERTEXCOL_MUL) + o.color = v.color; + #endif + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + fixed4 col = tex2D(_MainTex, i.uv) * _Color; + + #if defined(_VERTEXCOL_MUL) + col *= i.color; + #endif + + #if defined(_ALPHATEST_ON) + clip(col.a - _Cutoff); + #endif + + UNITY_APPLY_FOG(i.fogCoord, col); + return col; + } + ENDCG + } + } + CustomEditor "UniGLTF.UniUnlit.UniUnlitEditor" + Fallback "Unlit/Texture" +} diff --git a/UniGLTF/UniUnlit/Resources/UniUnlit.shader.meta b/UniGLTF/UniUnlit/Resources/UniUnlit.shader.meta new file mode 100644 index 000000000..750c8aba0 --- /dev/null +++ b/UniGLTF/UniUnlit/Resources/UniUnlit.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 8c17b56f4bf084c47872edcb95237e4a +timeCreated: 1535616926 +licenseType: Free +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniUnlit/Scripts.meta b/UniGLTF/UniUnlit/Scripts.meta new file mode 100644 index 000000000..183e72bc5 --- /dev/null +++ b/UniGLTF/UniUnlit/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b19f3d26eec098d46a33d8d35ba2eab5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/UniUnlit/Scripts/Utils.cs b/UniGLTF/UniUnlit/Scripts/Utils.cs new file mode 100644 index 000000000..d32d4f490 --- /dev/null +++ b/UniGLTF/UniUnlit/Scripts/Utils.cs @@ -0,0 +1,139 @@ +using UnityEngine; +using UnityEngine.Rendering; + +namespace UniGLTF.UniUnlit +{ + public enum UniUnlitRenderMode + { + Opaque = 0, + Cutout = 1, + Transparent = 2, + } + + public enum UniUnlitCullMode + { + Off = 0, +// Front = 1, + Back = 2, + } + + public enum UniUnlitVertexColorBlendOp + { + None = 0, + Multiply = 1, + } + + public static class Utils + { + public const string PropNameMainTex = "_MainTex"; + public const string PropNameColor = "_Color"; + public const string PropNameCutoff = "_Cutoff"; + public const string PropNameBlendMode = "_BlendMode"; + public const string PropNameCullMode = "_CullMode"; + public const string PropeNameVColBlendMode = "_VColBlendMode"; + public const string PropNameSrcBlend = "_SrcBlend"; + public const string PropNameDstBlend = "_DstBlend"; + public const string PropNameZWrite = "_ZWrite"; + + public const string PropNameStandardShadersRenderMode = "_Mode"; + + public const string KeywordAlphaTestOn = "_ALPHATEST_ON"; + public const string KeywordAlphaBlendOn = "_ALPHABLEND_ON"; + public const string KeywordVertexColMul = "_VERTEXCOL_MUL"; + + public const string TagRenderTypeKey = "RenderType"; + public const string TagRenderTypeValueOpaque = "Opaque"; + public const string TagRenderTypeValueTransparentCutout = "TransparentCutout"; + public const string TagRenderTypeValueTransparent = "Transparent"; + + public static void SetRenderMode(Material material, UniUnlitRenderMode mode) + { + material.SetInt(PropNameBlendMode, (int)mode); + } + + public static void SetCullMode(Material material, UniUnlitCullMode mode) + { + material.SetInt(PropNameCullMode, (int) mode); + } + + public static UniUnlitRenderMode GetRenderMode(Material material) + { + return (UniUnlitRenderMode)material.GetInt(PropNameBlendMode); + } + + public static UniUnlitCullMode GetCullMode(Material material) + { + return (UniUnlitCullMode)material.GetInt(PropNameCullMode); + } + + /// + /// Validate target material's UniUnlitRenderMode, UniUnlitVertexColorBlendOp. + /// Set appropriate hidden properites & keywords. + /// This will change RenderQueue independent to UniUnlitRenderMode if isRenderModeChagedByUser is true. + /// + /// Target material + /// Is changed by user + public static void ValidateProperties(Material material, bool isRenderModeChangedByUser = false) + { + SetupBlendMode(material, (UniUnlitRenderMode)material.GetFloat(PropNameBlendMode), + isRenderModeChangedByUser); + SetupVertexColorBlendOp(material, (UniUnlitVertexColorBlendOp)material.GetFloat(PropeNameVColBlendMode)); + } + + private static void SetupBlendMode(Material material, UniUnlitRenderMode renderMode, + bool isRenderModeChangedByUser = false) + { + switch (renderMode) + { + case UniUnlitRenderMode.Opaque: + material.SetOverrideTag(TagRenderTypeKey, TagRenderTypeValueOpaque); + material.SetInt(PropNameSrcBlend, (int)BlendMode.One); + material.SetInt(PropNameDstBlend, (int)BlendMode.Zero); + material.SetInt(PropNameZWrite, 1); + SetKeyword(material, KeywordAlphaTestOn, false); + SetKeyword(material, KeywordAlphaBlendOn, false); + if (isRenderModeChangedByUser) material.renderQueue = -1; + break; + case UniUnlitRenderMode.Cutout: + material.SetOverrideTag(TagRenderTypeKey, TagRenderTypeValueTransparentCutout); + material.SetInt(PropNameSrcBlend, (int)BlendMode.One); + material.SetInt(PropNameDstBlend, (int)BlendMode.Zero); + material.SetInt(PropNameZWrite, 1); + SetKeyword(material, KeywordAlphaTestOn, true); + SetKeyword(material, KeywordAlphaBlendOn, false); + if (isRenderModeChangedByUser) material.renderQueue = (int)RenderQueue.AlphaTest; + break; + case UniUnlitRenderMode.Transparent: + material.SetOverrideTag(TagRenderTypeKey, TagRenderTypeValueTransparent); + material.SetInt(PropNameSrcBlend, (int)BlendMode.SrcAlpha); + material.SetInt(PropNameDstBlend, (int)BlendMode.OneMinusSrcAlpha); + material.SetInt(PropNameZWrite, 0); + SetKeyword(material, KeywordAlphaTestOn, false); + SetKeyword(material, KeywordAlphaBlendOn, true); + if (isRenderModeChangedByUser) material.renderQueue = (int)RenderQueue.Transparent; + break; + } + } + + private static void SetupVertexColorBlendOp(Material material, UniUnlitVertexColorBlendOp vColBlendOp) + { + switch (vColBlendOp) + { + case UniUnlitVertexColorBlendOp.None: + SetKeyword(material, KeywordVertexColMul, false); + break; + case UniUnlitVertexColorBlendOp.Multiply: + SetKeyword(material, KeywordVertexColMul, true); + break; + } + } + + private static void SetKeyword(Material mat, string keyword, bool required) + { + if (required) + mat.EnableKeyword(keyword); + else + mat.DisableKeyword(keyword); + } + } +} \ No newline at end of file diff --git a/UniGLTF/UniUnlit/Scripts/Utils.cs.meta b/UniGLTF/UniUnlit/Scripts/Utils.cs.meta new file mode 100644 index 000000000..0b5c8bc7b --- /dev/null +++ b/UniGLTF/UniUnlit/Scripts/Utils.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e96cbbd810384352a6799dd731533178 +timeCreated: 1537534399 \ No newline at end of file diff --git a/UniGLTF/doc.meta b/UniGLTF/doc.meta new file mode 100644 index 000000000..c689ff2de --- /dev/null +++ b/UniGLTF/doc.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 556adc3131466f64ca27f06ff624a0e7 +folderAsset: yes +timeCreated: 1517138576 +licenseType: Free +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/doc/SciFiHelmet.png b/UniGLTF/doc/SciFiHelmet.png new file mode 100644 index 000000000..b2a9513f3 Binary files /dev/null and b/UniGLTF/doc/SciFiHelmet.png differ diff --git a/UniGLTF/doc/SciFiHelmet.png.meta b/UniGLTF/doc/SciFiHelmet.png.meta new file mode 100644 index 000000000..3a4f9ed3c --- /dev/null +++ b/UniGLTF/doc/SciFiHelmet.png.meta @@ -0,0 +1,77 @@ +fileFormatVersion: 2 +guid: af8ae648cfa51a340ac40fe7462cab62 +timeCreated: 1517152626 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/doc/animation.gif b/UniGLTF/doc/animation.gif new file mode 100644 index 000000000..45eee3f91 Binary files /dev/null and b/UniGLTF/doc/animation.gif differ diff --git a/UniGLTF/doc/animation.gif.meta b/UniGLTF/doc/animation.gif.meta new file mode 100644 index 000000000..85387be4e --- /dev/null +++ b/UniGLTF/doc/animation.gif.meta @@ -0,0 +1,77 @@ +fileFormatVersion: 2 +guid: e92d02ac9e1e5ec4798e64701d980023 +timeCreated: 1517138583 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/doc/duck.png b/UniGLTF/doc/duck.png new file mode 100644 index 000000000..5d9f1f0a6 Binary files /dev/null and b/UniGLTF/doc/duck.png differ diff --git a/UniGLTF/doc/duck.png.meta b/UniGLTF/doc/duck.png.meta new file mode 100644 index 000000000..1525167b5 --- /dev/null +++ b/UniGLTF/doc/duck.png.meta @@ -0,0 +1,77 @@ +fileFormatVersion: 2 +guid: b34f47033ccb0ed47b5596fd0ebe7bcd +timeCreated: 1517138582 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/doc/duck_assets.png b/UniGLTF/doc/duck_assets.png new file mode 100644 index 000000000..6e137752b Binary files /dev/null and b/UniGLTF/doc/duck_assets.png differ diff --git a/UniGLTF/doc/duck_assets.png.meta b/UniGLTF/doc/duck_assets.png.meta new file mode 100644 index 000000000..c74e0a6b2 --- /dev/null +++ b/UniGLTF/doc/duck_assets.png.meta @@ -0,0 +1,77 @@ +fileFormatVersion: 2 +guid: 4b45d82a9b6f6f944a4e697c39a57fb8 +timeCreated: 1517138582 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/doc/duck_prefab.png b/UniGLTF/doc/duck_prefab.png new file mode 100644 index 000000000..84eebc4ff Binary files /dev/null and b/UniGLTF/doc/duck_prefab.png differ diff --git a/UniGLTF/doc/duck_prefab.png.meta b/UniGLTF/doc/duck_prefab.png.meta new file mode 100644 index 000000000..ce633eccb --- /dev/null +++ b/UniGLTF/doc/duck_prefab.png.meta @@ -0,0 +1,77 @@ +fileFormatVersion: 2 +guid: 8106b49706f1aaf49b8493e00d32820d +timeCreated: 1517138582 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/doc/metalroughness.png b/UniGLTF/doc/metalroughness.png new file mode 100644 index 000000000..026237a1c Binary files /dev/null and b/UniGLTF/doc/metalroughness.png differ diff --git a/UniGLTF/doc/metalroughness.png.meta b/UniGLTF/doc/metalroughness.png.meta new file mode 100644 index 000000000..a21e9fd08 --- /dev/null +++ b/UniGLTF/doc/metalroughness.png.meta @@ -0,0 +1,77 @@ +fileFormatVersion: 2 +guid: 9562eeb01fd74dd45a551d868c32b1e6 +timeCreated: 1520020171 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniGLTF/doc/pbr_to_standard.png b/UniGLTF/doc/pbr_to_standard.png new file mode 100644 index 000000000..8b6569d46 Binary files /dev/null and b/UniGLTF/doc/pbr_to_standard.png differ diff --git a/UniGLTF/doc/pbr_to_standard.png.meta b/UniGLTF/doc/pbr_to_standard.png.meta new file mode 100644 index 000000000..97234a347 --- /dev/null +++ b/UniGLTF/doc/pbr_to_standard.png.meta @@ -0,0 +1,68 @@ +fileFormatVersion: 2 +guid: 300c7a0839ab0e24bb25c7f268696f2e +timeCreated: 1537519011 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: