Merge pull request #774 from ousttrue/texture_importer

ImporterContextの整理
This commit is contained in:
ousttrue 2021-03-10 17:14:34 +09:00 committed by GitHub
commit a7c453bbd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1239 additions and 3114 deletions

View File

@ -1,249 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace UniGLTF
{
/// <summary>
/// Editor で Asset 化する場合専用
///
/// GameObject を prefab 化するので、prefab の元になった GameObject は破棄対象となる。
///
/// </summary>
public class EditorImporterContext : IDisposable
{
ImporterContext m_context;
public EditorImporterContext(ImporterContext context)
{
m_context = context;
}
public void Dispose()
{
m_context.Dispose();
if (m_context.Root != null)
{
// Destroy the GameObject that became the basis of Prefab
GameObject.DestroyImmediate(m_context.Root);
}
}
public virtual IEnumerable<UnityEngine.Object> ObjectsForSubAsset()
{
foreach (var x in m_context.TextureFactory.ObjectsForSubAsset())
{
yield return x;
}
foreach (var x in m_context.MaterialFactory.ObjectsForSubAsset())
{
yield return x;
}
foreach (var x in m_context.Meshes) { yield return x.Mesh; }
foreach (var x in m_context.AnimationClips) { yield return x; }
}
/// <summary>
/// Destroy assets that created ImporterContext. This function is clean up for importer error.
/// </summary>
public virtual void EditorDestroyRootAndAssets()
{
// Remove hierarchy
if (m_context.Root != null) GameObject.DestroyImmediate(m_context.Root);
// Remove resources. materials, textures meshes etc...
foreach (var o in ObjectsForSubAsset())
{
UnityEngine.Object.DestroyImmediate(o, true);
}
}
public virtual UnityPath GetAssetPath(UnityPath prefabPath, UnityEngine.Object o, bool meshAsSubAsset)
{
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 AvoidOverwriteAndLoad(UnityPath assetPath, UnityEngine.Object o)
{
if (o is Material)
{
var loaded = assetPath.LoadAsset<Material>();
// replace component reference
foreach (var mesh in m_context.Meshes)
{
foreach (var r in mesh.Renderers)
{
for (int i = 0; i < r.sharedMaterials.Length; ++i)
{
if (r.sharedMaterials.Contains(o))
{
r.sharedMaterials = r.sharedMaterials.Select(x => x == o ? loaded : x).ToArray();
}
}
}
}
return true;
}
return false;
}
public void SaveAsAsset(UnityPath prefabPath, bool meshAsSubAsset = false)
{
m_context.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<UnityPath>(){
prefabPath
};
foreach (var o in ObjectsForSubAsset())
{
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(o)))
{
// already exists
continue;
}
var assetPath = GetAssetPath(prefabPath, o, meshAsSubAsset);
if (!assetPath.IsNull)
{
if (assetPath.IsFileExists)
{
if (AvoidOverwriteAndLoad(assetPath, o))
{
// 上書きせずに既存のアセットからロードして置き換えた
continue;
}
}
// アセットとして書き込む
assetPath.Parent.EnsureFolder();
assetPath.CreateAsset(o);
paths.Add(assetPath);
}
else
{
// save as subasset
prefabPath.AddObjectToAsset(o);
}
}
// Create or update Main Asset
if (prefabPath.IsFileExists)
{
Debug.LogFormat("replace prefab: {0}", prefabPath);
var prefab = prefabPath.LoadAsset<GameObject>();
#if UNITY_2018_3_OR_NEWER
PrefabUtility.SaveAsPrefabAssetAndConnect(m_context.Root, prefabPath.Value, InteractionMode.AutomatedAction);
#else
PrefabUtility.ReplacePrefab(Root, prefab, ReplacePrefabOptions.ReplaceNameBased);
#endif
}
else
{
Debug.LogFormat("create prefab: {0}", prefabPath);
#if UNITY_2018_3_OR_NEWER
PrefabUtility.SaveAsPrefabAssetAndConnect(m_context.Root, prefabPath.Value, InteractionMode.AutomatedAction);
#else
PrefabUtility.CreatePrefab(prefabPath.Value, Root);
#endif
}
foreach (var x in paths)
{
x.ImportAsset();
}
}
/// <summary>
/// Extract images from glb or gltf out of Assets folder.
/// </summary>
/// <param name="prefabPath"></param>
public void ExtractImages(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 < m_context.GLTF.textures.Count; ++i)
{
folder.EnsureFolder();
var gltfTexture = m_context.GLTF.textures[i];
var gltfImage = m_context.GLTF.images[gltfTexture.source];
var src = m_context.Storage.GetPath(gltfImage.uri);
if (UnityPath.FromFullpath(src).IsUnderAssetsFolder)
{
// asset is exists.
}
else
{
var byteSegment = m_context.GLTF.GetImageBytes(m_context.Storage, gltfTexture.source);
var textureName = gltfTexture.name;
// path
var dst = folder.Child(textureName + gltfImage.GetExt());
File.WriteAllBytes(dst.FullPath, byteSegment.ToArray());
dst.ImportAsset();
// make relative path from PrefabParentDir
gltfImage.uri = dst.Value.Substring(prefabParentDir.Value.Length + 1);
++created;
}
}
if (created > 0)
{
AssetDatabase.Refresh();
}
// texture will load from assets
m_context.TextureFactory.ImageBaseDir = prefabParentDir;
}
}
}

View File

@ -1,55 +0,0 @@
#if false
using System.IO;
using UnityEditor;
using UnityEngine;
namespace UniGLTF
{
public static class ImporterMenu
{
[MenuItem(UniGLTFVersion.MENU + "/Import(gltf, glb)", priority = 20)]
public static void ImportMenu()
{
var path = EditorUtility.OpenFilePanel("open gltf", "", "gltf,glb");
if (string.IsNullOrEmpty(path))
{
return;
}
if (Application.isPlaying)
{
//
// load into scene
//
var parser = new GltfParser();
parser.ParsePath(path);
var context = new ImporterContext(parser);
context.Load();
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));
}
}
}
}
#endif

View File

