Files
2025-11-30 08:35:03 +02:00

414 lines
16 KiB
C#

// 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<string> m_ImportedNames;
HashSet<Object> 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<Texture2D>($"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<string>();
m_ImportedObjects = new HashSet<Object>();
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<Animator>();
// 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<GltfAssetDependency>();
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<LogItem>();
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