Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Texture remapping fixes and extraction #111

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 114 additions & 3 deletions Editor/Scripts/GLTFImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@
using System.Collections.Generic;
using System.Linq;
using System;
using System.Text.RegularExpressions;
using Object = UnityEngine.Object;
using UnityGLTF.Loader;
using GLTF.Schema;
using GLTF;
using UnityGLTF.Plugins;
#if UNITY_2020_2_OR_NEWER
using UnityEditor.AssetImporters;
using UnityEngine.Rendering;
using UnityGLTF.Cache;
#else
using UnityEditor.Experimental.AssetImporters;
#endif
Expand All @@ -48,7 +49,7 @@ namespace UnityGLTF
#endif
public class GLTFImporter : ScriptedImporter, IGLTFImportRemap
{
private const int ImporterVersion = 9;
private const int ImporterVersion = 11;

private static void EnsureShadersAreLoaded()
{
Expand Down Expand Up @@ -88,6 +89,7 @@ private static void EnsureShadersAreLoaded()
[SerializeField] internal bool _readWriteEnabled = true;
[SerializeField] internal bool _generateColliders = false;
[SerializeField] internal bool _swapUvs = false;

[SerializeField] internal bool _generateLightmapUVs = false;
[SerializeField] internal GLTFImporterNormals _importNormals = GLTFImporterNormals.Import;
[SerializeField] internal GLTFImporterNormals _importTangents = GLTFImporterNormals.Import;
Expand All @@ -108,6 +110,7 @@ private static void EnsureShadersAreLoaded()
// asset remapping
[SerializeField] internal Material[] m_Materials = new Material[0];
[SerializeField] internal Texture[] m_Textures = new Texture[0];
[SerializeField] internal string[] m_OrgTexturesNames = new string[0];
[SerializeField] internal bool m_HasSceneData = true;
[SerializeField] internal bool m_HasAnimationData = true;
[SerializeField] internal bool m_HasMaterialData = true;
Expand Down Expand Up @@ -143,8 +146,10 @@ internal enum GLTFImporterTextureCompressionQuality
[Serializable]
public class TextureInfo
{
public int imageIndex;
public Texture2D texture;
public bool shouldBeLinear;
public bool isExtractable;
}

[Serializable]
Expand Down Expand Up @@ -223,6 +228,65 @@ void CheckAndAddDependency(string uri)
return dependencies.Distinct().ToArray();
}

internal static string ValidateFilename(string filename)
{
return Regex.Replace(filename, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled);
}

public string ExtractTexture(TextureInfo textureInfo, string toPath)
{
var projectFilePath = assetPath;

// TODO: replace with GltfImportContext
var importOptions = new ImportOptions
{
DataLoader = new FileLoader(Path.GetDirectoryName(projectFilePath)),
AnimationMethod = _importAnimations,
AnimationLoopTime = _animationLoopTime,
AnimationLoopPose = _animationLoopPose,
//ImportContext = context,
SwapUVs = _swapUvs,
ImportNormals = _importNormals,
ImportTangents = _importTangents
};

string resultPath = "";
using (var stream = File.OpenRead(projectFilePath))
{
GLTFParser.ParseJson(stream, out var gltfRoot);
stream.Position = 0; // Make sure the read position is changed back to the beginning of the file
var loader = new GLTFSceneImporter(gltfRoot, stream, importOptions);
loader.LoadUnreferencedImagesAndMaterials = true;
loader.MaximumLod = _maximumLod;
loader.IsMultithreaded = false;

AsyncHelpers.RunSync( () =>
loader.SetupLoad(async () =>
{
var imageIndex = textureInfo.imageIndex;
if (imageIndex >= 0 && imageIndex < loader.Root.Images.Count)
{
var result = await loader.GetImageFileExtensionAndData(gltfRoot.Images[imageIndex]);
var fileName = ValidateFilename(textureInfo.texture.name);
var relativePath = Path.Combine(toPath, fileName + result.Item1);
relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar);
relativePath = relativePath.Replace('\\', Path.DirectorySeparatorChar);

var fullPath = Path.GetFullPath(relativePath);
await File.WriteAllBytesAsync(fullPath, result.Item2);
resultPath = relativePath;
}
else
{
Debug.LogError("Invalid image index " + imageIndex);
}

}));
}

return resultPath;
}