@ -0,0 +1,16 @@
using UnityEngine;
namespace UniGLTF
{
public static class EditorAnimation
{
public static void OnGUIAnimation(GltfParser parser)
{
for (int i = 0; i < parser.GLTF.animations.Count; ++i)
{
var a = parser.GLTF.animations[i];
GUILayout.Label($"{i}: {a.name}");
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1c496f43ae12f9041a16af0489727587
guid: 005298fbae4759b4896174bb6e129e9d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
namespace UniGLTF
{
public static class EditorMaterial
{
class TmpGuiEnable : IDisposable
{
bool m_backup;
public TmpGuiEnable(bool enable)
{
m_backup = GUI.enabled;
GUI.enabled = enable;
}
public void Dispose()
{
GUI.enabled = m_backup;
}
}
static bool s_foldMaterials;
static bool s_foldTextures;
public static void OnGUIMaterial(ScriptedImporter importer, GltfParser parser)
{
var canExtract = !importer.GetExternalObjectMap().Any(x => x.Value is Material || x.Value is Texture2D);
using (new TmpGuiEnable(canExtract))
{
if (GUILayout.Button("Extract Materials And Textures ..."))
{
ExtractMaterialsAndTextures(importer);
}
}
//
// Draw ExternalObjectMap
//
s_foldMaterials = EditorGUILayout.Foldout(s_foldMaterials, "Remapped Materials");
if (s_foldMaterials)
{
DrawRemapGUI<UnityEngine.Material>(importer, parser.GLTF.materials.Select(x => x.name));
}
s_foldTextures = EditorGUILayout.Foldout(s_foldTextures, "Remapped Textures");
if (s_foldTextures)
{
DrawRemapGUI<UnityEngine.Texture2D>(importer, parser.EnumerateTextures().Select(x => x.ConvertedName));
}
if (GUILayout.Button("Clear"))
{
importer.ClearExternalObjects<UnityEngine.Material>();
importer.ClearExternalObjects<UnityEngine.Texture2D>();
}
}
static void DrawRemapGUI<T>(ScriptedImporter importer, IEnumerable<string> names) where T : UnityEngine.Object
{
EditorGUI.indentLevel++;
var map = importer.GetExternalObjectMap()
.Select(x => (x.Key.name, x.Value as T))
.Where(x => x.Item2 != null)
.ToDictionary(x => x.Item1, x => x.Item2)
;
foreach (var name in names)
{
if (string.IsNullOrEmpty(name))
{
throw new System.ArgumentNullException();
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(name);
map.TryGetValue(name, out T value);
var asset = EditorGUILayout.ObjectField(value, typeof(T), true) as T;
if (asset != value)
{
importer.SetExternalUnityObject(new AssetImporter.SourceAssetIdentifier(value), asset);
}
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel--;
}
public static void SetExternalUnityObject<T>(this ScriptedImporter self, UnityEditor.AssetImporter.SourceAssetIdentifier sourceAssetIdentifier, T obj) where T : UnityEngine.Object
{
self.AddRemap(sourceAssetIdentifier, obj);
AssetDatabase.WriteImportSettingsIfDirty(self.assetPath);
AssetDatabase.ImportAsset(self.assetPath, ImportAssetOptions.ForceUpdate);
}
static void ExtractMaterialsAndTextures(ScriptedImporter self)
{
if (string.IsNullOrEmpty(self.assetPath))
{
return;
}
Action<Texture2D> addRemap = externalObject =>
{
self.AddRemap(new AssetImporter.SourceAssetIdentifier(typeof(UnityEngine.Texture2D), externalObject.name), externalObject);
};
Action<IEnumerable<string>> onCompleted = _ =>
{
AssetDatabase.ImportAsset(self.assetPath, ImportAssetOptions.ForceUpdate);
self.ExtractMaterials();
AssetDatabase.ImportAsset(self.assetPath, ImportAssetOptions.ForceUpdate);
};
TextureExtractor.ExtractTextures(self.assetPath,
self.GetSubAssets<UnityEngine.Texture2D>(self.assetPath).ToArray(),
addRemap,
onCompleted
);
}
public static void ExtractMaterials(this ScriptedImporter importer)
{
if (string.IsNullOrEmpty(importer.assetPath))
{
return;
}
var path = $"{Path.GetDirectoryName(importer.assetPath)}/{Path.GetFileNameWithoutExtension(importer.assetPath)}.Materials";
var info = TextureExtractor.SafeCreateDirectory(path);
foreach (var asset in importer.GetSubAssets<Material>(importer.assetPath))
{
ExtractSubAsset(asset, $"{path}/{asset.name}.mat", false);
}
}
private static void ExtractSubAsset(UnityEngine.Object subAsset, string destinationPath, bool isForceUpdate)
{
string assetPath = AssetDatabase.GetAssetPath(subAsset);
var clone = UnityEngine.Object.Instantiate(subAsset);
AssetDatabase.CreateAsset(clone, destinationPath);
var assetImporter = AssetImporter.GetAtPath(assetPath);
assetImporter.AddRemap(new AssetImporter.SourceAssetIdentifier(clone), clone);
if (isForceUpdate)
{
AssetDatabase.WriteImportSettingsIfDirty(assetPath);
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
}
}
}
}

View File

@ -1,7 +1,5 @@
fileFormatVersion: 2
guid: ae2833922b06981439883d1e924b4cd0
timeCreated: 1517153624
licenseType: Free
guid: d87d4a592573eb048916575e202af37f
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,25 @@
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
namespace UniGLTF
{
[ScriptedImporter(1, "glb")]
public class GlbScriptedImporter : ScriptedImporter
{
[SerializeField]
Axises m_reverseAxis = default;
public override void OnImportAsset(AssetImportContext ctx)
{
try
{
ScriptedImporterImpl.Import(this, ctx, m_reverseAxis);
}
catch (System.Exception ex)
{
Debug.LogError(ex);
}
}
}
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: 7bbb264b34e75dd438622e1f29f0f46c
timeCreated: 1517119659
licenseType: Free
guid: cc45016b844e7624dae3aec10fb443ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0

View File

@ -0,0 +1,49 @@
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
namespace UniGLTF
{
[CustomEditor(typeof(GlbScriptedImporter))]
public class GlbScriptedImporterEditorGUI : ScriptedImporterEditor
{
GlbScriptedImporter m_importer;
GltfParser m_parser;
public override void OnEnable()
{
m_importer = target as GlbScriptedImporter;
m_parser = new GltfParser();
m_parser.ParsePath(m_importer.assetPath);
}
enum Tabs
{
Model,
Animation,
Materials,
}
static Tabs s_currentTab;
public override void OnInspectorGUI()
{
s_currentTab = MeshUtility.TabBar.OnGUI(s_currentTab);
GUILayout.Space(10);
switch (s_currentTab)
{
case Tabs.Model:
base.OnInspectorGUI();
break;
case Tabs.Animation:
EditorAnimation.OnGUIAnimation(m_parser);
break;
case Tabs.Materials:
EditorMaterial.OnGUIMaterial(m_importer, m_parser);
break;
}
}
}
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: ff15446e31b14ac409251c931b7c2038
timeCreated: 1531893103
licenseType: Free
guid: 706590752e82d004e99da97aff535f67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0

View File

@ -1,102 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
namespace UniGLTF
{
[ScriptedImporter(1, "glb")]
[ScriptedImporter(1, "gltf")]
public class GltfScriptedImporter : ScriptedImporter
{
[SerializeField]
Axises m_reverseAxis = default;
const string TextureDirName = "Textures";
const string MaterialDirName = "Materials";
public override void OnImportAsset(AssetImportContext ctx)
{
Debug.Log("OnImportAsset to " + ctx.assetPath);
try
{
// Parse
var parser = new GltfParser();
parser.ParsePath(ctx.assetPath);
// Build Unity Model
var externalObjectMap = GetExternalObjectMap()
.Select(kv => (kv.Key.name, kv.Value))
;
var context = new ImporterContext(parser, null, externalObjectMap);
context.InvertAxis = m_reverseAxis;
context.Load();
context.ShowMeshes();
// Texture
foreach (var info in context.TextureFactory.Textures)
{
if (!info.IsUsed)
{
continue;
}
if (!info.IsExternal)
{
var texture = info.Texture;
ctx.AddObjectToAsset(texture.name, texture);
}
}
// Material
foreach (var info in context.MaterialFactory.Materials)
{
if (!info.UseExternal)
{
var material = info.Asset;
ctx.AddObjectToAsset(material.name, material);
}
}
// Mesh
foreach (var mesh in context.Meshes.Select(x => x.Mesh))
{
ctx.AddObjectToAsset(mesh.name, mesh);
}
// Animation
foreach (var clip in context.AnimationClips)
{
ctx.AddObjectToAsset(clip.name, clip);
}
// Root
ctx.AddObjectToAsset(context.Root.name, context.Root);
ctx.SetMainObject(context.Root);
ScriptedImporterImpl.Import(this, ctx, m_reverseAxis);
}
catch (System.Exception ex)
{
Debug.LogError(ex);
}
}
public void ExtractMaterialsAndTextures()
{
this.ExtractTextures(TextureDirName, () =>
{
this.ExtractAssets<UnityEngine.Material>(MaterialDirName, ".mat");
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
});
}
public void SetExternalUnityObject<T>(UnityEditor.AssetImporter.SourceAssetIdentifier sourceAssetIdentifier, T obj) where T : UnityEngine.Object
{
this.AddRemap(sourceAssetIdentifier, obj);
AssetDatabase.WriteImportSettingsIfDirty(this.assetPath);
AssetDatabase.ImportAsset(this.assetPath, ImportAssetOptions.ForceUpdate);
}
}
}

View File

@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
@ -40,98 +37,13 @@ namespace UniGLTF
break;
case Tabs.Animation:
OnGUIAnimation(m_importer, m_parser);
EditorAnimation.OnGUIAnimation(m_parser);
break;
case Tabs.Materials:
OnGUIMaterial(m_importer, m_parser);
EditorMaterial.OnGUIMaterial(m_importer, m_parser);
break;
}
}
static bool s_foldMaterials;
static bool s_foldTextures;
class TmpGuiEnable : IDisposable
{
bool m_backup;
public TmpGuiEnable(bool enable)
{
m_backup = GUI.enabled;
GUI.enabled = enable;
}
public void Dispose()
{
GUI.enabled = m_backup;
}
}
static void OnGUIMaterial(GltfScriptedImporter importer, GltfParser parser)
{
var canExtract = !importer.GetExternalObjectMap().Any(x => x.Value is Material || x.Value is Texture2D);
using (new TmpGuiEnable(canExtract))
{
if (GUILayout.Button("Extract Materials And Textures ..."))
{
importer.ExtractMaterialsAndTextures();
}
}
// ObjectMap
s_foldMaterials = EditorGUILayout.Foldout(s_foldMaterials, "Remapped Materials");
if (s_foldMaterials)
{
DrawRemapGUI<UnityEngine.Material>(importer, parser.GLTF.materials.Select(x => x.name));
}
s_foldTextures = EditorGUILayout.Foldout(s_foldTextures, "Remapped Textures");
if (s_foldTextures)
{
DrawRemapGUI<UnityEngine.Texture2D>(importer, parser.EnumerateTextures().Select(x => x.Name));
}
if (GUILayout.Button("Clear"))
{
importer.ClearExternalObjects<UnityEngine.Material>();
importer.ClearExternalObjects<UnityEngine.Texture2D>();
}
}
static void DrawRemapGUI<T>(GltfScriptedImporter importer, IEnumerable<string> names) where T : UnityEngine.Object
{
EditorGUI.indentLevel++;
var map = importer.GetExternalObjectMap()
.Select(x => (x.Key.name, x.Value as T))
.Where(x => x.Item2 != null)
.ToDictionary(x => x.Item1, x => x.Item2)
;
foreach (var name in names)
{
if (string.IsNullOrEmpty(name))
{
throw new System.ArgumentNullException();
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(name);
map.TryGetValue(name, out T value);
var asset = EditorGUILayout.ObjectField(value, typeof(T), true) as T;
if (asset != value)
{
importer.SetExternalUnityObject(new AssetImporter.SourceAssetIdentifier(value), asset);
}
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel--;
}
static void OnGUIAnimation(GltfScriptedImporter importer, GltfParser parser)
{
foreach (var a in parser.GLTF.animations)
{
GUILayout.Label(a.name);
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 706590752e82d004e99da97aff535f67
guid: 16c69c858a1da5e4088d72a546a2963e
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -3,9 +3,6 @@ using System.IO;
using System.Linq;
using UnityEditor.Experimental.AssetImporters;
using UnityEditor;
using System;
using UnityEngine;
using System.Text.RegularExpressions;
namespace UniGLTF
{
@ -13,32 +10,15 @@ namespace UniGLTF
{
public static void ClearExternalObjects<T>(this ScriptedImporter importer) where T : UnityEngine.Object
{
foreach (var extarnalObject in importer.GetExternalObjectMap().Where(x => x.Key.type == typeof(T)))
foreach (var externalObject in importer.GetExternalObjectMap().Where(x => x.Key.type == typeof(T)))
{
importer.RemoveRemap(extarnalObject.Key);
importer.RemoveRemap(externalObject.Key);
}
AssetDatabase.WriteImportSettingsIfDirty(importer.assetPath);
AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate);
}
public static void ClearExtarnalObjects(this ScriptedImporter importer)
{
foreach (var extarnalObject in importer.GetExternalObjectMap())
{
importer.RemoveRemap(extarnalObject.Key);
}
AssetDatabase.WriteImportSettingsIfDirty(importer.assetPath);
AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate);
}
private static T GetSubAsset<T>(this ScriptedImporter importer, string assetPath) where T : UnityEngine.Object
{
return importer.GetSubAssets<T>(assetPath)
.FirstOrDefault();
}
public static IEnumerable<T> GetSubAssets<T>(this ScriptedImporter importer, string assetPath) where T : UnityEngine.Object
{
return AssetDatabase
@ -47,177 +27,5 @@ namespace UniGLTF
.Where(x => x is T)
.Select(x => x as T);
}
private static void ExtractFromAsset(UnityEngine.Object subAsset, string destinationPath, bool isForceUpdate)
{
string assetPath = AssetDatabase.GetAssetPath(subAsset);
var clone = UnityEngine.Object.Instantiate(subAsset);
AssetDatabase.CreateAsset(clone, destinationPath);
var assetImporter = AssetImporter.GetAtPath(assetPath);
assetImporter.AddRemap(new AssetImporter.SourceAssetIdentifier(subAsset), clone);
if (isForceUpdate)
{
AssetDatabase.WriteImportSettingsIfDirty(assetPath);
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
}
}
public static void ExtractAssets<T>(this ScriptedImporter importer, string dirName, string extension) where T : UnityEngine.Object
{
if (string.IsNullOrEmpty(importer.assetPath))
return;
var subAssets = importer.GetSubAssets<T>(importer.assetPath);
var path = string.Format("{0}/{1}.{2}",
Path.GetDirectoryName(importer.assetPath),
Path.GetFileNameWithoutExtension(importer.assetPath),
dirName
);
var info = importer.SafeCreateDirectory(path);
foreach (var asset in subAssets)
{
ExtractFromAsset(asset, string.Format("{0}/{1}{2}", path, asset.name, extension), false);
}
}
class TextureExtractor
{
GltfParser m_parser;
public GltfParser Parser => m_parser;
public glTF GLTF => m_parser.GLTF;
public IStorage Storage => m_parser.Storage;
public readonly Dictionary<string, GetTextureParam> Textures = new Dictionary<string, GetTextureParam>();
UnityEngine.Texture2D[] m_subAssets;
string m_path;
public TextureExtractor(ScriptedImporter importer)
{
// parse GLTF
m_parser = new GltfParser();
m_parser.ParsePath(importer.assetPath);
m_path = $"{Path.GetDirectoryName(importer.assetPath)}/{Path.GetFileNameWithoutExtension(importer.assetPath)}.Textures";
m_subAssets = importer.GetSubAssets<UnityEngine.Texture2D>(importer.assetPath).ToArray();
}
static Regex s_mimeTypeReg = new Regex("image/(?<mime>.*)$");
public void Extract(GetTextureParam param)
{
var subAsset = m_subAssets.FirstOrDefault(x => x.name == param.Name);
string targetPath = "";
switch (param.TextureType)
{
case GetTextureParam.METALLIC_GLOSS_PROP:
case GetTextureParam.OCCLUSION_PROP:
{
// write converted texture
targetPath = string.Format("{0}/{1}{2}",
m_path,
param.Name,
".png"
);
File.WriteAllBytes(targetPath, subAsset.EncodeToPNG().ToArray());
break;
}
default:
{
// write original bytes
targetPath = string.Format("{0}/{1}{2}",
m_path,
param.Name,
".png"
);
var gltfTexture = GLTF.textures[param.Index0.Value];
File.WriteAllBytes(targetPath, GLTF.GetImageBytes(Storage, gltfTexture.source).ToArray());
break;
}
}
AssetDatabase.ImportAsset(targetPath);
Textures.Add(targetPath, param);
}
}
public static void ExtractTextures(this ScriptedImporter importer, string dirName, Action onCompleted = null)
{
if (string.IsNullOrEmpty(importer.assetPath))
{
return;
}
var path = string.Format("{0}/{1}.{2}",
Path.GetDirectoryName(importer.assetPath),
Path.GetFileNameWithoutExtension(importer.assetPath),
dirName
);
importer.SafeCreateDirectory(path);
// Reload Model
var extractor = new TextureExtractor(importer);
foreach (var material in extractor.GLTF.materials)
{
foreach (var x in extractor.Parser.EnumerateTextures(material))
{
extractor.Extract(x);
}
}
EditorApplication.delayCall += () =>
{
foreach (var kv in extractor.Textures)
{
var targetPath = kv.Key;
var param = kv.Value;
// TextureImporter
var targetTextureImporter = AssetImporter.GetAtPath(targetPath) as TextureImporter;
switch (param.TextureType)
{
case GetTextureParam.OCCLUSION_PROP:
case GetTextureParam.METALLIC_GLOSS_PROP:
targetTextureImporter.sRGBTexture = false;
break;
case GetTextureParam.NORMAL_PROP:
targetTextureImporter.textureType = TextureImporterType.NormalMap;
break;
}
targetTextureImporter.SaveAndReimport();
// remap
var externalObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Texture2D>(targetPath);
importer.AddRemap(new AssetImporter.SourceAssetIdentifier(typeof(UnityEngine.Texture2D), externalObject.name), externalObject);
}
AssetDatabase.ImportAsset(importer.assetPath, ImportAssetOptions.ForceUpdate);
if (onCompleted != null)
{
onCompleted();
}
};
}
public static DirectoryInfo SafeCreateDirectory(this ScriptedImporter importer, string path)
{
if (Directory.Exists(path))
{
return null;
}
return Directory.CreateDirectory(path);
}
}
}

View File

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
namespace UniGLTF
{
public static class ScriptedImporterImpl
{
/// <summary>
/// glb をパースして、UnityObject化、さらにAsset化する
/// </summary>
/// <param name="scriptedImporter"></param>
/// <param name="context"></param>
/// <param name="reverseAxis"></param>
public static void Import(ScriptedImporter scriptedImporter, AssetImportContext context, Axises reverseAxis)
{
#if VRM_DEVELOP
Debug.Log("OnImportAsset to " + scriptedImporter.assetPath);
#endif
//
// Parse(parse glb, parser gltf json)
//
var parser = new GltfParser();
parser.ParsePath(scriptedImporter.assetPath);
//
// Import(create unity objects)
//
var externalObjectMap = scriptedImporter.GetExternalObjectMap();
using (var loaded = new ImporterContext(parser, null,
externalObjectMap.Where(x => x.Value != null).Select(x => (x.Value.name, x.Value)).Concat(
EnumerateTexturesFromUri(externalObjectMap, parser, UnityPath.FromUnityPath(scriptedImporter.assetPath).Parent))))
{
loaded.InvertAxis = reverseAxis;
loaded.Load();
loaded.ShowMeshes();
loaded.TransferOwnership(o =>
{
#if VRM_DEVELOP
Debug.Log($"[{o.GetType().Name}] {o.name} will not destroy");
#endif
context.AddObjectToAsset(o.name, o);
if (o is GameObject)
{
// Root GameObject is main object
context.SetMainObject(loaded.Root);
}
return true;
});
}
}
public static IEnumerable<(string, UnityEngine.Object)> EnumerateTexturesFromUri(Dictionary<AssetImporter.SourceAssetIdentifier, UnityEngine.Object> exclude,
GltfParser parser, UnityPath dir)
{
foreach (var texParam in parser.EnumerateTextures())
{
switch (texParam.TextureType)
{
case GetTextureParam.METALLIC_GLOSS_PROP:
case GetTextureParam.OCCLUSION_PROP:
break;
default:
{
var gltfTexture = parser.GLTF.textures.First(y => y.name == texParam.GltflName);
var gltfImage = parser.GLTF.images[gltfTexture.source];
if (!string.IsNullOrEmpty(gltfImage.uri))
{
var child = dir.Child(gltfImage.uri);
var asset = AssetDatabase.LoadAssetAtPath<Texture2D>(child.Value);
if (asset == null)
{
throw new System.IO.FileNotFoundException($"{child}");
}
// Debug.Log($"exists: {child}: {asset}");
if (exclude == null || !exclude.Any(kv => kv.Value.name == asset.name))
{
yield return (asset.name, asset);
}
}
}
break;
}
}
}
}
}

View File

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

View File

@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
using System.Linq;
namespace UniGLTF
{
public class TextureExtractor
{
const string TextureDirName = "Textures";
GltfParser m_parser;
public GltfParser Parser => m_parser;
public glTF GLTF => m_parser.GLTF;
public IStorage Storage => m_parser.Storage;
public readonly Dictionary<string, GetTextureParam> Textures = new Dictionary<string, GetTextureParam>();
UnityEngine.Texture2D[] m_subAssets;
string m_path;
public TextureExtractor(string assetPath, UnityEngine.Texture2D[] subAssets)
{
// parse GLTF
m_parser = new GltfParser();
m_parser.ParsePath(assetPath);
m_path = $"{Path.GetDirectoryName(assetPath)}/{Path.GetFileNameWithoutExtension(assetPath)}.Textures";
SafeCreateDirectory(m_path);
if (assetPath == null)
{
throw new ArgumentNullException();
}
m_subAssets = subAssets;
}
public static string GetExt(string mime, string uri)
{
switch (mime)
{
case "image/png": return ".png";
case "image/jpeg": return ".jpg";
}
return Path.GetExtension(uri).ToLower();
}
public void Extract(GetTextureParam param, bool hasUri)
{
if (Textures.Values.Contains(param))
{
return;
}
var subAsset = m_subAssets.FirstOrDefault(x => x.name == param.ConvertedName);
string targetPath = "";
if (hasUri && !param.ExtractConverted)
{
var gltfTexture = GLTF.textures[param.Index0.Value];
var gltfImage = GLTF.images[gltfTexture.source];
var ext = GetExt(gltfImage.mimeType, gltfImage.uri);
targetPath = $"{Path.GetDirectoryName(m_path)}/{param.GltflName}{ext}";
}
else
{
switch (param.TextureType)
{
case GetTextureParam.METALLIC_GLOSS_PROP:
case GetTextureParam.OCCLUSION_PROP:
{
// write converted texture
targetPath = $"{m_path}/{param.ConvertedName}.png";
File.WriteAllBytes(targetPath, subAsset.EncodeToPNG().ToArray());
AssetDatabase.ImportAsset(targetPath);
break;
}
default:
{
// write original bytes
var gltfTexture = GLTF.textures[param.Index0.Value];
var gltfImage = GLTF.images[gltfTexture.source];
var ext = GetExt(gltfImage.mimeType, gltfImage.uri);
targetPath = $"{m_path}/{param.GltflName}{ext}";
File.WriteAllBytes(targetPath, GLTF.GetImageBytes(Storage, gltfTexture.source).ToArray());
AssetDatabase.ImportAsset(targetPath);
break;
}
}
}
Textures.Add(targetPath, param);
}
public static DirectoryInfo SafeCreateDirectory(string path)
{
if (Directory.Exists(path))
{
return null;
}
return Directory.CreateDirectory(path);
}
/// <summary>
///
/// * Texture(.png etc...)をディスクに書き出す
/// * EditorApplication.delayCall で処理を進めて 書き出した画像が Asset として成立するのを待つ
/// * 書き出した Asset から TextureImporter を取得して設定する
///
/// </summary>
/// <param name="importer"></param>
/// <param name="dirName"></param>
/// <param name="onCompleted"></param>
public static void ExtractTextures(string assetPath, Texture2D[] subAssets, Action<Texture2D> addRemap, Action<IEnumerable<string>> onCompleted = null)
{
var extractor = new TextureExtractor(assetPath, subAssets);
var normalMaps = new List<string>();
foreach (var material in extractor.GLTF.materials)
{
foreach (var x in extractor.Parser.EnumerateTextures(material))
{
var gltfTexture = extractor.GLTF.textures[x.Index0.Value];
var gltfImage = extractor.GLTF.images[gltfTexture.source];
extractor.Extract(x, !string.IsNullOrEmpty(gltfImage.uri));
}
}
EditorApplication.delayCall += () =>
{
foreach (var kv in extractor.Textures)
{
var targetPath = kv.Key;
var param = kv.Value;
// TextureImporter
var targetTextureImporter = AssetImporter.GetAtPath(targetPath) as TextureImporter;
if (targetTextureImporter != null)
{
switch (param.TextureType)
{
case GetTextureParam.OCCLUSION_PROP:
case GetTextureParam.METALLIC_GLOSS_PROP:
#if VRM_DEVELOP
Debug.Log($"{targetPath} => linear");
#endif
targetTextureImporter.sRGBTexture = false;
targetTextureImporter.SaveAndReimport();
break;
case GetTextureParam.NORMAL_PROP:
#if VRM_DEVELOP
Debug.Log($"{targetPath} => normalmap");
#endif
targetTextureImporter.textureType = TextureImporterType.NormalMap;
targetTextureImporter.SaveAndReimport();
break;
}
}
else
{
throw new FileNotFoundException(targetPath);
}
// remap
var externalObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Texture2D>(targetPath);
if (externalObject != null)
{
addRemap(externalObject);
}
}
if (onCompleted != null)
{
onCompleted(extractor.Textures.Keys);
}
};
}
}
}

View File

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

View File

@ -1,89 +0,0 @@
#if false
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)
{
if (UnityPath.FromUnityPath(path).IsStreamingAsset)
{
Debug.LogFormat("Skip StreamingAssets: {0}", path);
continue;
}
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 parser = new GltfParser();
parser.ParsePath(src);
var context = new ImporterContext(parser);
// Extract textures to assets folder
context.ExtractImages(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();
}
};
}
}
}
#endif

View File

@ -229,13 +229,20 @@ namespace UniGLTF
for (int i = 0; i < GLTF.textures.Count; ++i)
{
var gltfTexture = GLTF.textures[i];
if (string.IsNullOrEmpty(gltfTexture.name))
var gltfImage = GLTF.images[gltfTexture.source];
if (!string.IsNullOrEmpty(gltfImage.uri))
{
// use image name
gltfTexture.name = GLTF.images[gltfTexture.source].name;
// from image uri
gltfTexture.name = Path.GetFileNameWithoutExtension(gltfImage.uri);
}
if (string.IsNullOrEmpty(gltfTexture.name))
{
// use image name
gltfTexture.name = gltfImage.name;
}
if (string.IsNullOrEmpty(gltfTexture.name))
{
// no name
var newName = $"texture_{i}";
if (!used.Add(newName))
{
@ -250,15 +257,17 @@ namespace UniGLTF
else
{
var lower = gltfTexture.name.ToLower();
if (used.Contains(lower))
if (!used.Add(lower))
{
// rename
var uname = lower + "_" + Guid.NewGuid().ToString("N");
Debug.LogWarning($"same name: {lower} => {uname}");
gltfTexture.name = uname;
lower = uname;
if (!used.Add(uname))
{
throw new Exception();
}
}
used.Add(lower);
}
}
}
@ -325,62 +334,6 @@ namespace UniGLTF
}
}
public string GetTextureExtension(int imageIndex)
{
foreach (var m in GLTF.materials)
{
if (m.pbrMetallicRoughness != null)
{
// base color
if (m.pbrMetallicRoughness?.baseColorTexture != null)
{
if (m.pbrMetallicRoughness.baseColorTexture.index == imageIndex)
{
return "";
}
}
// metallic roughness
if (m.pbrMetallicRoughness?.metallicRoughnessTexture != null)
{
if (m.pbrMetallicRoughness.metallicRoughnessTexture.index == imageIndex)
{
return ".metallicRoughness";
}
}
}
// emission
if (m.emissiveTexture != null)
{
if (m.emissiveTexture.index == imageIndex)
{
return "";
}
}
// normal
if (m.normalTexture != null)
{
if (m.normalTexture.index == imageIndex)
{
return "";
}
}
// occlusion
if (m.occlusionTexture != null)
{
if (m.occlusionTexture.index == imageIndex)
{
return ".occlusion";
}
}
}
return "";
}
public IEnumerable<GetTextureParam> EnumerateTextures(glTFMaterial m)
{
if (m.pbrMetallicRoughness != null)
@ -425,7 +378,7 @@ namespace UniGLTF
var m = GLTF.materials[i];
foreach (var x in EnumerateTextures(m))
{
if (used.Add(x.Name))
if (used.Add(x.ConvertedName))
{
yield return x;
}

View File

@ -103,17 +103,6 @@ namespace UniGLTF
throw new UniGLTFNotSupportedException("draco is not supported");
}
// using (MeasureTime("LoadTextures"))
// {
// for (int i = 0; i < GLTF.materials.Count; ++i)
// {
// foreach (var param in MaterialFactory.EnumerateGetTextureparam(i))
// {
// await m_textureFactory.GetTextureAsync(GLTF, param);
// }
// }
// }
using (MeasureTime("LoadMaterials"))
{
await m_materialFactory.LoadMaterialsAsync(m_awaitCaller, m_textureFactory.GetTextureAsync);
@ -201,8 +190,9 @@ namespace UniGLTF
}
#endregion
#region Imported
#region Imported
public GameObject Root;
bool m_ownRoot = true;
public List<Transform> Nodes = new List<Transform>();
public List<MeshWithMaterials> Meshes = new List<MeshWithMaterials>();
@ -216,6 +206,14 @@ namespace UniGLTF
}
}
}
void RemoveMesh(Mesh mesh)
{
var index = Meshes.FindIndex(x => x.Mesh == mesh);
if (index >= 0)
{
Meshes.RemoveAt(index);
}
}
public void EnableUpdateWhenOffscreen()
{
@ -236,20 +234,39 @@ namespace UniGLTF
#endregion
/// <summary>
/// Importに使った一時オブジェクトを破棄する
///
/// 変換のあるテクスチャで、変換前のもの
/// normal, occlusion, metallicRoughness
/// ImporterContextが所有する UnityEngine.Object を破棄する
/// </summary>
public virtual void Dispose()
{
foreach (var x in m_textureFactory.Textures)
Action<UnityEngine.Object> destroy = UnityResourceDestroyer.DestroyResource();
foreach (var x in AnimationClips)
{
if (!x.IsUsed)
{
// Destroy temporary texture object
UnityEngine.Object.Destroy(x.Texture);
}
#if VRM_DEVELOP
Debug.Log($"Destroy {x}");
#endif
destroy(x);
}
AnimationClips.Clear();
foreach (var x in Meshes)
{
#if VRM_DEVELOP
Debug.Log($"Destroy {x}");
#endif
destroy(x.Mesh);
}
Meshes.Clear();
m_materialFactory.Dispose();
m_textureFactory.Dispose();
if (m_ownRoot && Root != null)
{
#if VRM_DEVELOP
Debug.Log($"Destroy {Root}");
#endif
destroy(Root);
}
}
@ -257,33 +274,64 @@ namespace UniGLTF
/// Root ヒエラルキーで使っているリソース
/// </summary>
/// <returns></returns>
public virtual IEnumerable<UnityEngine.Object> ModelOwnResources()
public virtual void TransferOwnership(TakeOwnershipFunc take)
{
var list = new List<UnityEngine.Object>();
foreach (var mesh in Meshes)
{
yield return mesh.Mesh;
if (take(mesh.Mesh))
{
list.Add(mesh.Mesh);
}
}
foreach (var material in m_materialFactory.Materials)
foreach (var x in list)
{
yield return material.Asset;
}
foreach (var texture in m_textureFactory.Textures)
{
yield return texture.Texture;
RemoveMesh(x as Mesh);
}
TextureFactory.TransferOwnership(take);
MaterialFactory.TransferOwnership(take);
list.Clear();
foreach (var animation in AnimationClips)
{
yield return animation;
if (take(animation))
{
list.Add(animation);
}
}
foreach (var x in list)
{
AnimationClips.Remove(x as AnimationClip);
}
if (m_ownRoot && Root != null)
{
if (take(Root))
{
// 所有権(Dispose権)
m_ownRoot = false;
}
}
}
/// <summary>
/// RootにUnityResourceDestroyerをアタッチして、
/// RootをUnityEngine.Object.Destroyしたときに、
/// 関連するUnityEngine.Objectを破棄するようにする。
/// Mesh, Material, Texture, AnimationClip, GameObject の所有者が
/// ImporterContext から UnityResourceDestroyer に移動する。
/// ImporterContext.Dispose の対象から外れる。
/// </summary>
/// <returns></returns>
public UnityResourceDestroyer DisposeOnGameObjectDestroyed()
{
var destroyer = Root.AddComponent<UnityResourceDestroyer>();
foreach (var x in ModelOwnResources())
TransferOwnership(o =>
{
destroyer.Resources.Add(x);
}
destroyer.Resources.Add(o);
return true;
});
return destroyer;
}
}

