/// <param name="bytes">GLB file is supported</param> public static GameObject LoadFromBytes(byte[] bytes, ImportSettings importSettings, out AnimationClip[] animations) { return(ImportGLB(bytes, importSettings, out animations)); }
private static GameObject LoadInternal(this GLTFObject gltfObject, string filepath, byte[] bytefile, long binChunkStart, ImportSettings importSettings, out AnimationClip[] animations) { CheckExtensions(gltfObject); // directory root is sometimes used for loading buffers from containing file, or local images string directoryRoot = filepath != null?Directory.GetParent(filepath).ToString() + "/" : null; importSettings.shaderOverrides.CacheDefaultShaders(); // Import tasks synchronously GLTFBuffer.ImportTask bufferTask = new GLTFBuffer.ImportTask(gltfObject.buffers, filepath, bytefile, binChunkStart); bufferTask.RunSynchronously(); GLTFBufferView.ImportTask bufferViewTask = new GLTFBufferView.ImportTask(gltfObject.bufferViews, bufferTask); bufferViewTask.RunSynchronously(); GLTFAccessor.ImportTask accessorTask = new GLTFAccessor.ImportTask(gltfObject.accessors, bufferViewTask); accessorTask.RunSynchronously(); GLTFImage.ImportTask imageTask = new GLTFImage.ImportTask(gltfObject.images, directoryRoot, bufferViewTask); imageTask.RunSynchronously(); GLTFTexture.ImportTask textureTask = new GLTFTexture.ImportTask(gltfObject.textures, imageTask); textureTask.RunSynchronously(); GLTFMaterial.ImportTask materialTask = new GLTFMaterial.ImportTask(gltfObject.materials, textureTask, importSettings); materialTask.RunSynchronously(); GLTFMesh.ImportTask meshTask = new GLTFMesh.ImportTask(gltfObject.meshes, accessorTask, bufferViewTask, materialTask, importSettings); meshTask.RunSynchronously(); GLTFSkin.ImportTask skinTask = new GLTFSkin.ImportTask(gltfObject.skins, accessorTask); skinTask.RunSynchronously(); GLTFNode.ImportTask nodeTask = new GLTFNode.ImportTask(gltfObject.nodes, meshTask, skinTask, gltfObject.cameras); nodeTask.RunSynchronously(); GLTFAnimation.ImportResult[] animationResult = gltfObject.animations.Import(accessorTask.Result, nodeTask.Result, importSettings); if (animationResult != null) { animations = animationResult.Select(x => x.clip).ToArray(); } else { animations = new AnimationClip[0]; } foreach (var item in bufferTask.Result) { item.Dispose(); } GameObject gameObject = nodeTask.Result.GetRoot(); if (importSettings.extrasProcessor != null) { ProcessExtrasInternal(gameObject, gltfObject, importSettings, animations); } return(gameObject); }
private static IEnumerator LoadAsync(string json, string filepath, byte[] bytefile, long binChunkStart, ImportSettings importSettings, Action <GameObject, AnimationClip[]> onFinished, Action <float> onProgress = null) { // Threaded deserialization Task <GLTFObject> deserializeTask = new Task <GLTFObject>(() => JsonConvert.DeserializeObject <GLTFObject>(json)); deserializeTask.Start(); while (!deserializeTask.IsCompleted) { yield return(null); } GLTFObject gltfObject = deserializeTask.Result; CheckExtensions(gltfObject); // directory root is sometimes used for loading buffers from containing file, or local images string directoryRoot = filepath != null?Directory.GetParent(filepath).ToString() + "/" : null; importSettings.shaderOverrides.CacheDefaultShaders(); // Setup import tasks List <ImportTask> importTasks = new List <ImportTask>(); GLTFBuffer.ImportTask bufferTask = new GLTFBuffer.ImportTask(gltfObject.buffers, filepath, bytefile, binChunkStart); importTasks.Add(bufferTask); GLTFBufferView.ImportTask bufferViewTask = new GLTFBufferView.ImportTask(gltfObject.bufferViews, bufferTask); importTasks.Add(bufferViewTask); GLTFAccessor.ImportTask accessorTask = new GLTFAccessor.ImportTask(gltfObject.accessors, bufferViewTask); importTasks.Add(accessorTask); GLTFImage.ImportTask imageTask = new GLTFImage.ImportTask(gltfObject.images, directoryRoot, bufferViewTask); importTasks.Add(imageTask); GLTFTexture.ImportTask textureTask = new GLTFTexture.ImportTask(gltfObject.textures, imageTask); importTasks.Add(textureTask); GLTFMaterial.ImportTask materialTask = new GLTFMaterial.ImportTask(gltfObject.materials, textureTask, importSettings); importTasks.Add(materialTask); GLTFMesh.ImportTask meshTask = new GLTFMesh.ImportTask(gltfObject.meshes, accessorTask, bufferViewTask, materialTask, importSettings); importTasks.Add(meshTask); GLTFSkin.ImportTask skinTask = new GLTFSkin.ImportTask(gltfObject.skins, accessorTask); importTasks.Add(skinTask); GLTFNode.ImportTask nodeTask = new GLTFNode.ImportTask(gltfObject.nodes, meshTask, skinTask, gltfObject.cameras); importTasks.Add(nodeTask); // Ignite for (int i = 0; i < importTasks.Count; i++) { TaskSupervisor(importTasks[i], onProgress).RunCoroutine(); } // Wait for all tasks to finish while (!importTasks.All(x => x.IsCompleted)) { yield return(null); } // Fire onFinished when all tasks have completed GameObject root = nodeTask.Result.GetRoot(); GLTFAnimation.ImportResult[] animationResult = gltfObject.animations.Import(accessorTask.Result, nodeTask.Result, importSettings); AnimationClip[] animations = new AnimationClip[0]; if (animationResult != null) { animations = animationResult.Select(x => x.clip).ToArray(); } if (importSettings.extrasProcessor != null) { ProcessExtrasInternal(root, gltfObject, importSettings, animations); } if (onFinished != null) { onFinished(nodeTask.Result.GetRoot(), animations); } // Close file streams foreach (var item in bufferTask.Result) { item.Dispose(); } }
public static GameObject LoadFromFile(string filepath, ImportSettings importSettings, Format format = Format.AUTO) { AnimationClip[] animations; return(LoadFromFile(filepath, importSettings, out animations, format)); }
private static void ProcessExtrasInternal(GameObject gameObject, GLTFObject gltfObject, ImportSettings importSettings, AnimationClip[] animations) { if (gltfObject.extras == null) { gltfObject.extras = new JObject(); } if (gltfObject.materials != null) { JArray materialExtras = new JArray(); bool hasMaterialExtraData = false; foreach (GLTFMaterial material in gltfObject.materials) { if (material.extras != null) { materialExtras.Add(material.extras); hasMaterialExtraData = true; } else { materialExtras.Add(new JObject()); } } if (hasMaterialExtraData) { gltfObject.extras.Add("material", materialExtras); } } if (gltfObject.animations != null) { JArray animationExtras = new JArray(); bool hasAnimationExtraData = false; foreach (GLTFAnimation animation in gltfObject.animations) { if (animation.extras != null) { hasAnimationExtraData = true; animationExtras.Add(animation.extras); } else { animationExtras.Add(new JObject()); } } if (hasAnimationExtraData) { gltfObject.extras.Add("animation", animationExtras); } } importSettings.extrasProcessor.ProcessExtras(gameObject, animations, gltfObject.extras); }
public static void SaveToAsset(GameObject root, AnimationClip[] animations, AssetImportContext ctx, ImportSettings settings) { #if UNITY_2018_2_OR_NEWER ctx.AddObjectToAsset("main", root); ctx.SetMainObject(root); #else ctx.SetMainAsset("main obj", root); #endif MeshRenderer[] renderers = root.GetComponentsInChildren <MeshRenderer>(true); SkinnedMeshRenderer[] skinnedRenderers = root.GetComponentsInChildren <SkinnedMeshRenderer>(true); MeshFilter[] filters = root.GetComponentsInChildren <MeshFilter>(true); AddMeshes(filters, skinnedRenderers, ctx, settings.generateLightmapUVs); AddMaterials(renderers, skinnedRenderers, ctx); AddAnimations(animations, ctx, settings.animationSettings); }
public ImportResult Import(GLTFAccessor.ImportResult[] accessors, GLTFNode.ImportResult[] nodes, ImportSettings importSettings) { bool multiRoots = nodes.Where(x => x.IsRoot).Count() > 1; ImportResult result = new ImportResult(); result.clip = new AnimationClip(); result.clip.name = name; result.clip.frameRate = importSettings.animationSettings.frameRate; result.clip.legacy = importSettings.animationSettings.useLegacyClips; if (result.clip.legacy && importSettings.animationSettings.looping) { result.clip.wrapMode = WrapMode.Loop; } for (int i = 0; i < channels.Length; i++) { Channel channel = channels[i]; if (samplers.Length <= channel.sampler) { Debug.LogWarning($"GLTFUtility: Animation channel points to sampler at index {channel.sampler} which doesn't exist. Skipping animation clip."); continue; } Sampler sampler = samplers[channel.sampler]; // Get interpolation mode InterpolationMode interpolationMode = importSettings.animationSettings.interpolationMode; if (interpolationMode == InterpolationMode.ImportFromFile) { interpolationMode = sampler.interpolation; } if (interpolationMode == InterpolationMode.CUBICSPLINE) { Debug.LogWarning("Animation interpolation mode CUBICSPLINE not fully supported, result might look different."); } string relativePath = ""; GLTFNode.ImportResult node = nodes[channel.target.node.Value]; while (node != null && !node.IsRoot) { if (string.IsNullOrEmpty(relativePath)) { relativePath = node.transform.name; } else { relativePath = node.transform.name + "/" + relativePath; } if (node.parent.HasValue) { node = nodes[node.parent.Value]; } else { node = null; } } // If file has multiple root nodes, a new parent will be created for them as a final step of the import process. This parent f***s up the curve relative paths. // Add node.transform.name to path if there are multiple roots. This is not the most elegant fix but it works. // See GLTFNodeExtensions.GetRoot if (multiRoots) { relativePath = node.transform.name + "/" + relativePath; } System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; float[] keyframeInput = accessors[sampler.input].ReadFloat().ToArray(); switch (channel.target.path) { case "translation": Vector3[] pos = accessors[sampler.output].ReadVec3().ToArray(); AnimationCurve posX = new AnimationCurve(); AnimationCurve posY = new AnimationCurve(); AnimationCurve posZ = new AnimationCurve(); for (int k = 0; k < keyframeInput.Length; k++) { posX.AddKey(CreateKeyframe(k, keyframeInput, pos, x => - x.x, interpolationMode)); posY.AddKey(CreateKeyframe(k, keyframeInput, pos, x => x.y, interpolationMode)); posZ.AddKey(CreateKeyframe(k, keyframeInput, pos, x => x.z, interpolationMode)); } result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.x", posX); result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.y", posY); result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.z", posZ); break; case "rotation": Vector4[] rot = accessors[sampler.output].ReadVec4().ToArray(); AnimationCurve rotX = new AnimationCurve(); AnimationCurve rotY = new AnimationCurve(); AnimationCurve rotZ = new AnimationCurve(); AnimationCurve rotW = new AnimationCurve(); for (int k = 0; k < keyframeInput.Length; k++) { // The Animation window in Unity shows keyframes incorrectly converted to euler. This is only to deceive you. The quaternions underneath work correctly rotX.AddKey(CreateKeyframe(k, keyframeInput, rot, x => x.x, interpolationMode)); rotY.AddKey(CreateKeyframe(k, keyframeInput, rot, x => - x.y, interpolationMode)); rotZ.AddKey(CreateKeyframe(k, keyframeInput, rot, x => - x.z, interpolationMode)); rotW.AddKey(CreateKeyframe(k, keyframeInput, rot, x => x.w, interpolationMode)); } result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.x", rotX); result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.y", rotY); result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.z", rotZ); result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.w", rotW); break; case "scale": Vector3[] scale = accessors[sampler.output].ReadVec3().ToArray(); AnimationCurve scaleX = new AnimationCurve(); AnimationCurve scaleY = new AnimationCurve(); AnimationCurve scaleZ = new AnimationCurve(); for (int k = 0; k < keyframeInput.Length; k++) { scaleX.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.x, interpolationMode)); scaleY.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.y, interpolationMode)); scaleZ.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.z, interpolationMode)); } result.clip.SetCurve(relativePath, typeof(Transform), "localScale.x", scaleX); result.clip.SetCurve(relativePath, typeof(Transform), "localScale.y", scaleY); result.clip.SetCurve(relativePath, typeof(Transform), "localScale.z", scaleZ); break; case "weights": GLTFNode.ImportResult skinnedMeshNode = nodes[channel.target.node.Value]; SkinnedMeshRenderer skinnedMeshRenderer = skinnedMeshNode.transform.GetComponent <SkinnedMeshRenderer>(); int numberOfBlendShapes = skinnedMeshRenderer.sharedMesh.blendShapeCount; AnimationCurve[] blendShapeCurves = new AnimationCurve[numberOfBlendShapes]; for (int j = 0; j < numberOfBlendShapes; ++j) { blendShapeCurves[j] = new AnimationCurve(); } float[] weights = accessors[sampler.output].ReadFloat().ToArray(); float[] weightValues = new float[keyframeInput.Length]; float[] previouslyKeyedValues = new float[numberOfBlendShapes]; // Reference for my future self: // keyframeInput.Length = number of keyframes // keyframeInput[ k ] = timestamp of keyframe // weights.Length = number of keyframes * number of blendshapes // weights[ j ] = actual animated weight of a specific blend shape // (index into weights[] array accounts for keyframe index and blend shape index) for (int k = 0; k < keyframeInput.Length; ++k) { for (int j = 0; j < numberOfBlendShapes; ++j) { int weightIndex = (k * numberOfBlendShapes) + j; weightValues[k] = weights[weightIndex]; bool addKey = true; if (importSettings.animationSettings.compressBlendShapeKeyFrames) { if (k == 0 || !Mathf.Approximately(weightValues[k], previouslyKeyedValues[j])) { if (k > 0) { weightValues[k - 1] = previouslyKeyedValues[j]; blendShapeCurves[j].AddKey(CreateKeyframe(k - 1, keyframeInput, weightValues, x => x, interpolationMode)); } addKey = true; previouslyKeyedValues[j] = weightValues[k]; } else { addKey = false; } } if (addKey) { blendShapeCurves[j].AddKey(CreateKeyframe(k, keyframeInput, weightValues, x => x, interpolationMode)); } } } for (int j = 0; j < numberOfBlendShapes; ++j) { string propertyName = "blendShape." + skinnedMeshRenderer.sharedMesh.GetBlendShapeName(j); result.clip.SetCurve(relativePath, typeof(SkinnedMeshRenderer), propertyName, blendShapeCurves[j]); } break; } } return(result); }
public static GLTFAnimation.ImportResult[] Import(this List <GLTFAnimation> animations, GLTFAccessor.ImportResult[] accessors, GLTFNode.ImportResult[] nodes, ImportSettings importSettings) { if (animations == null) { return(null); } GLTFAnimation.ImportResult[] results = new GLTFAnimation.ImportResult[animations.Count]; for (int i = 0; i < results.Length; i++) { results[i] = animations[i].Import(accessors, nodes, importSettings); if (string.IsNullOrEmpty(results[i].clip.name)) { results[i].clip.name = "animation" + i; } } return(results); }
public ImportTask(List <GLTFMesh> meshes, GLTFAccessor.ImportTask accessorTask, GLTFBufferView.ImportTask bufferViewTask, GLTFMaterial.ImportTask materialTask, ImportSettings importSettings) : base(accessorTask, materialTask) { this.meshes = meshes; this.materialTask = materialTask; task = new Task(() => { if (meshes == null) { return; } meshData = new MeshData[meshes.Count]; for (int i = 0; i < meshData.Length; i++) { meshData[i] = new MeshData(meshes[i], accessorTask.Result, bufferViewTask.Result); } }); }
public ImportTask(List <GLTFMaterial> materials, GLTFTexture.ImportTask textureTask, ImportSettings importSettings) : base(textureTask) { this.materials = materials; this.textureTask = textureTask; this.importSettings = importSettings; task = new Task(() => { if (materials == null) { return; } Result = new ImportResult[materials.Count]; }); }