public override void OnImportAsset(AssetImportContext ctx)
{
var plugins = new List<GltfImportPluginContext>();
Expand Down Expand Up @@ -562,6 +626,26 @@ string GetUniqueName(string desiredName)
.Where(x => x)
.Union(invalidTextures).Distinct().ToList();

m_OrgTexturesNames = new string[textures.Count];

for (int i = 0; i < textures.Count; i++)
{
bool found = false;
for (int j = 0; j < importer.TextureCache.Length; j++)
{
if (textures[i] == importer.TextureCache[j].Texture)
{
found = true;
m_OrgTexturesNames[i] = importer.TextureSourceAssetId[j].name;
break;
}
}

if (!found)
{
m_OrgTexturesNames[i] = textures[i].name;
}
}
// if we're not importing materials or textures, we can clear the lists
// so that no assets are actually created.
if (!_importMaterials)
Expand Down Expand Up @@ -654,6 +738,7 @@ string GetUniqueName(string desiredName)

m_Materials = materials.ToArray();
m_Textures = textures.ToArray();

m_HasSceneData = gltfScene;
m_HasMaterialData = importer.Root.Materials != null && importer.Root.Materials.Count > 0;
m_HasTextureData = importer.Root.Textures != null && importer.Root.Textures.Count > 0;
Expand Down Expand Up @@ -829,9 +914,35 @@ private void CreateGLTFScene(GLTFImportContext context, out GameObject scene,
_extensions = new List<ExtensionInfo>();
}

bool isTextureExtractable(TextureCacheData tex)
{
var source = GLTFSceneImporter.GetTextureSource(tex.TextureDefinition);
if (source == null)
return false;

if (source.Value == null)
return false;

var uri = source.Value.Uri;

if (string.IsNullOrEmpty(uri))
return true;

if (URIHelper.IsBase64Uri(uri))
return true;

return false;
}

_textures = loader.TextureCache
.Where(x => x != null)
.Select(x => new TextureInfo() { texture = x.Texture, shouldBeLinear = x.IsLinear })
.Select(x => new TextureInfo()
{
texture = x.Texture,
shouldBeLinear = x.IsLinear,
imageIndex = GLTFSceneImporter.GetTextureSourceId(x.TextureDefinition),
isExtractable = isTextureExtractable(x)
})
.ToList();

scene = loader.LastLoadedScene;
Expand Down
128 changes: 78 additions & 50 deletions Editor/Scripts/GLTFImporterInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private void RemappingUI<T>(GLTFImporter t, SerializedProperty importedData, str
// TODO this also counts old remaps that are not used anymore
var remapCount = externalObjectMap.Values.Count(x => x is T);

void ExtractAsset(T subAsset, bool importImmediately)
void ExtractAsset(T subAsset, bool importImmediately, int index = -1)
{
if (!subAsset) return;
var filename = SanitizePath(subAsset.name);
Expand All @@ -225,11 +225,27 @@ void ExtractAsset(T subAsset, bool importImmediately)
var destinationPath = dirName + "/" + filename + fileExtension;
var assetPath = AssetDatabase.GetAssetPath(subAsset);

var clone = Instantiate(subAsset);
AssetDatabase.CreateAsset(clone, destinationPath);
if (subAsset is Texture)
{
var newFile = t.ExtractTexture(t.Textures[index], dirName);

var assetImporter = AssetImporter.GetAtPath(assetPath);
assetImporter.AddRemap(new AssetImporter.SourceAssetIdentifier(subAsset), clone);
if (string.IsNullOrEmpty(newFile))
return;

AssetDatabase.ImportAsset(newFile, ImportAssetOptions.ForceUpdate);
var newImage = AssetDatabase.LoadAssetAtPath<Texture2D>(newFile);
//newImage.name = Path.GetFileNameWithoutExtension(newFile);
var assetImporter = AssetImporter.GetAtPath(assetPath);
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
assetImporter.AddRemap(new AssetImporter.SourceAssetIdentifier(subAsset), newImage);
}
else
{
var clone = Instantiate(subAsset);
AssetDatabase.CreateAsset(clone, destinationPath);
var assetImporter = AssetImporter.GetAtPath(assetPath);
assetImporter.AddRemap(new AssetImporter.SourceAssetIdentifier(subAsset), clone);
}

if (importImmediately)
{
Expand All @@ -243,53 +259,61 @@ void ExtractAsset(T subAsset, bool importImmediately)
SessionState.SetBool(remapFoldoutKey, remapFoldout);
if (remapFoldout)
{
if (remapCount > 0)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginHorizontal();

if (GUILayout.Button("Restore all " + subDirectoryName))
if (remapCount > 0 && GUILayout.Button("Restore all " + subDirectoryName))
{
for (var i = 0; i < importedData.arraySize; i++)
{
for (var i = 0; i < importedData.arraySize; i++)
{
var mat = importedData.GetArrayElementAtIndex(i).objectReferenceValue as T;
if (!mat) continue;
t.RemoveRemap(new AssetImporter.SourceAssetIdentifier(mat));
}
var mat = importedData.GetArrayElementAtIndex(i).objectReferenceValue as T;
if (!mat) continue;
t.RemoveRemap(new AssetImporter.SourceAssetIdentifier(mat));
}

// also remove all old remaps
var oldRemaps = externalObjectMap.Where(x => x.Value is T).ToList();
foreach (var oldRemap in oldRemaps)
{
t.RemoveRemap(oldRemap.Key);
}
// also remove all old remaps
var oldRemaps = externalObjectMap.Where(x => x.Value is T).ToList();
foreach (var oldRemap in oldRemaps)
{
t.RemoveRemap(oldRemap.Key);
}
}

if (typeof(T) == typeof(Material) && GUILayout.Button("Extract all " + subDirectoryName))
if (typeof(T) == typeof(Material) && GUILayout.Button("Extract all " + subDirectoryName))
{
var materials = new T[importedData.arraySize];
for (var i = 0; i < importedData.arraySize; i++)
materials[i] = importedData.GetArrayElementAtIndex(i).objectReferenceValue as T;

for (var i = 0; i < materials.Length; i++)
{
var materials = new T[importedData.arraySize];
for (var i = 0; i < importedData.arraySize; i++)
materials[i] = importedData.GetArrayElementAtIndex(i).objectReferenceValue as T;
if (!materials[i]) continue;

for (var i = 0; i < materials.Length; i++)
{
if (!materials[i]) continue;
AssetDatabase.StartAssetEditing();
ExtractAsset(materials[i], false);
AssetDatabase.StopAssetEditing();
var assetPath = AssetDatabase.GetAssetPath(target);
AssetDatabase.WriteImportSettingsIfDirty(assetPath);
AssetDatabase.Refresh();
}
}
bool canExtracted = materials[i] is Material || (materials[i] is Texture && t.Textures[i].isExtractable);
if (!canExtracted) continue;

EditorGUILayout.EndHorizontal();
AssetDatabase.StartAssetEditing();
ExtractAsset(materials[i], false);
AssetDatabase.StopAssetEditing();
var assetPath = AssetDatabase.GetAssetPath(target);
AssetDatabase.WriteImportSettingsIfDirty(assetPath);
AssetDatabase.Refresh();
}
}

EditorGUILayout.EndHorizontal();


for (var i = 0; i < importedData.arraySize; i++)
{
var mat = importedData.GetArrayElementAtIndex(i).objectReferenceValue as T;
if (!mat) continue;
var id = new AssetImporter.SourceAssetIdentifier(mat);

AssetImporter.SourceAssetIdentifier id;
if (mat is Texture2D && i < t.m_OrgTexturesNames.Length)
id = new AssetImporter.SourceAssetIdentifier(typeof(Texture2D), t.m_OrgTexturesNames[i]);
else
id = new AssetImporter.SourceAssetIdentifier(mat);

externalObjectMap.TryGetValue(id, out var remap);
EditorGUILayout.BeginHorizontal();
// EditorGUILayout.ObjectField(/*mat.name,*/ mat, typeof(Material), false);
Expand All @@ -303,25 +327,29 @@ void ExtractAsset(T subAsset, bool importImmediately)
t.RemoveRemap(id);
}

if (!remap)
bool canExtracted = mat is Material || (mat is Texture && t.Textures[i].isExtractable);
if (canExtracted)
{
if (GUILayout.Button("Extract", GUILayout.Width(60)))
if (!remap)
{
ExtractAsset(mat, true);
GUIUtility.ExitGUI();
if (GUILayout.Button("Extract", GUILayout.Width(60)))
{
ExtractAsset(mat, true, i);
GUIUtility.ExitGUI();
}
}
}
else
{
if (GUILayout.Button("Restore", GUILayout.Width(60)))
else
{
t.RemoveRemap(id);
if (GUILayout.Button("Restore", GUILayout.Width(60)))
{
t.RemoveRemap(id);
#if UNITY_2022_2_OR_NEWER
SaveChanges();
SaveChanges();
#else
ApplyAndImport();
ApplyAndImport();
#endif
GUIUtility.ExitGUI();
GUIUtility.ExitGUI();
}
}
}

Expand Down
Loading