View File

@ -16,10 +16,21 @@ namespace UniGLTF
{
throw new Exception();
}
if (task.IsFaulted)
{
if (task.Exception is AggregateException ae && ae.InnerExceptions.Count == 1)
{
throw ae.InnerException;
}
else
{
throw task.Exception;
}
}
#if VRM_DEVELOP
Debug.Log(meassureTime.GetSpeedLog());
#endif
}
}
}

View File

@ -64,6 +64,8 @@ namespace UniGLTF
public readonly Material Asset;
public readonly bool UseExternal;
public bool IsSubAsset => !UseExternal;
public MaterialLoadInfo(Material asset, bool useExternal)
{
Asset = asset;
@ -73,19 +75,52 @@ namespace UniGLTF
List<MaterialLoadInfo> m_materials = new List<MaterialLoadInfo>();
public IReadOnlyList<MaterialLoadInfo> Materials => m_materials;
public void Dispose()
void Remove(Material material)
{
foreach (var x in ObjectsForSubAsset())
var index = m_materials.FindIndex(x => x.Asset == material);
if (index >= 0)
{
UnityEngine.Object.DestroyImmediate(x, true);
m_materials.RemoveAt(index);
}
}
public IEnumerable<UnityEngine.Object> ObjectsForSubAsset()
public void Dispose()
{
foreach (var x in m_materials)
{
yield return x.Asset;
if (!x.UseExternal)
{
// 外部の '.asset' からロードしていない
#if VRM_DEVELOP
Debug.Log($"Destroy {x.Asset}");
#endif
UnityEngine.Object.DestroyImmediate(x.Asset, false);
}
}
}
/// <summary>
/// 所有権(Dispose権)を移譲する
/// </summary>
/// <param name="take"></param>
public void TransferOwnership(TakeOwnershipFunc take)
{
var list = new List<Material>();
foreach (var x in m_materials)
{
if (!x.UseExternal)
{
// 外部の '.asset' からロードしていない
if (take(x.Asset))
{
list.Add(x.Asset);
}
}
}
foreach (var x in list)
{
Remove(x);
}
}
@ -200,22 +235,5 @@ namespace UniGLTF
var task = DefaultCreateMaterialAsync(default(ImmediateCaller), gltf, i, null);
return task.Result;
}
public IEnumerable<GetTextureParam> EnumerateGetTextureparam(int i)
{
var m = m_gltf.materials[i];
// color texture
var colorIndex = m.pbrMetallicRoughness?.baseColorTexture?.index;
if (colorIndex.HasValue)
{
yield return GetTextureParam.Create(m_gltf, i);
}
if (!glTF_KHR_materials_unlit.IsEnable(m))
{
// PBR
}
}
}
}

