using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using GLTF;
using GLTF.Schema;
using UnityEngine;
using UnityGLTF.Cache;
using UnityGLTF.Extensions;
using UnityEditor;
using Newtonsoft.Json.Linq;
namespace SeinJS
{
///
/// Editor windows to load a GLTF scene in editor
///
///
public class EditorImporter
{
protected class MeshAttrsTemp
{
public List vertices = new List();
public List normals = new List();
public List uv = new List();
public List uv2 = new List();
public List uv3 = new List();
public List uv4 = new List();
public List colors = new List();
public List tangents = new List();
public List materials = new List();
public List triangles = new List();
public List boneWeights = new List();
public List bindposes = new List();
public List morphNames = new List();
public List> morphVertices = new List>();
public List> morphNormals = new List>();
public List> morphTangents = new List>();
}
public bool _useGLTFMaterial = false;
bool _isDone = false;
// Import paths and options
private string _projectDirectoryPath;
private string _gltfDirectoryPath;
private string _glTFPath = "";
private bool _addToCurrentScene;
private bool _generateLightMapUvs = false;
// GLTF data
private byte[] _glTFData;
protected GLTFRoot _root;
AssetManager _assetManager;
private int _nbParsedNodes;
private GameObject _sceneObject;
public UnityEngine.Material defaultMaterial;
protected AssetCache _assetCache;
private TaskManager _taskManager;
private bool _userStopped = false;
private string _currentSampleName = "";
private List _assetsToRemove;
Dictionary _importedObjects;
Dictionary>_skinIndexToGameObjects;
Dictionary _skinObjectsStore = new Dictionary();
public delegate void RefreshWindow();
public delegate void ProgressCallback(string step, int current, int total);
private RefreshWindow _finishCallback;
private ProgressCallback _progressCallback;
protected const string Base64StringInitializer = "^data:[a-z-]+/[a-z-]+;base64,";
public GLTFRoot root
{
get
{
return _root;
}
}
public GameObject sceneObject
{
get
{
return _sceneObject;
}
}
public string gltfDirectoryPath
{
get
{
return _gltfDirectoryPath;
}
}
public string importDirectoryPath
{
get
{
return _projectDirectoryPath;
}
}
public TaskManager taskManager
{
get
{
return _taskManager;
}
}
public AssetCache assetCache
{
get
{
return _assetCache;
}
}
///
/// Constructor
///
public EditorImporter()
{
Initialize();
}
///
/// Constructors setting the delegate function to call after each iteration
///
/// The function to call after each iteration (usually Repaint())
public EditorImporter(ProgressCallback progressCallback, RefreshWindow finish=null)
{
_progressCallback = progressCallback;
_finishCallback = finish;
Initialize();
}
///
/// Initializes all the structures and objects
///
public void Initialize()
{
_importedObjects = new Dictionary();
_skinIndexToGameObjects = new Dictionary>();
_isDone = true;
_taskManager = new TaskManager();
_assetsToRemove = new List();
defaultMaterial = new UnityEngine.Material(Shader.Find("Sein/PBR"));
}
///
/// Setup importer for an import
///
/// Absolute path to the glTF file to import
/// Path in current project where to import the model
/// Name of the model prefab to create
public void setupForPath(string gltfPath, string importPath, string modelName, bool addScene=false, bool generateLightMapUvs = false)
{
_glTFPath = gltfPath;
_gltfDirectoryPath = Path.GetDirectoryName(_glTFPath);
_currentSampleName = modelName.Length > 0 ? modelName : "GLTFScene";
_projectDirectoryPath = importPath;
_assetManager = new AssetManager(_projectDirectoryPath, _currentSampleName);
_importedObjects.Clear();
_addToCurrentScene = addScene;
_generateLightMapUvs = generateLightMapUvs;
_skinIndexToGameObjects.Clear();
_skinObjectsStore.Clear();
}
public void Update()
{
if(!_isDone)
{
if (_userStopped)
{
_userStopped = false;
Clear();
_isDone = true;
}
else
{
if (_taskManager != null && _taskManager.play())
{
// Do stuff
}
else
{
_isDone = true;
finishImport();
}
}
}
}
public void Load(bool useGLTFMaterial=false)
{
_isDone = false;
_userStopped = false;
_useGLTFMaterial = useGLTFMaterial;
ExtensionManager.BeforeImport();
LoadFile();
LoadGLTFScene();
}
// Private
private void checkValidity()
{
if (_taskManager == null)
{
_taskManager = new TaskManager();
}
}
private void LoadFile(int sceneIndex = -1)
{
_glTFData = File.ReadAllBytes(_glTFPath);
_root = GLTFParser.ParseJson(_glTFData);
}
private void LoadGLTFScene(int sceneIndex = -1)
{
Scene scene;
if (sceneIndex >= 0 && sceneIndex < _root.Scenes.Count)
{
scene = _root.Scenes[sceneIndex];
}
else
{
scene = _root.GetDefaultScene();
}
if (scene == null)
{
throw new Exception("No default scene in gltf file.");
}
_assetManager.hasAnimatorExtension = _root.ExtensionsUsed == null ? false : _root.ExtensionsUsed.Contains(ExtensionManager.GetExtensionName(typeof(Sein_animatorExtensionFactory)));
_assetCache = new AssetCache(
_root.Images != null ? _root.Images.Count : 0,
_root.Textures != null ? _root.Textures.Count : 0,
_root.Materials != null ? _root.Materials.Count : 0,
_root.Buffers != null ? _root.Buffers.Count : 0,
_root.Meshes != null ? _root.Meshes.Count : 0
);
// Load dependencies
LoadBuffersEnum();
if (_root.Images != null)
LoadImagesEnum();
if (_root.Textures != null)
SetupTexturesEnum();
if (_root.Materials != null)
LoadMaterialsEnum();
var extensions = _root.Extensions;
if (extensions != null)
{
foreach (var extension in extensions)
{
ExtensionManager.Import(extension.Key, this, extension.Value);
}
}
LoadMeshesEnum();
LoadSceneEnum();
LoadSkinnedMeshesEnum();
if (_root.Animations != null && _root.Animations.Count > 0)
LoadAnimationsEnum();
if (_root.Skins != null && _root.Skins.Count > 0)
LoadSkinsEnum();
LoadNodesExtensionsEnum();
}
private void LoadBuffersEnum()
{
_taskManager.addTask(LoadBuffers());
}
private void LoadImagesEnum()
{
_taskManager.addTask(LoadImages());
}
private void SetupTexturesEnum()
{
_taskManager.addTask(SetupTextures());
}
private void LoadMaterialsEnum()
{
_taskManager.addTask(LoadMaterials());
}
private void LoadMeshesEnum()
{
_taskManager.addTask(LoadMeshes());
}
private void LoadSceneEnum()
{
_taskManager.addTask(LoadScene());
}
private void LoadSkinnedMeshesEnum()
{
_taskManager.addTask(LoadSkinnedMeshes());
}
private void LoadAnimationsEnum()
{
_taskManager.addTask(LoadAnimations());
}
private void LoadSkinsEnum()
{
_taskManager.addTask(LoadSkins());
}
private void LoadNodesExtensionsEnum()
{
_taskManager.addTask(LoadNodesExtensions());
}
private IEnumerator LoadBuffers()
{
if (_root.Buffers != null)
{
// todo add fuzzing to verify that buffers are before uri
SetProgress("BUFFER", 0, _root.Buffers.Count);
for (int i = 0; i < _root.Buffers.Count; ++i)
{
GLTF.Schema.Buffer buffer = _root.Buffers[i];
if (buffer.Uri != null)
{
LoadBuffer(_gltfDirectoryPath, buffer, i);
}
else //null buffer uri indicates GLB buffer loading
{
byte[] glbBuffer;
GLTFParser.ExtractBinaryChunk(_glTFData, i, out glbBuffer);
_assetCache.BufferCache[i] = glbBuffer;
}
SetProgress("BUFFER", (i + 1), _root.Buffers.Count);
yield return null;
}
}
}
protected virtual void LoadBuffer(string sourceUri, GLTF.Schema.Buffer buffer, int bufferIndex)
{
if (buffer.Uri != null)
{
byte[] bufferData = null;
var uri = buffer.Uri;
var bufferPath = Path.Combine(sourceUri, uri);
bufferData = File.ReadAllBytes(bufferPath);
_assetCache.BufferCache[bufferIndex] = bufferData;
}
}
private IEnumerator LoadImages()
{
for (int i = 0; i < _root.Images.Count; ++i)
{
Image image = _root.Images[i];
LoadImage(_gltfDirectoryPath, image, i);
SetProgress("IMAGE", (i + 1), _root.Images.Count);
yield return null;
}
}
private void LoadImage(string rootPath, Image image, int imageID)
{
if (_assetCache.ImageCache[imageID] == null)
{
if (image.Uri != null)
{
// Is base64 uri ?
var uri = image.Uri;
Regex regex = new Regex(Base64StringInitializer);
Match match = regex.Match(uri);
if (match.Success)
{
var base64Data = uri.Substring(match.Length);
var textureData = Convert.FromBase64String(base64Data);
_assetManager.registerImageFromData(textureData, imageID);
}
else if(File.Exists(Path.Combine(rootPath, uri))) // File is a real file
{
string imagePath = Path.Combine(rootPath, uri);
_assetManager.copyAndRegisterImageInProject(imagePath, imageID);
}
else
{
Debug.Log("Image not found / Unknown image buffer");
}
}
else
{
var bufferView = image.BufferView.Value;
var buffer = bufferView.Buffer.Value;
var data = new byte[bufferView.ByteLength];
var bufferContents = _assetCache.BufferCache[bufferView.Buffer.Id];
System.Buffer.BlockCopy(bufferContents, bufferView.ByteOffset, data, 0, data.Length);
_assetManager.registerImageFromData(data, imageID);
}
}
}
private IEnumerator SetupTextures()
{
for(int i = 0; i < _root.Textures.Count; ++i)
{
SetupTexture(_root.Textures[i], i);
SetProgress("TEXTURE", (i + 1), _root.Textures.Count);
yield return null;
}
}
private void SetupTexture(GLTF.Schema.Texture def, int textureIndex)
{
Texture2D source = _assetManager.getOrCreateTexture(def.Source.Id, textureIndex);
// Default values
var desiredFilterMode = FilterMode.Bilinear;
var desiredWrapMode = UnityEngine.TextureWrapMode.Repeat;
if (def.Sampler != null)
{
var sampler = def.Sampler.Value;
switch (sampler.MinFilter)
{
case MinFilterMode.Nearest:
desiredFilterMode = FilterMode.Point;
break;
case MinFilterMode.Linear:
default:
desiredFilterMode = FilterMode.Bilinear;
break;
}
switch (sampler.WrapS)
{
case GLTF.Schema.WrapMode.ClampToEdge:
desiredWrapMode = UnityEngine.TextureWrapMode.Clamp;
break;
case GLTF.Schema.WrapMode.Repeat:
default:
desiredWrapMode = UnityEngine.TextureWrapMode.Repeat;
break;
}
}
source.filterMode = desiredFilterMode;
source.wrapMode = desiredWrapMode;
var extensions = def.Extensions;
if (extensions != null)
{
foreach (var extension in extensions)
{
ExtensionManager.Import(extension.Key, this, source, def, extension.Value);
}
}
_assetManager.registerTexture(source);
}
private IEnumerator LoadMaterials()
{
for(int i = 0; i < _root.Materials.Count; ++i)
{
var mat = _root.Materials[i];
UnityEngine.Material material;
var extensions = mat.Extensions;
var cmeName = ExtensionManager.GetExtensionName(typeof(Sein_customMaterialExtensionFactory));
if (extensions != null && extensions.ContainsKey(cmeName))
{
material = new UnityEngine.Material(Shader.Find("Sein/PBR"));
ExtensionManager.Import(cmeName, this, material, mat, extensions[cmeName]);
}
else
{
material = CreateUnityMaterial(mat, i);
}
if (extensions != null)
{
foreach (var extension in extensions)
{
if (extension.Key != "KHR_materials_pbrSpecularGlossiness" && extension.Key != cmeName)
{
ExtensionManager.Import(extension.Key, this, material, mat, extension.Value);
}
}
}
material = _assetManager.saveMaterial(material, i);
_assetManager._parsedMaterials.Add(material);
material.hideFlags = HideFlags.None;
SetProgress("MATERIAL", (i + 1), _root.Materials.Count);
yield return null;
}
}
protected virtual UnityEngine.Material CreateUnityMaterial(GLTF.Schema.Material def, int materialIndex)
{
Extension specularGlossinessExtension = null;
bool isSpecularPBR = def.Extensions != null && def.Extensions.TryGetValue("KHR_materials_pbrSpecularGlossiness", out specularGlossinessExtension);
Shader shader = Shader.Find("Sein/PBR");
var material = new UnityEngine.Material(shader);
material.hideFlags = HideFlags.DontUnloadUnusedAsset; // Avoid material to be deleted while being built
material.name = def.Name;
if (isSpecularPBR)
{
material.SetInt("workflow", (int)SeinPBRShaderGUI.Workflow.Specular);
}
else
{
material.SetInt("workflow", (int)SeinPBRShaderGUI.Workflow.Metal);
}
//Transparency
if (def.AlphaMode == AlphaMode.MASK)
{
GLTFUtils.SetupMaterialWithBlendMode(material, GLTFUtils.BlendMode.Cutout);
material.SetFloat("_Mode", (int)SeinPBRShaderGUI.BlendMode.Cutout);
material.SetFloat("_Cutoff", (float)def.AlphaCutoff);
}
else if (def.AlphaMode == AlphaMode.BLEND)
{
GLTFUtils.SetupMaterialWithBlendMode(material, GLTFUtils.BlendMode.Transparent);
material.SetFloat("_Mode", (int)SeinPBRShaderGUI.BlendMode.Transparent);
}
else
{
material.SetFloat("_Mode", (int)SeinPBRShaderGUI.BlendMode.Opaque);
}
if (def.NormalTexture != null)
{
var texture = def.NormalTexture.Index.Id;
Texture2D normalTexture = GetTexture(texture) as Texture2D;
//Automatically set it to normal map
TextureImporter im = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(normalTexture)) as TextureImporter;
im.textureType = TextureImporterType.NormalMap;
if (im.sRGBTexture)
{
im.sRGBTexture = false;
}
im.SaveAndReimport();
material.SetTexture("_normalMap", GetTexture(texture));
material.SetFloat("_normalScale", (float)def.NormalTexture.Scale);
}
if (def.EmissiveTexture != null)
{
//material.EnableKeyword("EMISSION_MAP_ON");
var texture = def.EmissiveTexture.Index.Id;
material.SetTexture("_emissionMap", GetTexture(texture));
material.SetInt("_emissionUV", def.EmissiveTexture.TexCoord);
}
// PBR channels
if (specularGlossinessExtension != null)
{
KHR_materials_pbrSpecularGlossinessExtension pbr = (KHR_materials_pbrSpecularGlossinessExtension)specularGlossinessExtension;
material.SetColor("_baseColor", Utils.ImportColor(pbr.DiffuseFactor));
if (pbr.DiffuseTexture != null)
{
var texture = pbr.DiffuseTexture.Index.Id;
material.SetTexture("_baseColorMap", GetTexture(texture));
}
if (pbr.SpecularGlossinessTexture != null)
{
var textureID = pbr.SpecularGlossinessTexture.Index.Id;
var texture = GetTexture(textureID);
ChangeSRGB(ref texture, true);
material.SetTexture("_specularGlossinessMap", texture);
material.SetFloat("_glossMapScale", (float)pbr.GlossinessFactor);
material.SetFloat("_glossiness", (float)pbr.GlossinessFactor);
}
else
{
material.SetFloat("_glossiness", (float)pbr.GlossinessFactor);
}
Vector3 specularVec3 = pbr.SpecularFactor.ToUnityVector3();
var spec = new Color(specularVec3.x, specularVec3.y, specularVec3.z, 1.0f);
material.SetColor("_specular", Utils.ImportColor(spec));
if (def.OcclusionTexture != null)
{
var textureID = pbr.SpecularGlossinessTexture.Index.Id;
var texture = GetTexture(textureID);
material.SetFloat("_occlusionStrength", (float)def.OcclusionTexture.Strength);
ChangeSRGB(ref texture, false);
material.SetTexture("_occlusionMap", texture);
}
}
else if (def.PbrMetallicRoughness != null)
{
var pbr = def.PbrMetallicRoughness;
material.SetColor("_baseColor", Utils.ImportColor(pbr.BaseColorFactor));
if (pbr.BaseColorTexture != null)
{
var textureID = pbr.BaseColorTexture.Index.Id;
var texture = GetTexture(textureID);
ChangeSRGB(ref texture, true);
material.SetTexture("_baseColorMap", texture);
}
material.SetFloat("_metallic", (float)pbr.MetallicFactor);
material.SetFloat("_roughness", (float)pbr.RoughnessFactor);
if (pbr.MetallicRoughnessTexture != null)
{
var texture = pbr.MetallicRoughnessTexture.Index.Id;
Texture2D metalRoughnessTexture = GetTexture(texture) as Texture2D;
Texture2D occlusionTexture = null;
if (def.OcclusionTexture != null)
{
if (def.OcclusionTexture.Index.Id == pbr.MetallicRoughnessTexture.Index.Id)
{
occlusionTexture = metalRoughnessTexture;
}
else
{
occlusionTexture = GetTexture(def.OcclusionTexture.Index.Id) as Texture2D;
}
}
Texture2D ormTexture = combineMetalRoughOcclusionTexture(metalRoughnessTexture, occlusionTexture);
material.SetTexture("_metallicMap", ormTexture);
material.SetTexture("_roughnessMap", ormTexture);
if (def.OcclusionTexture != null)
{
material.SetFloat("_occlusionStrength", (float)def.OcclusionTexture.Strength);
material.SetTexture("_occlusionMap", ormTexture);
}
}
}
material.SetColor("_emission", Utils.ImportColor(def.EmissiveFactor));
return material;
}
public UnityEngine.Texture2D combineMetalRoughOcclusionTexture(Texture2D metalRoughnessTexture, Texture2D occlusionTexture)
{
string metalRoughnessTexturePath = AssetDatabase.GetAssetPath(metalRoughnessTexture);
string occlusionTexturePath = AssetDatabase.GetAssetPath(occlusionTexture);
if (!_assetsToRemove.Contains(metalRoughnessTexturePath))
{
_assetsToRemove.Add(metalRoughnessTexturePath);
}
if (!_assetsToRemove.Contains(occlusionTexturePath))
{
_assetsToRemove.Add(occlusionTexturePath);
}
int width = metalRoughnessTexture.width;
int height = metalRoughnessTexture.height;
var ormTexture = GLTFTextureUtils.packOcclusionMetalRough(metalRoughnessTexture, metalRoughnessTexture, occlusionTexture, Path.GetFileNameWithoutExtension(metalRoughnessTexturePath));
ormTexture = _assetManager.saveTexture(ormTexture);
// Delete original texture
AssetDatabase.Refresh();
ChangeSRGB(ref ormTexture, false);
return ormTexture;
}
public void ChangeSRGB(ref UnityEngine.Texture2D tex, bool isSRGB)
{
TextureImporter im = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(tex)) as TextureImporter;
if (im.sRGBTexture == isSRGB)
{
return;
}
im.sRGBTexture = isSRGB;
im.SaveAndReimport();
}
public Texture2D GetTexture(int index)
{
return _assetManager.getTexture(index);
}
private UnityEngine.Material getMaterial(int index)
{
return _assetManager.getMaterial(index);
}
private IEnumerator LoadMeshes()
{
if (_root.Meshes == null)
{
yield break;
}
for(int i = 0; i < _root.Meshes.Count; ++i)
{
CreateMeshObject(_root.Meshes[i], i);
SetProgress("MESH", (i + 1), _root.Meshes.Count);
yield return null;
}
}
protected virtual void CreateMeshObject(GLTF.Schema.Mesh mesh, int meshId)
{
var mainMesh = new UnityEngine.Mesh();
var attrs = new MeshAttrsTemp();
mainMesh.subMeshCount = mesh.Primitives.Count;
var meshTable = new Dictionary>();
for (int i = 0; i < mesh.Primitives.Count; ++i)
{
var primitive = mesh.Primitives[i];
var posBufferId = primitive.Attributes[SemanticProperties.POSITION].Value.BufferView.Id;
if (!meshTable.ContainsKey(posBufferId))
{
meshTable.Add(posBufferId, new List());
}
meshTable[posBufferId].Add(primitive);
}
var meshBaseIndexTable = new Dictionary();
int baseIndex = 0;
foreach (var pair in meshTable)
{
var i = 0;
foreach (var primitive in pair.Value)
{
meshBaseIndexTable.Add(primitive, baseIndex);
i += primitive.Attributes[SemanticProperties.POSITION].Value.Count;
}
baseIndex += i;
}
int index = 0;
foreach (var pair in meshBaseIndexTable)
{
var primitive = pair.Key;
var meshBaseIndex = pair.Value;
CreateMeshPrimitive(mainMesh, ref attrs, mesh, primitive, mesh.Name, meshId, index, meshBaseIndex); // Converted to mesh
index += 1;
}
if (mainMesh.uv2 == null && _generateLightMapUvs)
{
Unwrapping.GenerateSecondaryUVSet(mainMesh);
}
if (attrs.vertices.Count > 0)
{
mainMesh.vertices = attrs.vertices.ToArray();
}
if (attrs.normals.Count > 0)
{
mainMesh.normals = attrs.normals.ToArray();
}
if (attrs.uv.Count > 0)
{
mainMesh.uv = attrs.uv.ToArray();
}
if (attrs.uv2.Count > 0)
{
mainMesh.uv2 = attrs.uv2.ToArray();
}
if (attrs.uv3.Count > 0)
{
mainMesh.uv3 = attrs.uv3.ToArray();
}
if (attrs.uv4.Count > 0)
{
mainMesh.uv4 = attrs.uv4.ToArray();
}
if (attrs.colors.Count > 0)
{
mainMesh.colors = attrs.colors.ToArray();
}
if (attrs.tangents.Count > 0)
{
mainMesh.tangents = attrs.tangents.ToArray();
}
for (int i = 0; i < attrs.triangles.Count; i += 1)
{
mainMesh.SetTriangles(attrs.triangles[i], i);
}
if (attrs.boneWeights.Count > 0)
{
mainMesh.boneWeights = attrs.boneWeights.ToArray();
}
if (attrs.bindposes.Count > 0)
{
mainMesh.bindposes = attrs.bindposes.ToArray();
}
if (attrs.morphNames.Count > 0)
{
for (int i = 0; i < attrs.morphNames.Count; i += 1)
{
mainMesh.AddBlendShapeFrame(attrs.morphNames[i], 1.0f, attrs.morphVertices[i].ToArray(), attrs.morphNormals[i].ToArray(), attrs.morphTangents[i].ToArray());
}
}
mainMesh.RecalculateBounds();
mainMesh.RecalculateTangents();
mainMesh = _assetManager.saveMesh(mainMesh, mesh.Name + "-" + meshId);
var extensions = mesh.Extensions;
if (extensions != null)
{
foreach (var extension in extensions)
{
ExtensionManager.Import(extension.Key, this, mainMesh, mesh, extension.Value);
}
}
_assetManager.addPrimitiveMeshData(meshId, mainMesh, attrs.materials);
}
protected virtual void CreateMeshPrimitive(UnityEngine.Mesh mainMesh, ref MeshAttrsTemp attrs, GLTF.Schema.Mesh m, MeshPrimitive primitive, string meshName, int meshID, int primitiveIndex, int baseIndex)
{
var meshAttributes = BuildMeshAttributes(primitive, meshID, primitiveIndex);
var vertexCount = primitive.Attributes[SemanticProperties.POSITION].Value.Count;
if (primitive.Attributes.ContainsKey(SemanticProperties.POSITION))
{
attrs.vertices.AddRange(meshAttributes[SemanticProperties.POSITION].AccessorContent.AsVertices.ToUnityVector3());
}
if (primitive.Attributes.ContainsKey(SemanticProperties.NORMAL))
{
attrs.normals.AddRange(meshAttributes[SemanticProperties.NORMAL].AccessorContent.AsNormals.ToUnityVector3());
}
if (primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(0)))
{
attrs.uv.AddRange(meshAttributes[SemanticProperties.TexCoord(0)].AccessorContent.AsTexcoords.ToUnityVector2());
}
if (primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(1)))
{
attrs.uv2.AddRange(meshAttributes[SemanticProperties.TexCoord(1)].AccessorContent.AsTexcoords.ToUnityVector2());
}
if (primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(2)))
{
attrs.uv3.AddRange(meshAttributes[SemanticProperties.TexCoord(2)].AccessorContent.AsTexcoords.ToUnityVector2());
}
if (primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(3)))
{
attrs.uv3.AddRange(meshAttributes[SemanticProperties.TexCoord(3)].AccessorContent.AsTexcoords.ToUnityVector2());
}
if (primitive.Attributes.ContainsKey(SemanticProperties.Color(0)))
{
attrs.colors.AddRange(meshAttributes[SemanticProperties.Color(0)].AccessorContent.AsColors.ToUnityColor());
}
if (primitive.Attributes.ContainsKey(SemanticProperties.TANGENT))
{
attrs.tangents.AddRange(meshAttributes[SemanticProperties.TANGENT].AccessorContent.AsTangents.ToUnityVector4());
}
int[] indices = null;
if (primitive.Indices != null)
{
indices = meshAttributes[SemanticProperties.INDICES].AccessorContent.AsTriangles;
}
else
{
indices = MeshPrimitive.GenerateTriangles(vertexCount);
}
if (baseIndex > 0)
{
for (int i = 0; i < indices.Length; i += 1)
{
indices[i] += baseIndex;
}
}
attrs.triangles.Add(indices);
if (primitive.Attributes.ContainsKey(SemanticProperties.JOINT) && primitive.Attributes.ContainsKey(SemanticProperties.WEIGHT))
{
Vector4[] bones = new Vector4[1];
Vector4[] weights = new Vector4[1];
LoadSkinnedMeshAttributes(meshID, primitiveIndex, ref bones, ref weights);
if(bones.Length != vertexCount || weights.Length != vertexCount)
{
Debug.LogError("Not enough skinning data (bones:" + bones.Length + " weights:" + weights.Length + " verts:" + vertexCount + ")");
return;
}
BoneWeight[] bws = new BoneWeight[vertexCount];
int maxBonesIndex = 0;
for (int i = 0; i < bws.Length; ++i)
{
// Unity seems expects the the sum of weights to be 1.
float[] normalizedWeights = GLTFUtils.normalizeBoneWeights(weights[i]);
bws[i].boneIndex0 = (int)bones[i].x;
bws[i].weight0 = normalizedWeights[0];
bws[i].boneIndex1 = (int)bones[i].y;
bws[i].weight1 = normalizedWeights[1];
bws[i].boneIndex2 = (int)bones[i].z;
bws[i].weight2 = normalizedWeights[2];
bws[i].boneIndex3 = (int)bones[i].w;
bws[i].weight3 = normalizedWeights[3];
maxBonesIndex = (int)Mathf.Max(maxBonesIndex, bones[i].x, bones[i].y, bones[i].z, bones[i].w);
}
attrs.boneWeights.AddRange(bws);
// initialize inverseBindMatrix array with identity matrix in order to output a valid mesh object
Matrix4x4[] bindposes = new Matrix4x4[maxBonesIndex + 1];
for(int j=0; j <= maxBonesIndex; ++j)
{
bindposes[j] = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one);
}
attrs.bindposes.AddRange(bindposes);
}
if(primitive.Targets != null && primitive.Targets.Count > 0)
{
for (int b = 0; b < primitive.Targets.Count; ++b)
{
if (primitiveIndex == 0)
{
attrs.morphVertices.Add(new List());
attrs.morphNormals.Add(new List());
attrs.morphTangents.Add(new List());
}
Vector3[] deltaVertices = new Vector3[primitive.Targets[b]["POSITION"].Value.Count];
Vector3[] deltaNormals = new Vector3[primitive.Targets[b]["POSITION"].Value.Count];
Vector3[] deltaTangents = new Vector3[primitive.Targets[b]["POSITION"].Value.Count];
if(primitive.Targets[b].ContainsKey("POSITION"))
{
NumericArray num = new NumericArray();
deltaVertices = primitive.Targets[b]["POSITION"].Value.AsVector3Array(ref num, _assetCache.BufferCache[0], false).ToUnityVector3(true);
}
if (primitive.Targets[b].ContainsKey("NORMAL"))
{
NumericArray num = new NumericArray();
deltaNormals = primitive.Targets[b]["NORMAL"].Value.AsVector3Array(ref num, _assetCache.BufferCache[0], true).ToUnityVector3(true);
}
//if (primitive.Targets[b].ContainsKey("TANGENT"))
//{
// NumericArray num = new NumericArray();
// deltaTangents = primitive.Targets[b]["TANGENT"].Value.AsVector3Array(ref num, _assetCache.BufferCache[0], true).ToUnityVector3(true);
//}
string blendShapeName;
if (m.Extras != null && m.Extras.Value("targetNames") != null)
{
blendShapeName = (string)m.Extras.Value("targetNames")[b];
}
else
{
blendShapeName = GLTFUtils.buildBlendShapeName(meshID, b);
}
if (primitiveIndex == 0) {
attrs.morphNames.Add(blendShapeName);
}
attrs.morphVertices[b].AddRange(deltaVertices);
attrs.morphNormals[b].AddRange(deltaNormals);
attrs.morphTangents[b].AddRange(deltaTangents);
}
}
UnityEngine.Material material = primitive.Material != null && primitive.Material.Id >= 0 ? getMaterial(primitive.Material.Id) : defaultMaterial;
attrs.materials.Add(material);
}
protected virtual Dictionary BuildMeshAttributes(MeshPrimitive primitive, int meshID, int primitiveIndex)
{
Dictionary attributeAccessors = new Dictionary(primitive.Attributes.Count + 1);
foreach (var attributePair in primitive.Attributes)
{
AttributeAccessor AttributeAccessor = new AttributeAccessor()
{
AccessorId = attributePair.Value,
Buffer = _assetCache.BufferCache[attributePair.Value.Value.BufferView.Value.Buffer.Id]
};
attributeAccessors[attributePair.Key] = AttributeAccessor;
}
if (primitive.Indices != null)
{
AttributeAccessor indexBuilder = new AttributeAccessor()
{
AccessorId = primitive.Indices,
Buffer = _assetCache.BufferCache[primitive.Indices.Value.BufferView.Value.Buffer.Id]
};
attributeAccessors[SemanticProperties.INDICES] = indexBuilder;
}
GLTFHelpers.BuildMeshAttributes(ref attributeAccessors);
return attributeAccessors;
}
private IEnumerator LoadScene(int sceneIndex = -1)
{
Scene scene;
_nbParsedNodes = 0;
if (sceneIndex >= 0 && sceneIndex < _root.Scenes.Count)
{
scene = _root.Scenes[sceneIndex];
}
else
{
scene = _root.GetDefaultScene();
}
if (scene == null)
{
throw new Exception("No default scene in gltf file.");
}
if (scene.Nodes.Count == 1)
{
var node = scene.Nodes[0];
var name = GLTFUtils.cleanName(_currentSampleName);
_sceneObject = CreateNode(node.Value, node.Id);
_sceneObject.name = name;
}
else
{
_sceneObject = createGameObject(_currentSampleName);
foreach (var node in scene.Nodes)
{
var nodeObj = CreateNode(node.Value, node.Id);
nodeObj.transform.SetParent(_sceneObject.transform, false);
}
}
yield return null;
}
private IEnumerator LoadSkinnedMeshes()
{
int i = 0;
foreach (var pair in _skinObjectsStore)
{
SetProgress("SKINNEDMESH", i, _skinObjectsStore.Count);
var node = pair.Key;
var go = pair.Value;
BuildSkinnedMesh(go, node.Skin.Value, node.Mesh.Id);
_skinIndexToGameObjects[node.Skin.Id].Add(go.GetComponent());
i += 1;
}
yield return null;
}
private IEnumerator LoadAnimations()
{
List clips = new List();
var count = _root.Animations.Count;
for (int i = 0; i < count; i++)
{
AnimationClip clip = new AnimationClip();
clip.wrapMode = UnityEngine.WrapMode.Loop;
LoadAnimation(_root.Animations[i], i, clip);
SetProgress("ANIMATION", (i + 1), _root.Animations.Count);
_assetManager.saveAnimationClip(clip);
clips.Add(clip);
if (i == count - 1)
{
_assetManager.createAnimatorAsset(clips);
}
yield return null;
}
}
private void LoadAnimation(GLTF.Schema.Animation gltfAnimation, int index, AnimationClip clip)
{
Regex rgx = new Regex(@"\S+@");
string name = gltfAnimation.Name;
if (name != null)
{
name = gltfAnimation.Name.Replace('|', '-').Replace('.', '-');
name = rgx.Replace(name, "");
}
clip.name = name != null && name.Length > 0 ? name : "GLTFAnimation_" + index;
for (int i=0; i < gltfAnimation.Channels.Count; ++i)
{
addGLTFChannelDataToClip(gltfAnimation.Channels[i], clip);
}
clip.EnsureQuaternionContinuity();
Sein_animatorExtensionFactory.IMPORTED_CLIPS.Add(gltfAnimation.Name, clip);
}
private void addGLTFChannelDataToClip(GLTF.Schema.AnimationChannel channel, AnimationClip clip)
{
int animatedNodeIndex = channel.Target.Node.Id;
if (!_importedObjects.ContainsKey(animatedNodeIndex))
{
Debug.Log("Node '" + animatedNodeIndex + "' found for animation, aborting.");
}
Transform animatedNode = _importedObjects[animatedNodeIndex].transform;
string nodePath = AnimationUtility.CalculateTransformPath(animatedNode, _sceneObject.transform);
bool isStepInterpolation = channel.Sampler.Value.Interpolation != InterpolationType.LINEAR;
byte[] timeBufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id];
float[] times = GLTFHelpers.ParseKeyframeTimes(channel.Sampler.Value.Input.Value, timeBufferData);
if (channel.Target.Path == GLTFAnimationChannelPath.translation || channel.Target.Path == GLTFAnimationChannelPath.scale)
{
byte[] bufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id];
GLTF.Math.Vector3[] keyValues = GLTFHelpers.ParseVector3Keyframes(channel.Sampler.Value.Output.Value, bufferData);
if (keyValues == null)
return;
Vector3[] values = keyValues.ToUnityVector3();
AnimationCurve[] vector3Curves = GLTFUtils.createCurvesFromArrays(times, values, isStepInterpolation, channel.Target.Path == GLTFAnimationChannelPath.translation);
if (channel.Target.Path == GLTFAnimationChannelPath.translation)
GLTFUtils.addTranslationCurvesToClip(vector3Curves, nodePath, ref clip);
else
GLTFUtils.addScaleCurvesToClip(vector3Curves, nodePath, ref clip);
}
else if (channel.Target.Path == GLTFAnimationChannelPath.rotation)
{
byte[] bufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id];
Vector4[] values = GLTFHelpers.ParseRotationKeyframes(channel.Sampler.Value.Output.Value, bufferData).ToUnityVector4();
AnimationCurve[] rotationCurves = GLTFUtils.createCurvesFromArrays(times, values, isStepInterpolation);
GLTFUtils.addRotationCurvesToClip(rotationCurves, nodePath, ref clip);
}
else if(channel.Target.Path == GLTFAnimationChannelPath.weights)
{
List morphTargets = new List();
int meshIndex = _root.Nodes[animatedNodeIndex].Mesh.Id;
for(int i=0; i< _root.Meshes[meshIndex].Primitives[0].Targets.Count; ++i)
{
morphTargets.Add(GLTFUtils.buildBlendShapeName(meshIndex, i));
}
byte[] bufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id];
float[] values = GLTFHelpers.ParseKeyframeTimes(channel.Sampler.Value.Output.Value, bufferData);
AnimationCurve[] morphCurves = GLTFUtils.buildMorphAnimationCurves(times, values, morphTargets.Count);
GLTFUtils.addMorphAnimationCurvesToClip(morphCurves, nodePath, morphTargets.ToArray(), ref clip);
}
else
{
Debug.Log("Unsupported animation channel target: " + channel.Target.Path);
}
}
public void SetProgress(string step, int current, int total)
{
if (_progressCallback != null)
_progressCallback(step, current, total);
}
private IEnumerator LoadSkins()
{
SetProgress("SKIN", 0, _root.Skins.Count);
for (int i = 0; i < _root.Skins.Count; ++i)
{
LoadSkin(_root.Skins[i], i);
SetProgress("SKIN", (i + 1), _root.Skins.Count);
yield return null;
}
}
private void LoadSkin(GLTF.Schema.Skin skin, int index)
{
if (!_skinIndexToGameObjects.ContainsKey(index))
{
return;
}
Transform[] boneList = new Transform[skin.Joints.Count];
for (int i = 0; i < skin.Joints.Count; ++i)
{
boneList[i] = _importedObjects[skin.Joints[i].Id].transform;
}
foreach (SkinnedMeshRenderer skinMesh in _skinIndexToGameObjects[index])
{
skinMesh.bones = boneList;
}
}
private void BuildSkinnedMesh(GameObject nodeObj, GLTF.Schema.Skin skin, int meshIndex)
{
if(skin.InverseBindMatrices.Value.Count == 0)
return;
SkinnedMeshRenderer skinMesh = nodeObj.AddComponent();
skinMesh.sharedMesh = _assetManager.getMesh(meshIndex);
skinMesh.sharedMaterials = _assetManager.getMaterials(meshIndex);
byte[] bufferData = _assetCache.BufferCache[skin.InverseBindMatrices.Value.BufferView.Value.Buffer.Id];
NumericArray content = new NumericArray();
List bindPoseMatrices = new List();
GLTF.Math.Matrix4x4[] inverseBindMatrices = skin.InverseBindMatrices.Value.AsMatrixArray(ref content, bufferData);
foreach (GLTF.Math.Matrix4x4 mat in inverseBindMatrices)
{
bindPoseMatrices.Add(mat.ToUnityMatrix().switchHandedness());
}
skinMesh.sharedMesh.bindposes = bindPoseMatrices.ToArray();
if(skin.Skeleton != null && _importedObjects.ContainsKey(skin.Skeleton.Id))
skinMesh.rootBone = _importedObjects[skin.Skeleton.Id].transform;
}
protected virtual void LoadSkinnedMeshAttributes(int meshIndex, int primitiveIndex, ref Vector4[] boneIndexes, ref Vector4[] weights)
{
GLTF.Schema.MeshPrimitive prim = _root.Meshes[meshIndex].Primitives[primitiveIndex];
if (!prim.Attributes.ContainsKey(SemanticProperties.JOINT) || !prim.Attributes.ContainsKey(SemanticProperties.WEIGHT))
return;
parseAttribute(ref prim, SemanticProperties.JOINT, ref boneIndexes);
parseAttribute(ref prim, SemanticProperties.WEIGHT, ref weights);
foreach(Vector4 wei in weights)
{
wei.Normalize();
}
}
private void parseAttribute(ref GLTF.Schema.MeshPrimitive prim, string property, ref Vector4[] values)
{
byte[] bufferData = _assetCache.BufferCache[prim.Attributes[property].Value.BufferView.Value.Buffer.Id];
NumericArray num = new NumericArray();
GLTF.Math.Vector4[] gltfValues = prim.Attributes[property].Value.AsVector4Array(ref num, bufferData);
values = new Vector4[gltfValues.Length];
for (int i = 0; i < gltfValues.Length; ++i)
{
values[i] = gltfValues[i].ToUnityVector4();
}
}
protected virtual GameObject CreateNode(Node node, int index)
{
var nodeObj = createGameObject(node.Name != null ? node.Name : "SeinNode");
_nbParsedNodes++;
SetProgress("NODE", _nbParsedNodes, _root.Nodes.Count);
Vector3 position;
Quaternion rotation;
Vector3 scale;
node.GetUnityTRSProperties(out position, out rotation, out scale);
nodeObj.transform.localPosition = position;
nodeObj.transform.localRotation = rotation;
nodeObj.transform.localScale = scale;
bool isSkinned = node.Skin != null && isValidSkin(node.Skin.Id);
bool hasMorphOnly = node.Skin == null && node.Mesh != null && node.Mesh.Value.Weights != null && node.Mesh.Value.Weights.Count != 0;
if (node.Mesh != null)
{
if (isSkinned) // Mesh is skinned (it can also have morph)
{
if (!_skinIndexToGameObjects.ContainsKey(node.Skin.Id))
_skinIndexToGameObjects[node.Skin.Id] = new List();
_skinObjectsStore.Add(node, nodeObj);
}
else if (hasMorphOnly)
{
SkinnedMeshRenderer smr = nodeObj.AddComponent();
smr.sharedMesh = _assetManager.getMesh(node.Mesh.Id);
smr.materials = _assetManager.getMaterials(node.Mesh.Id);
}
else
{
// If several primitive, create several nodes and add them as child of this current Node
MeshFilter meshFilter = nodeObj.AddComponent();
meshFilter.sharedMesh = _assetManager.getMesh(node.Mesh.Id);
MeshRenderer meshRenderer = nodeObj.AddComponent();
meshRenderer.materials = _assetManager.getMaterials(node.Mesh.Id);
}
}
if (node.Children != null)
{
foreach (var child in node.Children)
{
var childObj = CreateNode(child.Value, child.Id);
childObj.transform.SetParent(nodeObj.transform, false);
}
}
if (node.Camera != null)
{
var cam = node.Camera.Value;
var camera = nodeObj.AddComponent();
if (cam.Type == GLTF.Schema.CameraType.orthographic)
{
camera.orthographic = true;
var matrix = camera.projectionMatrix;
matrix[1, 1] = 1 / (float)cam.Orthographic.YMag;
matrix[0, 0] = 1 / (float)cam.Orthographic.XMag;
matrix[2, 2] = 2 / (float)(cam.Orthographic.ZNear - cam.Orthographic.ZFar);
matrix[2, 3] = ((float)cam.Orthographic.ZFar + 1 / matrix[2, 2]) * matrix[2, 2];
}
else
{
camera.orthographic = false;
camera.farClipPlane = (float)cam.Perspective.ZFar;
camera.nearClipPlane = (float)cam.Perspective.ZNear;
camera.aspect = (float)cam.Perspective.AspectRatio;
camera.fieldOfView = (float)cam.Perspective.YFov;
}
}
if (node.Mesh != null)
{
foreach (var p in node.Mesh.Value.Primitives)
{
if (p.Material == null)
{
continue;
}
var mat = p.Material.Value;
var extName = ExtensionManager.GetExtensionName(typeof(Sein_customMaterialExtensionFactory));
if (mat != null && mat.Extensions != null && mat.Extensions.ContainsKey(extName))
{
var ext = mat.Extensions[extName] as Sein_customMaterialExtension;
if (ext.isComponent)
{
ext.AddComponentToGO(nodeObj);
}
}
}
}
_importedObjects.Add(index, nodeObj);
return nodeObj;
}
private GameObject createGameObject(string name)
{
name = GLTFUtils.cleanName(name);
return _assetManager.createGameObject(name);
}
private IEnumerator LoadNodesExtensions()
{
int i = 0;
foreach (var item in _importedObjects)
{
var go = item.Value;
var node = _root.Nodes[item.Key];
var extensions = node.Extensions;
if (extensions != null)
{
foreach (var extension in extensions)
{
ExtensionManager.Import(extension.Key, this, go, node, extension.Value);
}
}
i += 1;
SetProgress("NODE EXTENSIONS", i, _importedObjects.Count);
yield return null;
}
}
private bool isValidSkin(int skinIndex)
{
if (skinIndex >= _root.Skins.Count)
return false;
Skin glTFSkin = _root.Skins[skinIndex];
return glTFSkin.Joints.Count > 0 && glTFSkin.Joints.Count == glTFSkin.InverseBindMatrices.Value.Count;
}
private void finishImport()
{
GameObject prefab = null;
try
{
prefab = _assetManager.savePrefab(_sceneObject, _projectDirectoryPath, _addToCurrentScene);
}
catch (Exception error)
{
Debug.LogError(error);
}
if (_finishCallback != null)
_finishCallback();
Clear();
if (_addToCurrentScene == true && prefab != null)
{
// Select and focus imported object
_sceneObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
GameObject[] obj = new GameObject[1];
obj[0] = _sceneObject;
Selection.objects = obj;
#if UNITY_2017
EditorApplication.ExecuteMenuItem("Edit/Frame Selected");
#endif
}
}
///
/// Call this to abort current import
///
public void abortImport()
{
if (!_isDone)
{
_userStopped = true;
}
}
///
/// Cleans all generated files and structures
///
///
public void softClean()
{
if (_assetManager != null)
_assetManager.softClean();
_taskManager.clear();
Resources.UnloadUnusedAssets();
}
public void Clear()
{
if (_assetManager != null)
_assetManager.softClean();
_taskManager.clear();
Resources.UnloadUnusedAssets();
ExtensionManager.FinishExport();
}
private void cleanObjects()
{
foreach (GameObject ob in _importedObjects.Values)
{
GameObject.DestroyImmediate(ob);
}
GameObject.DestroyImmediate(_sceneObject);
_sceneObject = null;
_assetManager.softClean();
}
public void OnDestroy()
{
Clear();
}
}
}