// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors // SPDX-License-Identifier: Apache-2.0 #if !GLTFAST_EDITOR_IMPORT_OFF // glTFast is on the path to being official, so it should have highest priority as importer by default // This define is included for completeness. // Other glTF importers should specify this via AsmDef dependency, for example // `com.unity.cloud.gltfast@3.0.0: HAVE_GLTFAST` and then checking here `#if HAVE_GLTFAST` #if false #define ANOTHER_IMPORTER_HAS_HIGHER_PRIORITY #endif #if !ANOTHER_IMPORTER_HAS_HIGHER_PRIORITY && !GLTFAST_FORCE_DEFAULT_IMPORTER_OFF #define ENABLE_DEFAULT_GLB_IMPORTER #endif #if GLTFAST_FORCE_DEFAULT_IMPORTER_ON #define ENABLE_DEFAULT_GLB_IMPORTER #endif using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using GLTFast.Logging; using GLTFast.Utils; using UnityEditor; using UnityEditor.AssetImporters; using UnityEngine; using UnityEngine.Rendering; using Object = UnityEngine.Object; namespace GLTFast.Editor { #if ENABLE_DEFAULT_GLB_IMPORTER [ScriptedImporter(1, new[] { "gltf", "glb" })] #else [ScriptedImporter(1, null, overrideExts: new[] { "gltf","glb" })] #endif class GltfImporter : ScriptedImporter { [SerializeField] EditorImportSettings editorImportSettings; [SerializeField] ImportSettings importSettings; [SerializeField] InstantiationSettings instantiationSettings; // These are used/read in the GltfImporterEditor // ReSharper disable NotAccessedField.Local [SerializeField] GltfAssetDependency[] assetDependencies; [SerializeField] internal LogItem[] reportItems; // ReSharper restore NotAccessedField.Local GltfImport m_Gltf; HashSet m_ImportedNames; HashSet m_ImportedObjects; // static string[] GatherDependenciesFromSourceFile(string path) { // // Called before actual import for each changed asset that is imported by this importer type // // Extract the dependencies for the asset specified in path. // // For asset dependencies that are discovered, return them in the string array, where the string is the path to asset // // // TODO: Texture files with relative URIs should be included here // return null; // } public override void OnImportAsset(AssetImportContext ctx) { reportItems = null; var downloadProvider = new EditorDownloadProvider(); var logger = new CollectingLogger(); m_Gltf = new GltfImport( downloadProvider, new UninterruptedDeferAgent(), null, logger ); var gltfIcon = AssetDatabase.LoadAssetAtPath($"Packages/{GltfGlobals.GltfPackageName}/Editor/UI/gltf-icon-bug.png"); if (editorImportSettings == null) { // Design-time import specific settings editorImportSettings = new EditorImportSettings(); } if (importSettings == null) { // Design-time import specific changes to default settings importSettings = new ImportSettings { // Avoid naming conflicts by default NodeNameMethod = NameImportMethod.OriginalUnique, GenerateMipMaps = true, AnimationMethod = AnimationMethod.Mecanim, }; } if (instantiationSettings == null) { instantiationSettings = new InstantiationSettings(); } var success = AsyncHelpers.RunSync(() => m_Gltf.Load(ctx.assetPath, importSettings)); CollectingLogger instantiationLogger = null; if (success) { m_ImportedNames = new HashSet(); m_ImportedObjects = new HashSet(); if (instantiationSettings.SceneObjectCreation == SceneObjectCreation.Never) { // There *has* to be a common parent GameObject that gets // added to the ScriptedImporter, so we overrule this // setting. instantiationSettings.SceneObjectCreation = SceneObjectCreation.WhenMultipleRootNodes; Debug.LogWarning("SceneObjectCreation setting \"Never\" is not available for Editor (design-time) imports. Falling back to WhenMultipleRootNodes.", this); } instantiationLogger = new CollectingLogger(); for (var sceneIndex = 0; sceneIndex < m_Gltf.SceneCount; sceneIndex++) { try { ImportScene(ctx, sceneIndex, instantiationLogger); } catch (InvalidOperationException) { instantiationLogger.Error($"Failed creating scene {sceneIndex} instance."); } if (m_Gltf.MaterialsVariantsCount > 0) { for (var variantIndex = 0; variantIndex < m_Gltf.MaterialsVariantsCount; variantIndex++) { try { ImportScene(ctx, sceneIndex, instantiationLogger, variantIndex); } catch (InvalidOperationException) { instantiationLogger.Error($"Failed creating scene {sceneIndex} materials variant " + $"{variantIndex} instance."); } } } } for (var i = 0; i < m_Gltf.TextureCount; i++) { var texture = m_Gltf.GetTexture(i); if (texture != null) { var textureAssetPath = AssetDatabase.GetAssetPath(texture); if (string.IsNullOrEmpty(textureAssetPath)) { AddObjectToAsset(ctx, $"textures/{texture.name}", texture); } } } for (var i = 0; i < m_Gltf.MaterialCount; i++) { var mat = m_Gltf.GetMaterial(i); // Overriding double-sided for GI baking // Resolves problems with meshes that are not a closed // volume at a potential minor cost of baking speed. mat.doubleSidedGI = true; if (mat != null) { AddObjectToAsset(ctx, $"materials/{mat.name}", mat); } } if (m_Gltf.defaultMaterial != null) { // If a default/fallback material was created, import it as well' // to avoid (pink) objects without materials var mat = m_Gltf.defaultMaterial; AddObjectToAsset(ctx, $"materials/{mat.name}", mat); } var meshes = m_Gltf.Meshes; if (meshes != null) { foreach (var mesh in meshes) { if (mesh == null) { continue; } if (editorImportSettings.generateSecondaryUVSet && !HasSecondaryUVs(mesh)) { Unwrapping.GenerateSecondaryUVSet(mesh); } AddObjectToAsset(ctx, $"meshes/{mesh.name}", mesh); } } #if UNITY_ANIMATION var clips = m_Gltf.GetAnimationClips(); if (clips != null) { foreach (var animationClip in clips) { if (animationClip == null) { continue; } if (importSettings.AnimationMethod == AnimationMethod.Mecanim) { var settings = AnimationUtility.GetAnimationClipSettings(animationClip); settings.loopTime = true; AnimationUtility.SetAnimationClipSettings (animationClip, settings); } AddObjectToAsset(ctx, $"animations/{animationClip.name}", animationClip); } } // TODO seems the states don't properly connect to the Animator here // (would need to be saved as SubAssets of the AnimatorController) // var animators = go.GetComponentsInChildren(); // foreach (var animator in animators) // { // var controller = animator.runtimeAnimatorController as UnityEditor.Animations.AnimatorController; // if (controller != null) { // AddObjectToAsset(ctx, $"animatorControllers/{animator.name}", controller); // foreach (var layer in controller.layers) // { // var stateMachine = layer.stateMachine; // stateMachine.hideFlags = HideFlags.HideInHierarchy; // if(stateMachine) // AddObjectToAsset(ctx, $"animatorControllers/{animator.name}/{stateMachine.name}", stateMachine); // } // } // } #endif m_ImportedNames = null; m_ImportedObjects = null; } var deps = new List(); for (var index = 0; index < downloadProvider.assetDependencies.Count; index++) { var dependency = downloadProvider.assetDependencies[index]; if (ctx.assetPath == dependency.originalUri) { // Skip original gltf/glb file continue; } var guid = AssetDatabase.AssetPathToGUID(dependency.originalUri); if (!string.IsNullOrEmpty(guid)) { dependency.assetPath = dependency.originalUri; ctx.DependsOnSourceAsset(dependency.assetPath); } deps.Add(dependency); } assetDependencies = deps.ToArray(); var reportItemList = new List(); if (logger.Count > 0) { reportItemList.AddRange(logger.Items); } if (instantiationLogger?.Items != null) { reportItemList.AddRange(instantiationLogger.Items); } if (reportItemList.Any(x => x.Type == LogType.Error || x.Type == LogType.Exception)) { Debug.LogError($"Failed to import {assetPath} (see inspector for details)", this); } reportItems = reportItemList.ToArray(); } void ImportScene( AssetImportContext ctx, int sceneIndex, CollectingLogger instantiationLogger, int? materialsVariantIndex = null ) { var scene = m_Gltf.GetSourceScene(sceneIndex); var sceneName = m_Gltf.GetSceneName(sceneIndex); string sceneObjectName = null; string variantNameSuffix = null; if (materialsVariantIndex.HasValue) { variantNameSuffix = m_Gltf.GetMaterialsVariantName(materialsVariantIndex.Value); if (string.IsNullOrEmpty(variantNameSuffix)) { variantNameSuffix = $"variant_{materialsVariantIndex.Value}"; } sceneObjectName = $"{sceneName}_{variantNameSuffix}"; } else { sceneObjectName = sceneName; } var go = new GameObject(sceneObjectName); var instantiator = new GameObjectInstantiator(m_Gltf, go.transform, instantiationLogger, instantiationSettings); var index = sceneIndex; var success = AsyncHelpers.RunSync(() => m_Gltf.InstantiateSceneAsync(instantiator, index)); if (!success) { throw new InvalidOperationException("Instantiating scene failed"); } var useFirstChild = true; var multipleNodes = scene.nodes.Length > 1; var hasAnimation = false; #if UNITY_ANIMATION if (importSettings.AnimationMethod != AnimationMethod.None && (instantiationSettings.Mask & ComponentType.Animation) != 0) { var animationClips = m_Gltf.GetAnimationClips(); if (animationClips != null && animationClips.Length > 0) { hasAnimation = true; } } #endif if (instantiationSettings.SceneObjectCreation == SceneObjectCreation.Never || instantiationSettings.SceneObjectCreation == SceneObjectCreation.WhenMultipleRootNodes && !multipleNodes) { // No scene GameObject was created, so the first // child is the first (and in this case only) node. // If there's animation, its clips' paths are relative // to the root GameObject (which will also carry the // `Animation` component. If not, we can import the the // first and only node as root directly. useFirstChild = !hasAnimation; } var sceneTransform = useFirstChild ? go.transform.GetChild(0) : go.transform; var sceneGo = sceneTransform.gameObject; if (materialsVariantIndex.HasValue) { sceneGo.name = $"{sceneGo.name}_{variantNameSuffix}"; var variantsControl = instantiator.SceneInstance.MaterialsVariantsControl; AsyncHelpers.RunSync(() => variantsControl.ApplyMaterialsVariantAsync(materialsVariantIndex.Value)); } AddObjectToAsset(ctx, $"scenes/{sceneObjectName}", sceneGo); if (sceneIndex == m_Gltf.DefaultSceneIndex && !materialsVariantIndex.HasValue) { ctx.SetMainObject(sceneGo); } } void AddObjectToAsset(AssetImportContext ctx, string originalName, Object obj, Texture2D thumbnail = null) { if (m_ImportedObjects.Contains(obj)) { return; } var uniqueAssetName = GetUniqueAssetName(originalName); ctx.AddObjectToAsset(uniqueAssetName, obj, thumbnail); m_ImportedNames.Add(uniqueAssetName); m_ImportedObjects.Add(obj); } string GetUniqueAssetName(string originalName) { if (string.IsNullOrWhiteSpace(originalName)) { originalName = "Asset"; } if (m_ImportedNames.Contains(originalName)) { var i = 0; string extName; do { extName = $"{originalName}_{i++}"; } while (m_ImportedNames.Contains(extName)); return extName; } return originalName; } static bool HasSecondaryUVs(Mesh mesh) { var attributes = mesh.GetVertexAttributes(); return attributes.Any(attribute => attribute.attribute == VertexAttribute.TexCoord1); } } } #endif // !GLTFAST_EDITOR_IMPORT_OFF