View File

@ -71,13 +71,13 @@ namespace UniGLTF
{
getTexture = (_x, _y, _z) => Task.FromResult<Texture2D>(null);
}
var src = gltf.materials[i];
var material = MaterialFactory.CreateMaterial(i, src, ShaderName);
// PBR material
if (src != null)
var material = default(Material);
if (i >= 0 && i < gltf.materials.Count)
{
var src = gltf.materials[i];
material = MaterialFactory.CreateMaterial(i, src, ShaderName);
if (src.pbrMetallicRoughness != null)
{
if (src.pbrMetallicRoughness.baseColorFactor != null && src.pbrMetallicRoughness.baseColorFactor.Length == 4)
@ -214,6 +214,10 @@ namespace UniGLTF
material.SetFloat("_Mode", (float)blendMode);
}
else
{
material = MaterialFactory.CreateMaterial(i, null, ShaderName);
}
return material;
}

View File

@ -0,0 +1,13 @@
namespace UniGLTF
{
/// <summary>
/// 所有権を移動する関数。
///
/// * 所有権が移動する。return true => ImporterContext.Dispose の対象から外れる
/// * 所有権が移動しない。return false => Importer.Context.Dispose でDestroyされる
///
/// </summary>
/// <param name="o">対象のオブジェクト</param>
/// <returns>所有権が移動したらtrue</returns>
public delegate bool TakeOwnershipFunc(UnityEngine.Object o);
}

