using System;
using System.Collections.Generic;
using System.IO;
using Unity.Jobs;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine.Formats.Alembic.Sdk;
namespace UnityEngine.Formats.Alembic.Importer
{
sealed class AlembicStream : IDisposable
{
///
/// This class hides the context. The reason for that it that there are Jobs depending on it and we need
/// to ensure they get completed before any set happens on the context
///
public struct SafeContext
{
public SafeContext(aiContext c)
{
context = c;
updateJobHandle = new JobHandle();
}
aiContext context;
public JobHandle updateJobHandle { get; private set; }
public bool isValid => context;
public aiObject root => context.topObject;
public int timeSamplingCount => context.timeSamplingCount;
public aiTimeSampling GetTimeSampling(int i)
{
return context.GetTimeSampling(i);
}
public bool IsHDF5()
{
return context.IsHDF5();
}
public string GetApplication()
{
return context.GetApplication();
}
public void GetTimeRange(out double begin, out double end)
{
context.GetTimeRange(out begin, out end);
}
public void SetConfig(ref aiConfig conf)
{
updateJobHandle.Complete();
context.SetConfig(ref conf);
}
public bool Load(string path)
{
updateJobHandle.Complete();
return context.Load(path);
}
public void Destroy()
{
updateJobHandle.Complete();
context.Destroy();
}
public void ScheduleUpdateSamples(double time)
{
var updateJob = new UpdateSamplesJob { context = context, time = time };
updateJobHandle = updateJob.Schedule();
}
struct UpdateSamplesJob : IJob
{
public aiContext context;
public double time;
public void Execute()
{
context.UpdateSamples(time);
}
}
}
static List s_streams = new List();
public static void DisconnectStreamsWithPath(string path)
{
aiContext.DestroyByPath(path);
s_streams.ForEach(s =>
{
if (s.m_streamDesc.PathToAbc == path)
{
s.m_streamInterupted = true;
s.m_context = new SafeContext(default);
s.m_loaded = false;
}
});
}
public static void RemapStreamsWithPath(string oldPath, string newPath)
{
s_streams.ForEach(s =>
{
if (s.m_streamDesc.PathToAbc == oldPath)
{
s.m_streamInterupted = true;
s.m_streamDesc.PathToAbc = newPath;
}
});
}
public static void ReconnectStreamsWithPath(string path)
{
s_streams.ForEach(s =>
{
if (s.m_streamDesc.PathToAbc == path)
{
s.m_streamInterupted = false;
}
});
}
IStreamDescriptor m_streamDesc;
AlembicTreeNode m_abcTreeRoot;
aiConfig m_config;
SafeContext m_context;
double m_time;
bool m_loaded;
bool m_streamInterupted;
internal IStreamDescriptor streamDescriptor { get { return m_streamDesc; } }
public AlembicTreeNode abcTreeRoot { get { return m_abcTreeRoot; } }
internal SafeContext abcContext { get { return m_context; } }
public bool abcIsValid { get { return m_context.isValid; } }
internal aiConfig config { get { return m_config; } }
internal bool IsHDF5()
{
return m_context.IsHDF5();
}
public void SetVertexMotionScale(float value) { m_config.vertexMotionScale = value; }
public void GetTimeRange(out double begin, out double end) { m_context.GetTimeRange(out begin, out end); }
internal AlembicStream(GameObject rootGo, IStreamDescriptor streamDesc)
{
m_config.SetDefaults();
m_abcTreeRoot = new AlembicTreeNode() { stream = this, gameObject = rootGo };
m_streamDesc = streamDesc;
}
void AbcBeforeUpdateSamples(AlembicTreeNode node)
{
if (node.abcObject != null && node.gameObject != null)
node.abcObject.AbcPrepareSample();
foreach (var child in node.Children)
AbcBeforeUpdateSamples(child);
}
void AbcBeginSyncData(AlembicTreeNode node)
{
if (node != null && node.abcObject != null && node.gameObject != null)
node.abcObject.AbcSyncDataBegin();
foreach (var child in node.Children)
AbcBeginSyncData(child);
}
void AbcEndSyncData(AlembicTreeNode node)
{
if (node.abcObject != null && node.gameObject != null)
node.abcObject.AbcSyncDataEnd();
foreach (var child in node.Children)
AbcEndSyncData(child);
}
// returns false if the context needs to be recovered.
public bool AbcUpdateBegin(double time)
{
if (m_streamInterupted) return true;
if (!abcIsValid || !m_loaded) return false;
m_time = time;
m_context.SetConfig(ref m_config);
AbcBeforeUpdateSamples(m_abcTreeRoot);
m_context.ScheduleUpdateSamples(time);
return true;
}
// returns false if the context needs to be recovered.
public void AbcUpdateEnd()
{
if (m_streamInterupted)
return;
m_context.updateJobHandle.Complete();
AbcBeginSyncData(m_abcTreeRoot);
AbcEndSyncData(m_abcTreeRoot);
}
public void ClearMotionVectors()
{
ClearMotionVectors(m_abcTreeRoot);
}
void ClearMotionVectors(AlembicTreeNode node)
{
if (node.abcObject is AlembicMesh mesh)
{
mesh.ClearMotionVectors();
}
foreach (var child in node.Children)
ClearMotionVectors(child);
}
public bool AbcLoad(bool createMissingNodes, bool serializeMesh)
{
m_time = 0.0f;
m_context = new SafeContext(aiContext.Create(m_abcTreeRoot.gameObject.GetInstanceID()));
var settings = m_streamDesc.Settings;
m_config.swapHandedness = settings.SwapHandedness;
m_config.flipFaces = settings.FlipFaces;
m_config.aspectRatio = GetAspectRatio(settings.CameraAspectRatio);
m_config.scaleFactor = settings.ScaleFactor;
m_config.normalsMode = settings.Normals;
m_config.tangentsMode = settings.Tangents;
m_config.interpolateSamples = settings.InterpolateSamples;
m_config.importPointPolygon = settings.ImportPointPolygon;
m_config.importLinePolygon = settings.ImportLinePolygon;
m_config.importTrianglePolygon = settings.ImportTrianglePolygon;
m_context.SetConfig(ref m_config);
#if UNITY_EDITOR && UNITY_2021_3_OR_NEWER
string filePath = FileUtil.GetPhysicalPath(m_streamDesc.PathToAbc); // use physical path
#else
string filePath = m_streamDesc.PathToAbc; // use relative path
#endif
m_loaded = m_context.Load(filePath);
if (m_loaded)
{
UpdateAbcTree(m_context.root, m_abcTreeRoot, m_time, createMissingNodes, serializeMesh);
s_streams.Add(this);
}
else
{
if (!File.Exists(filePath))
{
Debug.LogError("File does not exist: " + filePath);
}
else if (m_context.IsHDF5())
{
Debug.LogError("Failed to load HDF5 alembic. Please convert to Ogawa: " + filePath);
}
else
{
Debug.LogError("File is in unknown format: " + filePath);
}
}
return m_loaded;
}
public void Dispose()
{
AlembicStream.s_streams.Remove(this);
if (m_abcTreeRoot != null)
{
m_abcTreeRoot.Dispose();
m_abcTreeRoot = null;
}
if (abcIsValid)
{
m_context.Destroy();
}
}
class ImportContext
{
public AlembicTreeNode alembicTreeNode;
public aiSampleSelector ss;
public bool createMissingNodes;
}
ImportContext m_importContext;
void UpdateAbcTree(aiObject top, AlembicTreeNode node, double time, bool createMissingNodes, bool serializeMesh)
{
if (!top)
return;
aiSampleSelector ss = default;
NativeMethods.aiTimeToSampleSelector(time, ref ss);
m_importContext = new ImportContext
{
alembicTreeNode = node,
ss = ss,
createMissingNodes = createMissingNodes,
};
top.EachChild(ImportCallback);
if (!serializeMesh)
{
foreach (var meshFilter in node.gameObject.GetComponentsInChildren(includeInactive: true))
{
if (meshFilter.sharedMesh != null)
{
meshFilter.sharedMesh.hideFlags |= HideFlags.DontSave;
}
}
}
m_importContext = null;
}
void ImportCallback(aiObject obj)
{
var ic = m_importContext;
AlembicTreeNode treeNode = ic.alembicTreeNode;
AlembicTreeNode childTreeNode = null;
aiSchema schema = obj.AsXform();
if (!schema) schema = obj.AsPolyMesh();
if (!schema) schema = obj.AsSubD();
if (!schema) schema = obj.AsCamera();
if (!schema) schema = obj.AsPoints();
if (!schema) schema = obj.AsCurves();
if (schema)
{
// Get child. create if needed and allowed.
string childName = obj.name;
// Find targetted child GameObj
GameObject childGO = null;
var childTransf = treeNode.gameObject == null ? null : treeNode.gameObject.transform.Find(childName);
if (childTransf == null)
{
if (!ic.createMissingNodes)
{
obj.SetEnabled(false);
return;
}
else
{
obj.SetEnabled(true);
}
childGO = RuntimeUtils.CreateGameObjectWithUndo("Create AlembicObject");
childGO.name = childName;
childGO.GetComponent().SetParent(treeNode.gameObject.transform, false);
}
else
childGO = childTransf.gameObject;
childTreeNode = new AlembicTreeNode() { stream = this, gameObject = childGO };
treeNode.Children.Add(childTreeNode);
// Update
AlembicElement elem = null;
if (obj.AsXform() && m_streamDesc.Settings.ImportXform)
elem = childTreeNode.GetOrAddAlembicObj();
else if (obj.AsCamera() && m_streamDesc.Settings.ImportCameras)
elem = childTreeNode.GetOrAddAlembicObj();
else if (obj.AsPolyMesh() && m_streamDesc.Settings.ImportMeshes)
elem = childTreeNode.GetOrAddAlembicObj();
else if (obj.AsSubD() && m_streamDesc.Settings.ImportMeshes)
elem = childTreeNode.GetOrAddAlembicObj();
else if (obj.AsPoints() && m_streamDesc.Settings.ImportPoints)
elem = childTreeNode.GetOrAddAlembicObj();
else if (obj.AsCurves() && m_streamDesc.Settings.ImportCurves)
{
var curves = childTreeNode.GetOrAddAlembicObj();
curves.CreateRenderingComponent = m_streamDesc.Settings.CreateCurveRenderers;
elem = curves;
}
if (elem != null)
{
elem.AbcSetup(obj, schema);
elem.AbcPrepareSample();
schema.UpdateSample(ref ic.ss);
elem.AbcSyncDataBegin();
elem.AbcSyncDataEnd();
}
}
else
{
obj.SetEnabled(false);
}
ic.alembicTreeNode = childTreeNode;
obj.EachChild(ImportCallback);
ic.alembicTreeNode = treeNode;
}
internal static float GetAspectRatio(AspectRatioMode mode)
{
if (mode == AspectRatioMode.CameraAperture)
{
return 0.0f;
}
else if (mode == AspectRatioMode.CurrentResolution)
{
return (float)Screen.width / (float)Screen.height;
}
else
{
#if UNITY_EDITOR
return (float)PlayerSettings.defaultScreenWidth / (float)PlayerSettings.defaultScreenHeight;
#else
// fallback on current resoltution
return (float)Screen.width / (float)Screen.height;
#endif
}
}
}
}