View File

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

View File

@ -154,7 +154,7 @@ namespace UniGLTF
);
}
public virtual int ExportTexture(glTF gltf, int bufferIndex, Texture texture, glTFTextureTypes textureType)
public int ExportTexture(glTF gltf, int bufferIndex, Texture texture, glTFTextureTypes textureType)
{
var bytesWithMime = GetBytesWithMime(texture, textureType); ;
@ -166,7 +166,7 @@ namespace UniGLTF
var imageIndex = gltf.images.Count;
gltf.images.Add(new glTFImage
{
name = texture.name,
name = GetTextureParam.RemoveSuffix(texture.name),
bufferView = viewIndex,
mimeType = bytesWithMime.mine,
});

View File

@ -5,10 +5,50 @@ namespace UniGLTF
public struct GetTextureParam
{
public const string NORMAL_PROP = "_BumpMap";
public const string NORMAL_SUFFIX = ".normal";
public const string METALLIC_GLOSS_PROP = "_MetallicGlossMap";
public const string METALLIC_GLOSS_SUFFIX = ".metallicRoughness";
public const string OCCLUSION_PROP = "_OcclusionMap";
public const string OCCLUSION_SUFFIX = ".occlusion";
public static string RemoveSuffix(string src)
{
if (src.EndsWith(NORMAL_SUFFIX))
{
return src.Substring(0, src.Length - NORMAL_SUFFIX.Length);
}
else if (src.EndsWith(METALLIC_GLOSS_SUFFIX))
{
return src.Substring(0, src.Length - METALLIC_GLOSS_SUFFIX.Length);
}
else if (src.EndsWith(OCCLUSION_SUFFIX))
{
return src.Substring(0, src.Length - OCCLUSION_SUFFIX.Length);
}
else
{
return src;
}
}
readonly string m_name;
public string GltflName => m_name;
public string ConvertedName
{
get
{
switch (TextureType)
{
case METALLIC_GLOSS_PROP: return $"{m_name}{METALLIC_GLOSS_SUFFIX}";
case OCCLUSION_PROP: return $"{m_name}{OCCLUSION_SUFFIX}";
case NORMAL_PROP: return $"{m_name}{NORMAL_SUFFIX}";
default: return m_name;
}
}
}
public readonly string Name;
public readonly string TextureType;
public readonly float MetallicFactor;
public readonly ushort? Index0;
@ -18,13 +58,18 @@ namespace UniGLTF
public readonly ushort? Index4;
public readonly ushort? Index5;
/// <summary>
/// この種類は変換済みをExtract
/// </summary>
public bool ExtractConverted => TextureType == OCCLUSION_PROP || TextureType == METALLIC_GLOSS_PROP;
public GetTextureParam(string name, string textureType, float metallicFactor, int i0, int i1, int i2, int i3, int i4, int i5)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException();
}
Name = name;
m_name = name;
TextureType = textureType;
MetallicFactor = metallicFactor;
@ -69,13 +114,13 @@ namespace UniGLTF
public static GetTextureParam CreateMetallic(glTF gltf, int textureIndex, float metallicFactor)
{
var name = gltf.textures[textureIndex].name;
return new GetTextureParam(name + ".metallicRoughness", METALLIC_GLOSS_PROP, metallicFactor, textureIndex, default, default, default, default, default);
return new GetTextureParam(name, METALLIC_GLOSS_PROP, metallicFactor, textureIndex, default, default, default, default, default);
}
public static GetTextureParam CreateOcclusion(glTF gltf, int textureIndex)
{
var name = gltf.textures[textureIndex].name;
return new GetTextureParam(name + ".occlusion", OCCLUSION_PROP, default, textureIndex, default, default, default, default, default);
return new GetTextureParam(name, OCCLUSION_PROP, default, textureIndex, default, default, default, default, default);
}
}
}

View File

@ -24,6 +24,8 @@ namespace UniGLTF
public bool IsUsed => Flags.HasFlag(TextureLoadFlags.Used);
public bool IsExternal => Flags.HasFlag(TextureLoadFlags.External);
public bool IsSubAsset => IsUsed && !IsExternal;
public TextureLoadInfo(Texture2D texture, bool used, bool isExternal)
{
Texture = texture;
@ -49,10 +51,15 @@ namespace UniGLTF
{
if (param.Index0.HasValue && m_externalMap != null)
{
if (m_externalMap.TryGetValue(param.Name, out external))
var cacheName = param.ConvertedName;
if (param.TextureType == GetTextureParam.NORMAL_PROP)
{
// Debug.Log($"use external: {param.Name}");
m_textureCache.Add(param.Name, new TextureLoadInfo(external, used, true));
cacheName = param.GltflName;
}
if (m_externalMap.TryGetValue(cacheName, out external))
{
m_textureCache.Add(cacheName, new TextureLoadInfo(external, used, true));
return external;
}
}
@ -77,17 +84,42 @@ namespace UniGLTF
public void Dispose()
{
foreach (var x in ObjectsForSubAsset())
{
UnityEngine.Object.DestroyImmediate(x, true);
}
}
public IEnumerable<UnityEngine.Object> ObjectsForSubAsset()
{
Action<UnityEngine.Object> destroy = UnityResourceDestroyer.DestroyResource();
foreach (var kv in m_textureCache)
{
yield return kv.Value.Texture;
if (!kv.Value.IsExternal)
{
#if VRM_DEVELOP
Debug.Log($"Destroy {kv.Value.Texture}");
#endif
destroy(kv.Value.Texture);
}
}
m_textureCache.Clear();
}
/// <summary>
/// 所有権(Dispose権)を移譲する
/// </summary>
/// <param name="take"></param>
public void TransferOwnership(TakeOwnershipFunc take)
{
var keys = new List<string>();
foreach (var x in m_textureCache)
{
if (x.Value.IsUsed && !x.Value.IsExternal)
{
// マテリアルから参照されていて
// 外部のAssetからロードしていない。
if (take(x.Value.Texture))
{
keys.Add(x.Key);
}
}
}
foreach (var x in keys)
{
m_textureCache.Remove(x);
}
}
@ -118,7 +150,7 @@ namespace UniGLTF
/// <returns></returns>
public async Task<Texture2D> GetTextureAsync(IAwaitCaller awaitCaller, glTF gltf, GetTextureParam param)
{
if (m_textureCache.TryGetValue(param.Name, out TextureLoadInfo cacheInfo))
if (m_textureCache.TryGetValue(param.ConvertedName, out TextureLoadInfo cacheInfo))
{
return cacheInfo.Texture;
}
@ -131,26 +163,12 @@ namespace UniGLTF
{
case GetTextureParam.NORMAL_PROP:
{
if (Application.isPlaying)
{
var baseTexture = await GetOrCreateBaseTexture(awaitCaller, gltf, param.Index0.Value, false);
var converted = new NormalConverter().GetImportTexture(baseTexture.Texture);
var info = new TextureLoadInfo(converted, true, false);
m_textureCache.Add(param.Name, info);
return info.Texture;
}
else
{
#if UNITY_EDITOR
var info = await LoadTextureAsync(awaitCaller, param.Index0.Value, true);
var name = gltf.textures[param.Index0.Value].name;
m_textureCache.Add(name, info);
var textureAssetPath = AssetDatabase.GetAssetPath(info.Texture);
TextureIO.MarkTextureAssetAsNormalMap(textureAssetPath);
#endif
return info.Texture;
}
var baseTexture = await GetOrCreateBaseTexture(awaitCaller, gltf, param.Index0.Value, false);
var converted = new NormalConverter().GetImportTexture(baseTexture.Texture);
converted.name = param.ConvertedName;
var info = new TextureLoadInfo(converted, true, false);
m_textureCache.Add(converted.name, info);
return info.Texture;
}
case GetTextureParam.METALLIC_GLOSS_PROP:
@ -158,9 +176,9 @@ namespace UniGLTF
// Bake roughnessFactor values into a texture.
var baseTexture = await GetOrCreateBaseTexture(awaitCaller, gltf, param.Index0.Value, false);
var converted = new MetallicRoughnessConverter(param.MetallicFactor).GetImportTexture(baseTexture.Texture);
converted.name = param.Name;
converted.name = param.ConvertedName;
var info = new TextureLoadInfo(converted, true, false);
m_textureCache.Add(param.Name, info);
m_textureCache.Add(converted.name, info);
return info.Texture;
}
@ -168,9 +186,9 @@ namespace UniGLTF
{
var baseTexture = await GetOrCreateBaseTexture(awaitCaller, gltf, param.Index0.Value, false);
var converted = new OcclusionConverter().GetImportTexture(baseTexture.Texture);
converted.name = param.Name;
converted.name = param.ConvertedName;
var info = new TextureLoadInfo(converted, true, false);
m_textureCache.Add(param.Name, info);
m_textureCache.Add(converted.name, info);
return info.Texture;
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using UnityEngine;
@ -9,7 +10,7 @@ namespace UniGLTF
/// </summary>
public class UnityResourceDestroyer : MonoBehaviour
{
List<UnityEngine.Object> m_resources = new List<Object>();
List<UnityEngine.Object> m_resources = new List<UnityEngine.Object>();
public IList<UnityEngine.Object> Resources => m_resources;
void OnDestroy()
@ -23,5 +24,16 @@ namespace UniGLTF
Destroy(x);
}
}
public static Action<UnityEngine.Object> DestroyResource()
{
Action<UnityEngine.Object> des = (UnityEngine.Object o) => UnityEngine.Object.Destroy(o);
Action<UnityEngine.Object> desi = (UnityEngine.Object o) => UnityEngine.Object.DestroyImmediate(o);
Action<UnityEngine.Object> func = Application.isPlaying
? des
: desi
;
return func;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ca87b3d02ce67634a9ebcfe8cca70ece
guid: 93b3af59a4d8b704a883260f2fd40c44
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,20 +0,0 @@
using System;
namespace UniJSON
{
public struct ActionDisposer : IDisposable
{
Action m_action;
public ActionDisposer(Action action)
{
m_action = action;
}
public void Dispose()
{
m_action();
}
}
}

View File

@ -98,44 +98,31 @@ namespace UniGLTF
public void UniGLTFSimpleSceneTest()
{
var go = CreateSimpleScene();
ImporterContext context = default;
try
// export
var gltf = new glTF();
string json = null;
using (var exporter = new gltfExporter(gltf))
{
// export
var gltf = new glTF();
exporter.Prepare(go);
exporter.Export(MeshExportSettings.Default);
string json = null;
using (var exporter = new gltfExporter(gltf))
{
exporter.Prepare(go);
exporter.Export(MeshExportSettings.Default);
// remove empty buffer
gltf.buffers.Clear();
// remove empty buffer
gltf.buffers.Clear();
json = gltf.ToJson();
}
// parse
var parser = new GltfParser();
parser.ParseJson(json, new SimpleStorage(new ArraySegment<byte>()));
// import
context = new ImporterContext(parser);
context.Load();
AssertAreEqual(go.transform, context.Root.transform);
json = gltf.ToJson();
}
finally
// parse
var parser = new GltfParser();
parser.ParseJson(json, new SimpleStorage(new ArraySegment<byte>()));
// import
using (var context = new ImporterContext(parser))
{
//Debug.LogFormat("Destroy, {0}", go.name);
GameObject.DestroyImmediate(go);
if (context != null)
{
var editor = new EditorImporterContext(context);
editor.EditorDestroyRootAndAssets();
}
context.Load();
AssertAreEqual(go.transform, context.Root.transform);
}
}
@ -568,19 +555,22 @@ namespace UniGLTF
{
var parser = new GltfParser();
parser.ParseJson(json, new SimpleStorage(new ArraySegment<byte>(new byte[1024 * 1024])));
var context = new ImporterContext(parser);
//Debug.LogFormat("{0}", context.Json);
context.Load();
var importedRed = context.Root.transform.GetChild(0);
var importedRedMaterial = importedRed.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("red", importedRedMaterial.name);
Assert.AreEqual(Color.red, importedRedMaterial.color);
using (var context = new ImporterContext(parser))
{
//Debug.LogFormat("{0}", context.Json);
context.Load();
var importedBlue = context.Root.transform.GetChild(1);
var importedBlueMaterial = importedBlue.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("blue", importedBlueMaterial.name);
Assert.AreEqual(Color.blue, importedBlueMaterial.color);
var importedRed = context.Root.transform.GetChild(0);
var importedRedMaterial = importedRed.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("red", importedRedMaterial.name);
Assert.AreEqual(Color.red, importedRedMaterial.color);
var importedBlue = context.Root.transform.GetChild(1);
var importedBlueMaterial = importedBlue.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("blue", importedBlueMaterial.name);
Assert.AreEqual(Color.blue, importedBlueMaterial.color);
}
}
// import new version
@ -588,18 +578,20 @@ namespace UniGLTF
var parser = new GltfParser();
parser.ParseJson(json, new SimpleStorage(new ArraySegment<byte>(new byte[1024 * 1024])));
//Debug.LogFormat("{0}", context.Json);
var context = new ImporterContext(parser);
context.Load();
using (var context = new ImporterContext(parser))
{
context.Load();
var importedRed = context.Root.transform.GetChild(0);
var importedRedMaterial = importedRed.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("red", importedRedMaterial.name);
Assert.AreEqual(Color.red, importedRedMaterial.color);
var importedRed = context.Root.transform.GetChild(0);
var importedRedMaterial = importedRed.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("red", importedRedMaterial.name);
Assert.AreEqual(Color.red, importedRedMaterial.color);
var importedBlue = context.Root.transform.GetChild(1);
var importedBlueMaterial = importedBlue.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("blue", importedBlueMaterial.name);
Assert.AreEqual(Color.blue, importedBlueMaterial.color);
var importedBlue = context.Root.transform.GetChild(1);
var importedBlueMaterial = importedBlue.GetComponent<Renderer>().sharedMaterial;
Assert.AreEqual("blue", importedBlueMaterial.name);
Assert.AreEqual(Color.blue, importedBlueMaterial.color);
}
}
}
finally

View File

@ -45,13 +45,12 @@ namespace VRM.Samples
var parser = new GltfParser();
parser.ParseGlb(File.ReadAllBytes(path));
var context = new VRMImporterContext(parser);
context.Load();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
using (new ActionDisposer(() => { GameObject.DestroyImmediate(context.Root); }))
using (var context = new VRMImporterContext(parser))
{
context.Load();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
var importedJson = JsonParser.Parse(context.Json);
importedJson.SetValue("/extensions/VRM/exporterVersion", VRMVersion.VRM_VERSION, (f, x) => f.Value(x));
importedJson.SetValue("/asset/generator", UniGLTF.UniGLTFVersion.UNIGLTF_VERSION, (f, x) => f.Value(x));
@ -118,16 +117,18 @@ namespace VRM.Samples
var path = AliciaPath;
var parser = new GltfParser();
parser.ParseGlb(File.ReadAllBytes(path));
var context = new VRMImporterContext(parser);
context.Load();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
foreach (var mesh in context.Meshes)
using (var context = new VRMImporterContext(parser))
{
var src = mesh.Mesh;
var dst = src.Copy(true);
MeshTests.MeshEquals(src, dst);
context.Load();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
foreach (var mesh in context.Meshes)
{
var src = mesh.Mesh;
var dst = src.Copy(true);
MeshTests.MeshEquals(src, dst);
}
}
}
@ -138,28 +139,31 @@ namespace VRM.Samples
var path = AliciaPath;
var parser = new GltfParser();
parser.ParseGlb(File.ReadAllBytes(path));
var context = new VRMImporterContext(parser);
var oldJson = context.GLTF.ToJson().ParseAsJson().ToString(" ");
// 生成シリアライザでJSON化する
var f = new JsonFormatter();
GltfSerializer.Serialize(f, context.GLTF);
var parsed = f.ToString().ParseAsJson();
var newJson = parsed.ToString(" ");
using (var context = new VRMImporterContext(parser))
{
var oldJson = context.GLTF.ToJson().ParseAsJson().ToString(" ");
// File.WriteAllText("old.json", oldJson);
// File.WriteAllText("new.json", newJson);
// 生成シリアライザでJSON化する
var f = new JsonFormatter();
GltfSerializer.Serialize(f, context.GLTF);
var parsed = f.ToString().ParseAsJson();
var newJson = parsed.ToString(" ");
// 比較
Assert.AreEqual(oldJson.ParseAsJson().ToString(), newJson.ParseAsJson().ToString());
// File.WriteAllText("old.json", oldJson);
// File.WriteAllText("new.json", newJson);
// 生成デシリアライザでロードする
var ff = new JsonFormatter();
var des = GltfDeserializer.Deserialize(parsed);
ff.Clear();
GltfSerializer.Serialize(ff, des);
var desJson = ff.ToString().ParseAsJson().ToString(" ");
Assert.AreEqual(oldJson.ParseAsJson().ToString(), desJson.ParseAsJson().ToString());
// 比較
Assert.AreEqual(oldJson.ParseAsJson().ToString(), newJson.ParseAsJson().ToString());
// 生成デシリアライザでロードする
var ff = new JsonFormatter();
var des = GltfDeserializer.Deserialize(parsed);
ff.Clear();
GltfSerializer.Serialize(ff, des);
var desJson = ff.ToString().ParseAsJson().ToString(" ");
Assert.AreEqual(oldJson.ParseAsJson().ToString(), desJson.ParseAsJson().ToString());
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2f563e7bcfaebb74dbf9748df1f4824c
timeCreated: 1520832729
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -50,16 +50,18 @@ namespace VRM.Samples
var parser = new GltfParser();
parser.ParseGlb(bytes);
var context = new VRMImporterContext(parser);
using (var context = new VRMImporterContext(parser))
{
// metaを取得(todo: thumbnailテクスチャのロード)
var meta = await context.ReadMetaAsync();
Debug.LogFormat("meta: title:{0}", meta.Title);
// metaを取得(todo: thumbnailテクスチャのロード)
var meta = await context.ReadMetaAsync();
Debug.LogFormat("meta: title:{0}", meta.Title);
// ParseしたJSONをシーンオブジェクトに変換していく
await context.LoadAsync();
// ParseしたJSONをシーンオブジェクトに変換していく
await context.LoadAsync();
OnLoaded(context);
OnLoaded(context);
}
}
void OnLoaded(VRMImporterContext context)

View File

@ -90,23 +90,24 @@ namespace VRM.Samples
var parser = new GltfParser();
parser.Parse(path, bytes);
var context = new VRMImporterContext(parser);
// metaを取得(todo: thumbnailテクスチャのロード)
var meta = await context.ReadMetaAsync();
Debug.LogFormat("meta: title:{0}", meta.Title);
// ParseしたJSONをシーンオブジェクトに変換していく
if (m_loadAsync)
using (var context = new VRMImporterContext(parser))
{
await context.LoadAsync();
}
else
{
context.Load();
}
// metaを取得(todo: thumbnailテクスチャのロード)
var meta = await context.ReadMetaAsync();
Debug.LogFormat("meta: title:{0}", meta.Title);
OnLoaded(context);
// ParseしたJSONをシーンオブジェクトに変換していく
if (m_loadAsync)
{
await context.LoadAsync();
}
else
{
context.Load();
}
OnLoaded(context);
}
}
/// <summary>
@ -167,10 +168,12 @@ namespace VRM.Samples
void OnLoaded(VRMImporterContext context)
{
var root = context.Root;
root.transform.SetParent(transform, false);
//メッシュを表示します
context.ShowMeshes();
context.DisposeOnGameObjectDestroyed();
// add motion
var humanPoseTransfer = root.AddComponent<UniHumanoid.HumanPoseTransfer>();

View File

@ -1,188 +0,0 @@
#pragma warning disable 0414
using System;
using System.IO;
using UnityEngine;
#if (NET_4_6 && UNITY_2017_1_OR_NEWER)
using System.Threading.Tasks;
#endif
namespace VRM.Samples
{
public class VRMRuntimeLoaderNet4 : MonoBehaviour
{
[SerializeField, Header("GUI")]
CanvasManager m_canvas;
[SerializeField]
LookTarget m_faceCamera;
[SerializeField, Header("loader")]
UniHumanoid.HumanPoseTransfer m_source;
[SerializeField]
UniHumanoid.HumanPoseTransfer m_target;
[SerializeField, Header("runtime")]
VRMFirstPerson m_firstPerson;
#if (NET_4_6 && UNITY_2017_1_OR_NEWER && !UNITY_WEBGL)
VRMBlendShapeProxy m_blendShape;
void SetupTarget()
{
if (m_target != null)
{
m_target.Source = m_source;
m_target.SourceType = UniHumanoid.HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseTransfer;
m_blendShape = m_target.GetComponent<VRMBlendShapeProxy>();
m_firstPerson = m_target.GetComponent<VRMFirstPerson>();
var animator = m_target.GetComponent<Animator>();
if (animator != null)
{
m_firstPerson.Setup();
if (m_faceCamera != null)
{
m_faceCamera.Target = animator.GetBoneTransform(HumanBodyBones.Head);
}
}
}
}
private void Awake()
{
SetupTarget();
}
private void Start()
{
if (m_canvas == null)
{
Debug.LogWarning("no canvas");
return;
}
m_canvas.LoadVRMButton.onClick.AddListener(LoadVRMClicked);
m_canvas.LoadBVHButton.onClick.AddListener(LoadBVHClicked);
}
// Byte列を得る
async static Task<Byte[]> ReadBytesAsync(string path)
{
return File.ReadAllBytes(path);
}
async static Task<VRMImporterContext> LoadAsync(Byte[] bytes)
{
var context = new VRMImporterContext();
// GLB形式でJSONを取得しParseします
context.ParseGlb(bytes);
// ParseしたJSONをシーンオブジェクトに変換していく
await context.LoadAsyncTask();
return context;
}
/// <summary>
/// Taskで非同期にロードする例
/// </summary>
async void LoadVRMClicked()
{
#if UNITY_STANDALONE_WIN
var path = FileDialogForWindows.FileDialog("open VRM", ".vrm");
#elif UNITY_EDITOR
var path = UnityEditor.EditorUtility.OpenFilePanel("Open VRM", "", "vrm");
#else
var path = Application.dataPath + "/default.vrm";
#endif
if (string.IsNullOrEmpty(path))
{
return;
}
var context = new VRMImporterContext();
var bytes = await ReadBytesAsync(path);
// GLB形式でJSONを取得しParseします
context.ParseGlb(bytes);
// metaを取得(todo: thumbnailテクスチャのロード)
var meta = context.ReadMeta();
Debug.LogFormat("meta: title:{0}", meta.Title);
// ParseしたJSONをシーンオブジェクトに変換していく
var now = Time.time;
await context.LoadAsyncTask();
var delta = Time.time - now;
Debug.LogFormat("LoadVrmAsync {0:0.0} seconds", delta);
OnLoaded(context);
}
void LoadBVHClicked()
{
#if UNITY_STANDALONE_WIN
var path = FileDialogForWindows.FileDialog("open BVH", ".bvh");
if (!string.IsNullOrEmpty(path))
{
LoadBvh(path);
}
#elif UNITY_EDITOR
var path = UnityEditor.EditorUtility.OpenFilePanel("Open BVH", "", "bvh");
if (!string.IsNullOrEmpty(path))
{
LoadBvh(path);
}
#else
LoadBvh(Application.dataPath + "/default.bvh");
#endif
}
void OnLoaded(VRMImporterContext context)
{
var root = context.Root;
root.transform.SetParent(transform, false);
//メッシュを表示します
context.ShowMeshes();
// add motion
var humanPoseTransfer = root.AddComponent<UniHumanoid.HumanPoseTransfer>();
if (m_target != null)
{
GameObject.Destroy(m_target.gameObject);
}
m_target = humanPoseTransfer;
SetupTarget();
}
void LoadBvh(string path)
{
Debug.LogFormat("ImportBvh: {0}", path);
var context = new UniHumanoid.BvhImporterContext();
context.Parse(path);
context.Load();
if (m_source != null)
{
GameObject.Destroy(m_source.gameObject);
}
m_source = context.Root.GetComponent<UniHumanoid.HumanPoseTransfer>();
SetupTarget();
}
#endif
}
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 649886f2803ade846a93be89f73e35c7
timeCreated: 1517899576
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -314,15 +314,15 @@ namespace VRM.Samples
var parser = new GltfParser();
parser.ParseGlb(file);
var context = new VRMImporterContext(parser);
await m_texts.UpdateMetaAsync(context);
await context.LoadAsync();
context.DisposeOnGameObjectDestroyed();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
context.ShowMeshes();
SetModel(context.Root);
using (var context = new VRMImporterContext(parser))
{
await m_texts.UpdateMetaAsync(context);
await context.LoadAsync();
context.EnableUpdateWhenOffscreen();
context.ShowMeshes();
context.DisposeOnGameObjectDestroyed();
SetModel(context.Root);
}
break;
}
@ -334,10 +334,9 @@ namespace VRM.Samples
var context = new UniGLTF.ImporterContext(parser);
context.Load();
context.DisposeOnGameObjectDestroyed();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
context.ShowMeshes();
context.DisposeOnGameObjectDestroyed();
SetModel(context.Root);
break;
}
@ -350,10 +349,9 @@ namespace VRM.Samples
var context = new UniGLTF.ImporterContext(parser);
context.Load();
context.DisposeOnGameObjectDestroyed();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
context.ShowMeshes();
context.DisposeOnGameObjectDestroyed();
SetModel(context.Root);
break;
}

View File

@ -1,42 +1,26 @@
using System.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UnityEditor;
using UnityEngine;
namespace VRM
{
public class VRMEditorImporterContext : EditorImporterContext
public class VRMEditorImporterContext
{
VRMImporterContext m_context;
UnityPath m_prefabPath;
List<UnityPath> m_paths = new List<UnityPath>();
public VRMEditorImporterContext(VRMImporterContext context) : base(context)
public VRMEditorImporterContext(VRMImporterContext context, UnityPath prefabPath)
{
m_context = context;
m_prefabPath = prefabPath;
}
public override IEnumerable<UnityEngine.Object> ObjectsForSubAsset()
{
foreach (var x in base.ObjectsForSubAsset())
{
yield return x;
}
yield return m_context.AvatarDescription;
yield return m_context.HumanoidAvatar;
if (m_context.BlendShapeAvatar != null && m_context.BlendShapeAvatar.Clips != null)
{
foreach (var x in m_context.BlendShapeAvatar.Clips)
{
yield return x;
}
}
yield return m_context.BlendShapeAvatar;
yield return m_context.Meta;
}
public override bool AvoidOverwriteAndLoad(UnityPath assetPath, UnityEngine.Object o)
public bool AvoidOverwriteAndLoad(UnityPath assetPath, UnityEngine.Object o)
{
if (o is BlendShapeAvatar)
{
@ -52,10 +36,36 @@ namespace VRM
return true;
}
return base.AvoidOverwriteAndLoad(assetPath, o);
if (o is Material)
{
var loaded = assetPath.LoadAsset<Material>();
if (loaded == null)
{
throw new Exception();
}
// replace component reference
foreach (var mesh in m_context.Meshes)
{
foreach (var r in mesh.Renderers)
{
for (int i = 0; i < r.sharedMaterials.Length; ++i)
{
if (r.sharedMaterials.Contains(o))
{
r.sharedMaterials = r.sharedMaterials.Select(x => x == o ? loaded : x).ToArray();
}
}
}
}
return true;
}
return false;
}
public override UnityPath GetAssetPath(UnityPath prefabPath, UnityEngine.Object o, bool meshAsSubAsset)
public UnityPath GetAssetPath(UnityPath prefabPath, UnityEngine.Object o)
{
if (o is BlendShapeAvatar
|| o is BlendShapeClip)
@ -82,9 +92,141 @@ namespace VRM
var assetPath = dir.Child(o.name.EscapeFilePath() + ".asset");
return assetPath;
}
else if (o is Material)
{
var materialDir = prefabPath.GetAssetFolder(".Materials");
var materialPath = materialDir.Child(o.name.EscapeFilePath() + ".asset");
return materialPath;
}
// texture is already extracted
// 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)
{
var meshDir = prefabPath.GetAssetFolder(".Meshes");
var meshPath = meshDir.Child(o.name.EscapeFilePath() + ".asset");
return meshPath;
}
else
{
return base.GetAssetPath(prefabPath, o, meshAsSubAsset);
return default(UnityPath);
}
}
/// <summary>
/// Extract images from glb or gltf out of Assets folder.
/// </summary>
/// <param name="assetPath"></param>
public void ConvertAndExtractImages(UnityPath assetPath, Action<IEnumerable<string>> onTextureReloaded)
{
//
// convert images(metallic roughness, occlusion map)
//
var task = m_context.MaterialFactory.LoadMaterialsAsync(default(ImmediateCaller), m_context.TextureFactory.GetTextureAsync);
if (!task.IsCompleted)
{
throw new Exception();
}
if (task.IsFaulted)
{
if (task.Exception is AggregateException ae && ae.InnerExceptions.Count == 1)
{
throw ae.InnerException;
}
else
{
throw task.Exception;
}
}
//
// extract converted textures
//
var subAssets = m_context.TextureFactory.Textures
.Where(x => x.IsUsed)
.Select(x => x.Texture)
.ToArray();
var prefabParentDir = assetPath.Parent;
var folder = assetPath.GetAssetFolder(".Textures");
TextureExtractor.ExtractTextures(assetPath.Value, subAssets, _ => { }, onTextureReloaded);
}
bool SaveAsAsset(UnityEngine.Object o)
{
if (o is GameObject)
{
return false;
}
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(o)))
{
// already exists. not dispose
#if VRM_DEVELOP
Debug.Log($"Loaded. skip: {o}");
#endif
return true;
}
var assetPath = GetAssetPath(m_prefabPath, o);
if (assetPath.IsNull)
{
// not dispose
return true;
}
// if (assetPath.IsFileExists)
// {
// if (AvoidOverwriteAndLoad(assetPath, o))
// {
// #if VRM_DEVELOP
// Debug.Log($"AvoidOverwriteAndLoad: {assetPath}");
// #endif
// // 上書きせずに既存のアセットからロードして置き換えた
// return true;
// }
// }
// アセットとして書き込む
assetPath.Parent.EnsureFolder();
assetPath.CreateAsset(o);
m_paths.Add(assetPath);
// 所有権が移動
return true;
}
public void SaveAsAsset()
{
m_context.ShowMeshes();
//
// save sub assets
//
m_paths.Clear();
m_paths.Add(m_prefabPath);
m_context.TransferOwnership(SaveAsAsset);
// Create or update Main Asset
if (m_prefabPath.IsFileExists)
{
Debug.LogFormat("replace prefab: {0}", m_prefabPath);
var prefab = m_prefabPath.LoadAsset<GameObject>();
PrefabUtility.SaveAsPrefabAssetAndConnect(m_context.Root, m_prefabPath.Value, InteractionMode.AutomatedAction);
}
else
{
Debug.LogFormat("create prefab: {0}", m_prefabPath);
PrefabUtility.SaveAsPrefabAssetAndConnect(m_context.Root, m_prefabPath.Value, InteractionMode.AutomatedAction);
}
foreach (var x in m_paths)
{
x.ImportAsset();
}
}
}

View File

@ -2,7 +2,8 @@
using UnityEditor;
using UnityEngine;
using UniGLTF;
using System;
using System.Collections.Generic;
namespace VRM
{
@ -22,11 +23,15 @@ namespace VRM
// load into scene
var parser = new GltfParser();
parser.ParsePath(path);
var context = new VRMImporterContext(parser);
context.Load();
context.ShowMeshes();
context.EnableUpdateWhenOffscreen();
Selection.activeGameObject = context.Root;
using (var context = new VRMImporterContext(parser))
{
context.Load();
context.EnableUpdateWhenOffscreen();
context.ShowMeshes();
context.DisposeOnGameObjectDestroyed();
Selection.activeGameObject = context.Root;
}
}
else
{
@ -52,19 +57,25 @@ namespace VRM
var prefabPath = UnityPath.FromUnityPath(assetPath);
var parser = new GltfParser();
parser.ParseGlb(File.ReadAllBytes(path));
var context = new VRMImporterContext(parser);
var editor = new VRMEditorImporterContext(context);
editor.ExtractImages(prefabPath);
EditorApplication.delayCall += () =>
Action<IEnumerable<string>> onCompleted = _ =>
{
//
// after textures imported
//
context.Load();
editor.SaveAsAsset(prefabPath);
editor.Dispose();
using (var context = new VRMImporterContext(parser))
{
var editor = new VRMEditorImporterContext(context, prefabPath);
context.Load();
editor.SaveAsAsset();
}
};
using (var context = new VRMImporterContext(parser))
{
var editor = new VRMEditorImporterContext(context, prefabPath);
editor.ConvertAndExtractImages(UnityPath.FromFullpath(path), onCompleted);
}
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UniGLTF;
using UnityEditor;
using UnityEngine;
@ -37,41 +38,31 @@ namespace VRM
}
var parser = new GltfParser();
try
{
parser.ParseGlb(File.ReadAllBytes(path.FullPath));
}
catch (KeyNotFoundException)
{
// invalid VRM-0.X.
// maybe VRM-1.0.do nothing
return;
}
parser.ParseGlb(File.ReadAllBytes(path.FullPath));
var prefabPath = path.Parent.Child(path.FileNameWithoutExtension + ".prefab");
// save texture assets !
LoadTextureAsyncFunc textureLoader = async (caller, textureIndex, used) =>
Action<IEnumerable<string>> onCompleted = texturePaths =>
{
var gltfTexture = parser.GLTF.textures[textureIndex];
var gltfImage = parser.GLTF.images[gltfTexture.source];
var assetPath = prefabPath.Parent.Child(gltfImage.uri);
var texture = await UniGLTF.AssetTextureLoader.LoadTaskAsync(assetPath, parser.GLTF, textureIndex);
return new TextureLoadInfo(texture, used, false);
var map = texturePaths.Select(x =>
{
var texture = AssetDatabase.LoadAssetAtPath(x, typeof(Texture2D));
return (texture.name, texture);
}).ToArray();
using (var context = new VRMImporterContext(parser, null, map))
{
var editor = new VRMEditorImporterContext(context, prefabPath);
context.Load();
editor.SaveAsAsset();
}
};
var context = new VRMImporterContext(parser, textureLoader);
var editor = new VRMEditorImporterContext(context);
editor.ExtractImages(prefabPath);
EditorApplication.delayCall += () =>
// extract texture images
using (var context = new VRMImporterContext(parser))
{
//
// after textures imported
//
context.Load();
editor.SaveAsAsset(prefabPath);
editor.Dispose();
};
var editor = new VRMEditorImporterContext(context, prefabPath);
editor.ConvertAndExtractImages(path, onCompleted);
}
}
}
#endif

View File

@ -28,7 +28,7 @@ VRM extension is for 3d humanoid avatars (and models) in VR applications.
public glTF_VRM_SecondaryAnimation secondaryAnimation = new glTF_VRM_SecondaryAnimation();
public List<glTF_VRM_Material> materialProperties = new List<glTF_VRM_Material>();
public static bool TryDeserilize(glTFExtension extension, out glTF_VRM_extensions vrm)
public static bool TryDeserialize(glTFExtension extension, out glTF_VRM_extensions vrm)
{
if (extension is glTFExtensionImport import)
{

View File

@ -8,16 +8,16 @@ using System.Threading.Tasks;
namespace VRM
{
public class VRMImporterContext : ImporterContext
{
public VRM.glTF_VRM_extensions VRM { get; private set; }
public VRMImporterContext(GltfParser parser, UniGLTF.LoadTextureAsyncFunc asyncTextureLoader = null) : base(parser, asyncTextureLoader)
public VRMImporterContext(GltfParser parser,
UniGLTF.LoadTextureAsyncFunc asyncTextureLoader = null,
IEnumerable<(string, UnityEngine.Object)> externalObjectMap = null) : base(parser, asyncTextureLoader, externalObjectMap)
{
// parse VRM part
if (glTF_VRM_extensions.TryDeserilize(GLTF.extensions, out glTF_VRM_extensions vrm))
if (glTF_VRM_extensions.TryDeserialize(GLTF.extensions, out glTF_VRM_extensions vrm))
{
VRM = vrm;
// override material importer
@ -290,7 +290,10 @@ namespace VRM
meta.ContactInformation = gltfMeta.contactInformation;
meta.Reference = gltfMeta.reference;
meta.Title = gltfMeta.title;
meta.Thumbnail = await TextureFactory.GetTextureAsync(awaitCaller, GLTF, GetTextureParam.Create(GLTF, gltfMeta.texture));
if (gltfMeta.texture >= 0)
{
meta.Thumbnail = await TextureFactory.GetTextureAsync(awaitCaller, GLTF, GetTextureParam.Create(GLTF, gltfMeta.texture));
}
meta.AllowedUser = gltfMeta.allowedUser;
meta.ViolentUssage = gltfMeta.violentUssage;
meta.SexualUssage = gltfMeta.sexualUssage;
@ -303,22 +306,44 @@ namespace VRM
return meta;
}
public override IEnumerable<UnityEngine.Object> ModelOwnResources()
public override void TransferOwnership(TakeOwnershipFunc take)
{
foreach (var x in base.ModelOwnResources())
// VRM 固有のリソース(ScriptableObject)
if (take(HumanoidAvatar))
{
yield return x;
HumanoidAvatar = null;
}
// VRM 固有のリソース(ScriptableObject)
yield return HumanoidAvatar;
yield return AvatarDescription;
yield return Meta;
if (take(Meta))
{
Meta = null;
}
if (take(AvatarDescription))
{
AvatarDescription = null;
}
var list = new List<BlendShapeClip>();
foreach (var x in BlendShapeAvatar.Clips)
{
yield return x;
if (take(x))
{
list.Add(x);
}
}
yield return BlendShapeAvatar;
foreach (var x in list)
{
BlendShapeAvatar.Clips.Remove(x);
}
if (take(BlendShapeAvatar))
{
BlendShapeAvatar = null;
}
// GLTF のリソース
base.TransferOwnership(take);
}
}
}

View File

@ -37,7 +37,9 @@ namespace VRM
}
else
{
Debug.LogWarningFormat("unknown shader {0}.", shaderName);
// #if VRM_DEVELOP
// Debug.LogWarningFormat("unknown shader {0}.", shaderName);
// #endif
}
return await MaterialFactory.DefaultCreateMaterialAsync(awaitCaller, gltf, m_index, getTexture);
}