/** \mainpage Obi documentation Introduction: ------------- Obi is a position-based dynamics framework for unity. It enables the simulation of cloth, ropes and fluid in realtime, complete with two-way rigidbody interaction. Features: ------------------- - Particles can be pinned both in local space and to rigidbodies (kinematic or not). - Realistic wind forces. - Rigidbodies react to particle dynamics, and particles reach to each other and to rigidbodies too. - Easy prefab instantiation, particle-based actors can be translated, scaled and rotated. - Custom editor tools. */ using UnityEngine; using Unity.Profiling; using System; using System.Collections.Generic; using System.Linq; using System.Collections; namespace Obi { /** * ObiSolver simulates particles and constraints, provided by a list of ObiActor. Particles belonging to different solvers won't interact with each other in any way. */ [AddComponentMenu("Physics/Obi/Obi Solver", 800)] [ExecuteInEditMode] [DisallowMultipleComponent] public sealed class ObiSolver : MonoBehaviour { static ProfilerMarker m_StateInterpolationPerfMarker = new ProfilerMarker("ApplyStateInterpolation"); static ProfilerMarker m_UpdateVisibilityPerfMarker = new ProfilerMarker("UpdateVisibility"); static ProfilerMarker m_GetSolverBoundsPerfMarker = new ProfilerMarker("GetSolverBounds"); static ProfilerMarker m_TestBoundsPerfMarker = new ProfilerMarker("TestBoundsAgainstCameras"); static ProfilerMarker m_GetAllCamerasPerfMarker = new ProfilerMarker("GetAllCameras"); static ProfilerMarker m_PushActiveParticles = new ProfilerMarker("PushActiveParticles"); static ProfilerMarker m_UpdateColliderWorld = new ProfilerMarker("UpdateColliderWorld"); static ProfilerMarker m_PushSimplices = new ProfilerMarker("PushSimplices"); static ProfilerMarker m_PushDeformableEdges = new ProfilerMarker("PushDeformableEdges"); static ProfilerMarker m_PushDeformableTriangles = new ProfilerMarker("PushDeformableTriangles"); public enum BackendType { [InspectorName("Compute (GPU)")] Compute, [InspectorName("Burst (CPU)")] Burst } public enum Synchronization { Asynchronous, Synchronous, SynchronousFixed } [Serializable] public class ParticleInActor { public ObiActor actor; public int indexInActor; public ParticleInActor() { actor = null; indexInActor = -1; } public ParticleInActor(ObiActor actor, int indexInActor) { this.actor = actor; this.indexInActor = indexInActor; } } public class SpatialQuery { public ObiNativeQueryShapeList shapes; public ObiNativeAffineTransformList transforms; public ObiNativeQueryResultList results; public Action callback; public bool synchronous = false; public bool isValid => shapes != null && transforms != null && results != null && shapes.count > 0 && transforms.count > 0; public bool done => results.noReadbackInFlight; public SpatialQuery(ObiNativeQueryShapeList shapes, ObiNativeAffineTransformList transforms, ObiNativeQueryResultList results, Action callback = null, bool synchronous = false) { this.shapes = shapes; this.transforms = transforms; this.results = results; this.callback = callback; this.synchronous = synchronous; } public void WaitForCompletion() { results.WaitForReadback(); } } public delegate void SolverCallback(ObiSolver solver); public delegate void SolverStepCallback(ObiSolver solver, float timeToSimulate, float substepTime); public delegate void CollisionCallback(ObiSolver solver, ObiNativeContactList contacts); public delegate void SpatialQueryCallback(ObiSolver solver, ObiNativeQueryResultList results); public event CollisionCallback OnCollision; public event CollisionCallback OnParticleCollision; public event SpatialQueryCallback OnSpatialQueryResults; public event SolverCallback OnAdvection; public event SolverCallback OnInitialize; public event SolverCallback OnTeardown; public event SolverCallback OnUpdateParameters; public event SolverCallback OnParticleCountChanged; public event SolverStepCallback OnSimulationStart; /**< Called at the start of physics simulation, before updating active particles, constraints, etc.*/ public event SolverCallback OnRequestReadback; public event SolverStepCallback OnSimulationEnd; /**< Called at the end of physics simulation.*/ public event SolverStepCallback OnInterpolate; /**< Called every frame after interpolation, right before updating rendering.*/ [Tooltip("If enabled, will force the solver to keep simulating even when not visible from any camera.")] public bool simulateWhenInvisible = true; private IObiBackend m_SimulationBackend = #if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS) new BurstBackend(); #else new NullBackend(); #endif [SerializeField] private BackendType m_Backend = BackendType.Burst; private ObiRenderSystemStack m_RenderSystems = new ObiRenderSystemStack(3); [Min(1)] public int substeps = 4; [Min(0)] public int maxStepsPerFrame = 1; public Synchronization synchronization = Synchronization.Asynchronous; public Oni.SolverParameters parameters = new Oni.SolverParameters(Oni.SolverParameters.Interpolation.None, new Vector4(0, -9.81f, 0, 0)); [Min(32)] [SerializeField] private uint m_MaxSurfaceChunks = 32768; public uint maxSurfaceChunks { set { // make sure anytime active particles need to be updated, simplices will be updated too: m_MaxSurfaceChunks = value; dirtyRendering |= (int)Oni.RenderingSystemType.Fluid; } get { return m_MaxSurfaceChunks; } } public uint usedSurfaceChunks { get { var system = GetRenderSystem(Oni.RenderingSystemType.Fluid) as ISurfaceChunkUser; if (system == null) return 0; return system.usedChunkCount; } } public uint maxQueryResults = 8192; public uint maxFoamParticles = 8192; public uint maxParticleNeighbors = 128; public uint maxParticleContacts = 6; public Vector3 gravity = new Vector3(0, -9.81f, 0); public Space gravitySpace = Space.Self; public Vector3 ambientWind = new Vector3(0, 0, 0); public Space windSpace = Space.Self; [Min(1)] public int foamSubsteps = 1; [Tooltip("Foam particles can stretch along the direction of their velocity. This parameter controls the maximum amount of stretch.")] [Range(0, 3)] public float maxFoamVelocityStretch = 0.3f; [Tooltip("Determines how foam particles fade in/out during its lifetime.")] [MinMax(0, 1)] public Vector2 foamFade = new Vector2(0.05f, 0.8f); [Tooltip("Determines the utilization % range in which particles age faster.")] [MinMax(0, 1)] public Vector2 foamAccelAgingRange = new Vector2(0.5f, 0.8f); [Tooltip("Determines the utilization % range in which particles age faster.")] [Min(1)] public float foamAccelAging = 4; [Tooltip("How much does world-space linear inertia affect particles in the solver.")] [Range(0, 1)] public float worldLinearInertiaScale = 0; /**< how much does world-space linear inertia affect particles in the solver*/ [Tooltip("How much does world-space angular inertia affect particles in the solver.")] [Range(0, 1)] public float worldAngularInertiaScale = 0; /**< how much does world-space angular inertia affect particles in the solver.*/ [HideInInspector] [NonSerialized] public List actors = new List(); [HideInInspector] [NonSerialized] private ParticleInActor[] m_ParticleToActor; [HideInInspector] [NonSerialized] private Queue addBuffer = new Queue(); /**< actors pending insertion into the solver.*/ private ObiNativeIntList freeList; private Stack freeGroupIDs = new Stack(); [NonSerialized] public ObiNativeIntList deformableTriangles; [NonSerialized] public ObiNativeIntList deformableEdges; [NonSerialized] public ObiNativeVector2List deformableUVs; [NonSerialized] private ObiNativeIntList m_Points; /**< 0-simplices*/ [NonSerialized] private ObiNativeIntList m_Edges; /**< 1-simplices*/ [NonSerialized] private ObiNativeIntList m_Triangles; /**< 2-simplices*/ [NonSerialized] public SimplexCounts m_SimplexCounts; [NonSerialized] private IObiJobHandle simulationHandle; [NonSerialized] private Synchronization bufferedSynchronization; [NonSerialized] private int steps = 0; [NonSerialized] private float substepTime = 0; [NonSerialized] private float simulatedTime = 0; [NonSerialized] private float accumulatedTime = 0; public float timeSinceSimulationStart { get; private set; } = 0; [HideInInspector] [NonSerialized] public bool dirtyDeformableTriangles = true; [HideInInspector] [NonSerialized] public bool dirtyDeformableEdges = true; [HideInInspector] [NonSerialized] public Oni.SimplexType dirtySimplices = Oni.SimplexType.All; [HideInInspector] [NonSerialized] public int dirtyRendering = 0; [HideInInspector] [NonSerialized] public int dirtyConstraints = 0; public bool synchronousSpatialQueries = false; private bool m_dirtyActiveParticles = true; public bool dirtyActiveParticles { set { m_dirtyActiveParticles = value; } get { return m_dirtyActiveParticles; } } private Bounds m_Bounds = new Bounds(); private Bounds m_BoundsWS = new Bounds(); private Plane[] planes = new Plane[6]; private Camera[] sceneCameras = new Camera[1]; // constraints: [NonSerialized] private IObiConstraints[] m_Constraints = new IObiConstraints[Oni.ConstraintTypeCount]; // constraint parameters: public Oni.ConstraintParameters distanceConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); public Oni.ConstraintParameters bendingConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters particleCollisionConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); public Oni.ConstraintParameters particleFrictionConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters collisionConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); public Oni.ConstraintParameters frictionConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters skinConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); public Oni.ConstraintParameters volumeConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters shapeMatchingConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters tetherConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters pinConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters stitchConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters densityConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Parallel, 1); public Oni.ConstraintParameters stretchShearConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); public Oni.ConstraintParameters bendTwistConstraintParameters = new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); public Oni.ConstraintParameters chainConstraintParameters = new Oni.ConstraintParameters(false, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); // rigidbodies: ObiNativeVector4List m_RigidbodyLinearVelocities; ObiNativeVector4List m_RigidbodyAngularVelocities; // colors: [NonSerialized] private ObiNativeColorList m_Colors; // cell indices: [NonSerialized] private ObiNativeInt4List m_CellCoords; // status: [NonSerialized] private ObiNativeIntList m_ActiveParticles; [NonSerialized] private ObiNativeIntList m_Simplices; // positions: [NonSerialized] private ObiNativeVector4List m_Positions; [NonSerialized] private ObiNativeVector4List m_PrevPositions; [NonSerialized] private ObiNativeVector4List m_RestPositions; [NonSerialized] private ObiNativeVector4List m_StartPositions; [NonSerialized] private ObiNativeVector4List m_EndPositions; [NonSerialized] private ObiNativeVector4List m_RenderablePositions; // orientations: [NonSerialized] private ObiNativeQuaternionList m_Orientations; [NonSerialized] private ObiNativeQuaternionList m_PrevOrientations; [NonSerialized] private ObiNativeQuaternionList m_RestOrientations; [NonSerialized] private ObiNativeQuaternionList m_StartOrientations; [NonSerialized] private ObiNativeQuaternionList m_EndOrientations; [NonSerialized] private ObiNativeQuaternionList m_RenderableOrientations; /**< renderable particle orientations.*/ // velocities: [NonSerialized] private ObiNativeVector4List m_Velocities; [NonSerialized] private ObiNativeVector4List m_AngularVelocities; // masses tensors: [NonSerialized] private ObiNativeFloatList m_InvMasses; [NonSerialized] private ObiNativeFloatList m_InvRotationalMasses; // external forces: [NonSerialized] private ObiNativeVector4List m_ExternalForces; [NonSerialized] private ObiNativeVector4List m_ExternalTorques; [NonSerialized] private ObiNativeVector4List m_Wind; // deltas: [NonSerialized] private ObiNativeVector4List m_PositionDeltas; [NonSerialized] private ObiNativeQuaternionList m_OrientationDeltas; [NonSerialized] private ObiNativeIntList m_PositionConstraintCounts; [NonSerialized] private ObiNativeIntList m_OrientationConstraintCounts; // particle collisions: [NonSerialized] private ObiNativeIntList m_CollisionMaterials; [NonSerialized] private ObiNativeIntList m_Phases; [NonSerialized] private ObiNativeIntList m_Filters; // particle shape: [NonSerialized] private ObiNativeVector4List m_PrincipalRadii; [NonSerialized] private ObiNativeVector4List m_RenderableRadii; [NonSerialized] private ObiNativeVector4List m_Normals; // fluids: [NonSerialized] private ObiNativeFloatList m_Life; [NonSerialized] private ObiNativeVector4List m_FluidData; [NonSerialized] private ObiNativeVector4List m_FluidMaterials; /**< fluidRadius / surfTension / viscosity / vorticity */ [NonSerialized] private ObiNativeVector4List m_FluidInterface; /**< drag / pressure / buoyancy / miscibility */ [NonSerialized] private ObiNativeVector4List m_UserData; [NonSerialized] private ObiNativeMatrix4x4List m_Anisotropy; // foam particles: [NonSerialized] private ObiNativeVector4List m_FoamPositions; /**< xyz = position, w = amount of neighbors*/ [NonSerialized] private ObiNativeVector4List m_FoamVelocities; /**< xyz = velocity, w = buoyancy*/ [NonSerialized] private ObiNativeVector4List m_FoamColors; [NonSerialized] private ObiNativeVector4List m_FoamAttributes; /**< life, aging rate, size, drag*/ [NonSerialized] private ObiNativeIntList m_FoamCount; // contacts: [NonSerialized] private ObiNativeContactList m_ColliderContacts; [NonSerialized] private ObiNativeContactList m_ParticleContacts; [NonSerialized] private ObiNativeEffectiveMassesList m_ContactEffectiveMasses; [NonSerialized] private ObiNativeEffectiveMassesList m_ParticleContactEffectiveMasses; // queries: [NonSerialized] private ObiNativeQueryShapeList m_BufferedQueryShapes; [NonSerialized] private ObiNativeAffineTransformList m_BufferedQueryTransforms; [NonSerialized] private ObiNativeQueryShapeList m_QueryShapes; [NonSerialized] private ObiNativeAffineTransformList m_QueryTransforms; [NonSerialized] private ObiNativeQueryResultList m_QueryResults; public ISolverImpl implementation { get; private set; } public bool initialized { get { return implementation != null; } } public IObiBackend simulationBackend { get { return m_SimulationBackend; } } public BackendType backendType { set { if (m_Backend != value) { m_Backend = value; UpdateBackend(); } } get { return m_Backend; } } public SimplexCounts simplexCounts { get { return m_SimplexCounts; } } /// /// Solver bounds expressed in world space. /// public UnityEngine.Bounds bounds { get { return m_BoundsWS; } } /// /// Solver bounds expressed in the solver's local space. /// public UnityEngine.Bounds localBounds { get { return m_Bounds; } } public bool isVisible { get; private set; } = true; public float maxScale { get; private set; } = 1; public bool simulationInFlight { get; private set; } = false; public int pendingQueryCount => bufferedQueryShapes.count; public int allocParticleCount { get { return particleToActor.Count(s => s != null && s.actor != null); } } public int activeParticleCount => activeParticles.count; public int contactCount { get { return (backendType == BackendType.Burst || OnCollision != null) ? colliderContacts.count : 0; } } public int particleContactCount { get { return (backendType == BackendType.Burst || OnParticleCollision != null) ? particleContacts.count : 0; } } public ParticleInActor[] particleToActor { get { if (m_ParticleToActor == null) m_ParticleToActor = new ParticleInActor[0]; return m_ParticleToActor; } } public ObiNativeIntList activeParticles { get { if (m_ActiveParticles == null) m_ActiveParticles = new ObiNativeIntList(); return m_ActiveParticles; } } #region Simplices public ObiNativeIntList simplices { get { if (m_Simplices == null) m_Simplices = new ObiNativeIntList(); return m_Simplices; } } public ObiNativeIntList points { get { if (m_Points == null) m_Points = new ObiNativeIntList(8); return m_Points; } } public ObiNativeIntList edges { get { if (m_Edges == null) m_Edges = new ObiNativeIntList(8); return m_Edges; } } public ObiNativeIntList triangles { get { if (m_Triangles == null) m_Triangles = new ObiNativeIntList(8); return m_Triangles; } } #endregion #region Rigidbodies public ObiNativeVector4List rigidbodyLinearDeltas { get { if (m_RigidbodyLinearVelocities == null) { m_RigidbodyLinearVelocities = new ObiNativeVector4List(); } return m_RigidbodyLinearVelocities; } } public ObiNativeVector4List rigidbodyAngularDeltas { get { if (m_RigidbodyAngularVelocities == null) { m_RigidbodyAngularVelocities = new ObiNativeVector4List(); } return m_RigidbodyAngularVelocities; } } #endregion public ObiNativeColorList colors { get { if (m_Colors == null) { m_Colors = new ObiNativeColorList(); } return m_Colors; } } public ObiNativeInt4List cellCoords { get { if (m_CellCoords == null) { m_CellCoords = new ObiNativeInt4List(8, 16, new VInt4(int.MaxValue)); } return m_CellCoords; } } #region Position arrays public ObiNativeVector4List positions { get { if (m_Positions == null) m_Positions = new ObiNativeVector4List(); return m_Positions; } } public ObiNativeVector4List prevPositions { get { if (m_PrevPositions == null) m_PrevPositions = new ObiNativeVector4List(); return m_PrevPositions; } } public ObiNativeVector4List restPositions { get { if (m_RestPositions == null) m_RestPositions = new ObiNativeVector4List(); return m_RestPositions; } } public ObiNativeVector4List startPositions { get { if (m_StartPositions == null) m_StartPositions = new ObiNativeVector4List(); return m_StartPositions; } } public ObiNativeVector4List endPositions { get { if (m_EndPositions == null) m_EndPositions = new ObiNativeVector4List(); return m_EndPositions; } } public ObiNativeVector4List renderablePositions { get { if (m_RenderablePositions == null) m_RenderablePositions = new ObiNativeVector4List(); return m_RenderablePositions; } } #endregion #region Orientation arrays public ObiNativeQuaternionList orientations { get { if (m_Orientations == null) m_Orientations = new ObiNativeQuaternionList(); return m_Orientations; } } public ObiNativeQuaternionList prevOrientations { get { if (m_PrevOrientations == null) m_PrevOrientations = new ObiNativeQuaternionList(); return m_PrevOrientations; } } public ObiNativeQuaternionList restOrientations { get { if (m_RestOrientations == null) m_RestOrientations = new ObiNativeQuaternionList(); return m_RestOrientations; } } public ObiNativeQuaternionList startOrientations { get { if (m_StartOrientations == null) m_StartOrientations = new ObiNativeQuaternionList(); return m_StartOrientations; } } public ObiNativeQuaternionList endOrientations { get { if (m_EndOrientations == null) m_EndOrientations = new ObiNativeQuaternionList(); return m_EndOrientations; } } public ObiNativeQuaternionList renderableOrientations { get { if (m_RenderableOrientations == null) m_RenderableOrientations = new ObiNativeQuaternionList(); return m_RenderableOrientations; } } #endregion #region Velocity arrays public ObiNativeVector4List velocities { get { if (m_Velocities == null) m_Velocities = new ObiNativeVector4List(); return m_Velocities; } } public ObiNativeVector4List angularVelocities { get { if (m_AngularVelocities == null) m_AngularVelocities = new ObiNativeVector4List(); return m_AngularVelocities; } } #endregion #region Mass arrays public ObiNativeFloatList invMasses { get { if (m_InvMasses == null) m_InvMasses = new ObiNativeFloatList(); return m_InvMasses; } } public ObiNativeFloatList invRotationalMasses { get { if (m_InvRotationalMasses == null) m_InvRotationalMasses = new ObiNativeFloatList(); return m_InvRotationalMasses; } } #endregion #region External forces public ObiNativeVector4List externalForces { get { if (m_ExternalForces == null) m_ExternalForces = new ObiNativeVector4List(); return m_ExternalForces; } } public ObiNativeVector4List externalTorques { get { if (m_ExternalTorques == null) m_ExternalTorques = new ObiNativeVector4List(); return m_ExternalTorques; } } public ObiNativeVector4List wind { get { if (m_Wind == null) m_Wind = new ObiNativeVector4List(); return m_Wind; } } #endregion #region Deltas public ObiNativeVector4List positionDeltas { get { if (m_PositionDeltas == null) m_PositionDeltas = new ObiNativeVector4List(); return m_PositionDeltas; } } public ObiNativeQuaternionList orientationDeltas { get { if (m_OrientationDeltas == null) m_OrientationDeltas = new ObiNativeQuaternionList(8, 16, new Quaternion(0, 0, 0, 0)); return m_OrientationDeltas; } } public ObiNativeIntList positionConstraintCounts { get { if (m_PositionConstraintCounts == null) m_PositionConstraintCounts = new ObiNativeIntList(); return m_PositionConstraintCounts; } } public ObiNativeIntList orientationConstraintCounts { get { if (m_OrientationConstraintCounts == null) m_OrientationConstraintCounts = new ObiNativeIntList(); return m_OrientationConstraintCounts; } } #endregion #region Shape and phase public ObiNativeIntList collisionMaterials { get { if (m_CollisionMaterials == null) m_CollisionMaterials = new ObiNativeIntList(); return m_CollisionMaterials; } } public ObiNativeIntList phases { get { if (m_Phases == null) m_Phases = new ObiNativeIntList(); return m_Phases; } } public ObiNativeIntList filters { get { if (m_Filters == null) m_Filters = new ObiNativeIntList(); return m_Filters; } } public ObiNativeVector4List renderableRadii { get { if (m_RenderableRadii == null) m_RenderableRadii = new ObiNativeVector4List(); return m_RenderableRadii; } } public ObiNativeVector4List principalRadii { get { if (m_PrincipalRadii == null) m_PrincipalRadii = new ObiNativeVector4List(); return m_PrincipalRadii; } } public ObiNativeVector4List normals { get { if (m_Normals == null) m_Normals = new ObiNativeVector4List(); return m_Normals; } } #endregion #region Fluid properties public ObiNativeFloatList life { get { if (m_Life == null) m_Life = new ObiNativeFloatList(); return m_Life; } } public ObiNativeVector4List fluidData { get { if (m_FluidData == null) m_FluidData = new ObiNativeVector4List(); return m_FluidData; } } public ObiNativeVector4List userData { get { if (m_UserData == null) m_UserData = new ObiNativeVector4List(); return m_UserData; } } public ObiNativeVector4List fluidInterface { get { if (m_FluidInterface == null) m_FluidInterface = new ObiNativeVector4List(); return m_FluidInterface; } } public ObiNativeVector4List fluidMaterials { get { if (m_FluidMaterials == null) m_FluidMaterials = new ObiNativeVector4List(); return m_FluidMaterials; } } public ObiNativeMatrix4x4List anisotropies { get { if (m_Anisotropy == null) m_Anisotropy = new ObiNativeMatrix4x4List(); return m_Anisotropy; } } public ObiNativeVector4List foamPositions { get { if (m_FoamPositions == null) m_FoamPositions = new ObiNativeVector4List(); return m_FoamPositions; } } public ObiNativeVector4List foamVelocities { get { if (m_FoamVelocities == null) m_FoamVelocities = new ObiNativeVector4List(); return m_FoamVelocities; } } public ObiNativeVector4List foamColors { get { if (m_FoamColors == null) m_FoamColors = new ObiNativeVector4List(); return m_FoamColors; } } public ObiNativeVector4List foamAttributes { get { if (m_FoamAttributes == null) m_FoamAttributes = new ObiNativeVector4List(); return m_FoamAttributes; } } public ObiNativeIntList foamCount { get { if (m_FoamCount == null) { m_FoamCount = new ObiNativeIntList(); m_FoamCount.ResizeUninitialized(9); // post-emission particle dispatch (4 floats), post-update particle dispatch (4 floats), // plus 1 extra float for storing currently alive particles while updating/killing. m_FoamCount.CopyFrom(new int[] { 0, 1, 1, 0, 0, 1, 1, 0, 0 }, 0, 0, 9); } return m_FoamCount; } } #endregion #region Contacts public ObiNativeContactList colliderContacts { get { if (m_ColliderContacts == null) m_ColliderContacts = new ObiNativeContactList(); return m_ColliderContacts; } } public ObiNativeContactList particleContacts { get { if (m_ParticleContacts == null) m_ParticleContacts = new ObiNativeContactList(); return m_ParticleContacts; } } public ObiNativeEffectiveMassesList contactEffectiveMasses { get { if (m_ContactEffectiveMasses == null) m_ContactEffectiveMasses = new ObiNativeEffectiveMassesList(); return m_ContactEffectiveMasses; } } public ObiNativeEffectiveMassesList particleContactEffectiveMasses { get { if (m_ParticleContactEffectiveMasses == null) m_ParticleContactEffectiveMasses = new ObiNativeEffectiveMassesList(); return m_ParticleContactEffectiveMasses; } } #endregion #region Queries private ObiNativeQueryShapeList bufferedQueryShapes { get { if (m_BufferedQueryShapes == null) m_BufferedQueryShapes = new ObiNativeQueryShapeList(); return m_BufferedQueryShapes; } } private ObiNativeAffineTransformList bufferedQueryTransforms { get { if (m_BufferedQueryTransforms == null) m_BufferedQueryTransforms = new ObiNativeAffineTransformList(8); return m_BufferedQueryTransforms; } } private ObiNativeQueryShapeList queryShapes { get { if (m_QueryShapes == null) m_QueryShapes = new ObiNativeQueryShapeList(); return m_QueryShapes; } } private ObiNativeAffineTransformList queryTransforms { get { if (m_QueryTransforms == null) m_QueryTransforms = new ObiNativeAffineTransformList(8); return m_QueryTransforms; } } public ObiNativeQueryResultList queryResults { get { if (m_QueryResults == null) m_QueryResults = new ObiNativeQueryResultList(); return m_QueryResults; } } #endregion public void OnEnable() { bufferedSynchronization = synchronization; accumulatedTime = 0; } private void FixedUpdate() { // first fixed update this frame: if (steps++ == 0) { // Signal the start of a frame, so we know the world needs to be updated this frame. ObiColliderWorld.GetInstance().FrameStart(); // Wait for the previous frame's simulation to end and GPU data to be available. if (bufferedSynchronization == Synchronization.Asynchronous) CompleteSimulation(); } if (bufferedSynchronization == Synchronization.SynchronousFixed) { // Update collider world, making sure it will also update after FixedUpdate() for solvers not using fixed sync. ObiColliderWorld.GetInstance().UpdateWorld(Time.fixedDeltaTime); ObiColliderWorld.GetInstance().FrameStart(); // kick off this step's simulation, and immediately wait for it to complete: StartSimulation(Time.fixedDeltaTime, 1); CompleteSimulation(); } } private void Update() { // Make sure ObiColliderWorld updates after all solvers have called CompleteSimulation() on their FixedUpdate. // This way we can be sure no physics updates are in flight. if (steps > 0 && bufferedSynchronization != Synchronization.SynchronousFixed) { ObiColliderWorld.GetInstance().UpdateWorld(Time.fixedDeltaTime * steps); } } private void LateUpdate() { var scale = transform.lossyScale; maxScale = Mathf.Max(Mathf.Max(scale.x, scale.y), scale.z); // Accumulate amount of time to simulate (duration of the frame - time already simulated) if (Application.isPlaying) accumulatedTime += Time.deltaTime - Time.fixedDeltaTime * steps; else { // if in editor, we don't accumulate any simulation time // and just update solver bounds before rendering/simulation. accumulatedTime = 0; UpdateBounds(); } if (bufferedSynchronization == Synchronization.Asynchronous || bufferedSynchronization == Synchronization.SynchronousFixed) Render(accumulatedTime); // if in play mode, kick off this frame's simulation. if (Application.isPlaying && bufferedSynchronization != Synchronization.SynchronousFixed) StartSimulation(Time.fixedDeltaTime, steps); if (bufferedSynchronization == Synchronization.Synchronous) { // if the simulation has been stepped this frame, // sychronously wait for completion before rendering. if (steps > 0) CompleteSimulation(); Render(accumulatedTime); } // Reset step counter to zero, now that // simulation tasks for this frame have been dispatched. steps = 0; } private void OnDestroy() { // Remove all actors from the solver. This will trigger Teardown() when the last actor is removed. while (actors.Count > 0) RemoveActor(actors[actors.Count - 1]); } private void CreateBackend() { switch (m_Backend) { #if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS) case BackendType.Burst: m_SimulationBackend = new BurstBackend(); break; #endif case BackendType.Compute: if (SystemInfo.supportsComputeShaders) m_SimulationBackend = new ComputeBackend(); else goto default; break; default: Debug.LogWarning("The Burst backend depends on the following packages: Mathematics, Collections, Jobs and Burst. Please install the required dependencies. Simulation will fall back to the compute backend, if possible."); if (SystemInfo.supportsComputeShaders) m_SimulationBackend = new ComputeBackend(); else { Debug.LogError("This platform doesn't support compute shaders. Please switch to the Burst backend."); m_SimulationBackend = new NullBackend(); } break; } } public void Initialize() { if (!initialized) { CreateBackend(); substepTime = Time.fixedDeltaTime / substeps; // Set up local actor and particle buffers: actors = new List(); freeList = new ObiNativeIntList(); m_ParticleToActor = new ParticleInActor[0]; deformableUVs = new ObiNativeVector2List(); deformableTriangles = new ObiNativeIntList(); deformableEdges = new ObiNativeIntList(); // Create constraints: m_Constraints[(int)Oni.ConstraintType.Distance] = new ObiDistanceConstraintsData(); m_Constraints[(int)Oni.ConstraintType.Bending] = new ObiBendConstraintsData(); m_Constraints[(int)Oni.ConstraintType.Aerodynamics] = new ObiAerodynamicConstraintsData(); m_Constraints[(int)Oni.ConstraintType.StretchShear] = new ObiStretchShearConstraintsData(); m_Constraints[(int)Oni.ConstraintType.BendTwist] = new ObiBendTwistConstraintsData(); m_Constraints[(int)Oni.ConstraintType.Chain] = new ObiChainConstraintsData(); m_Constraints[(int)Oni.ConstraintType.ShapeMatching] = new ObiShapeMatchingConstraintsData(); m_Constraints[(int)Oni.ConstraintType.Volume] = new ObiVolumeConstraintsData(); m_Constraints[(int)Oni.ConstraintType.Tether] = new ObiTetherConstraintsData(); m_Constraints[(int)Oni.ConstraintType.Skin] = new ObiSkinConstraintsData(); m_Constraints[(int)Oni.ConstraintType.Pin] = new ObiPinConstraintsData(); // Create the solver: implementation = m_SimulationBackend.CreateSolver(this, 0); // Set data arrays: implementation.ParticleCountChanged(this); implementation.SetRigidbodyArrays(this); OnParticleCountChanged?.Invoke(this); // Initialize moving transform: InitializeTransformFrame(); // Initial collider world update: ObiColliderWorld.GetInstance().FrameStart(); ObiColliderWorld.GetInstance().UpdateWorld(0); OnInitialize?.Invoke(this); // Set initial parameter values: PushSolverParameters(); #if UNITY_EDITOR ObiActorEditorSelectionHandler.SolverInitialized(this); #endif } } public void Teardown() { if (initialized) { CompleteSimulation(); // Clear all constraints: PushConstraints(); // Destroy the solver: m_SimulationBackend.DestroySolver(implementation); implementation = null; // Free particle / rigidbody memory: FreeParticleArrays(); FreeRigidbodyArrays(); freeList.Dispose(); // Reset bounds: m_Bounds = new Bounds(); OnTeardown?.Invoke(this); #if UNITY_EDITOR ObiActorEditorSelectionHandler.SolverTeardown(this); #endif } } public void UpdateBackend() { // remove all actors, this will trigger a teardown: List temp = new List(actors); foreach (ObiActor actor in temp) actor.RemoveFromSolver(); // re-add all actors. // Solver will be re-initialized on adding the first one. foreach (ObiActor actor in temp) actor.AddToSolver(); } private void FreeRigidbodyArrays() { rigidbodyLinearDeltas.Dispose(); rigidbodyAngularDeltas.Dispose(); m_RigidbodyLinearVelocities = null; m_RigidbodyAngularVelocities = null; } public void EnsureRigidbodyArraysCapacity(int count) { if (initialized && (count > rigidbodyLinearDeltas.count || !rigidbodyLinearDeltas.isCreated)) { rigidbodyLinearDeltas.ResizeInitialized(count); rigidbodyAngularDeltas.ResizeInitialized(count); implementation.SetRigidbodyArrays(this); } } private void FreeParticleArrays() { activeParticles.Dispose(); simplices.Dispose(); points.Dispose(); edges.Dispose(); triangles.Dispose(); colors.Dispose(); cellCoords.Dispose(); startPositions.Dispose(); endPositions.Dispose(); startOrientations.Dispose(); endOrientations.Dispose(); positions.Dispose(); prevPositions.Dispose(); restPositions.Dispose(); velocities.Dispose(); orientations.Dispose(); prevOrientations.Dispose(); restOrientations.Dispose(); angularVelocities.Dispose(); invMasses.Dispose(); invRotationalMasses.Dispose(); principalRadii.Dispose(); collisionMaterials.Dispose(); phases.Dispose(); filters.Dispose(); renderablePositions.Dispose(); renderableOrientations.Dispose(); renderableRadii.Dispose(); fluidInterface.Dispose(); fluidMaterials.Dispose(); foamPositions.Dispose(); foamVelocities.Dispose(); foamColors.Dispose(); foamAttributes.Dispose(); foamCount.Dispose(); anisotropies.Dispose(); life.Dispose(); fluidData.Dispose(); userData.Dispose(); externalForces.Dispose(); externalTorques.Dispose(); wind.Dispose(); positionDeltas.Dispose(); orientationDeltas.Dispose(); positionConstraintCounts.Dispose(); orientationConstraintCounts.Dispose(); normals.Dispose(); colliderContacts.Dispose(); particleContacts.Dispose(); contactEffectiveMasses.Dispose(); particleContactEffectiveMasses.Dispose(); bufferedQueryShapes.Dispose(); bufferedQueryTransforms.Dispose(); queryShapes.Dispose(); queryTransforms.Dispose(); queryResults.Dispose(); deformableUVs.Dispose(); deformableTriangles.Dispose(); deformableEdges.Dispose(); m_ActiveParticles = null; m_Simplices = null; m_Points = null; m_Edges = null; m_Triangles = null; m_Colors = null; m_CellCoords = null; m_Positions = null; m_RestPositions = null; m_PrevPositions = null; m_StartPositions = null; m_EndPositions = null; m_RenderablePositions = null; m_Orientations = null; m_RestOrientations = null; m_PrevOrientations = null; m_StartOrientations = null; m_EndOrientations = null; m_RenderableOrientations = null; m_Velocities = null; m_AngularVelocities = null; m_InvMasses = null; m_InvRotationalMasses = null; m_ExternalForces = null; m_ExternalTorques = null; m_Wind = null; m_PositionDeltas = null; m_OrientationDeltas = null; m_PositionConstraintCounts = null; m_OrientationConstraintCounts = null; m_CollisionMaterials = null; m_Phases = null; m_Filters = null; m_RenderableRadii = null; m_PrincipalRadii = null; m_Normals = null; m_Life = null; m_FluidData = null; m_UserData = null; m_FluidInterface = null; m_FluidMaterials = null; m_FoamPositions = null; m_FoamVelocities = null; m_FoamColors = null; m_FoamAttributes = null; m_FoamCount = null; m_Anisotropy = null; m_ColliderContacts = null; m_ParticleContacts = null; m_ContactEffectiveMasses = null; m_ParticleContactEffectiveMasses = null; m_BufferedQueryShapes = null; m_BufferedQueryTransforms = null; m_QueryShapes = null; m_QueryTransforms = null; m_QueryResults = null; deformableUVs = null; deformableTriangles = null; deformableEdges = null; } private void EnsureParticleArraysCapacity(int count) { // only resize if the count is larger than the current amount of particles: if (count >= positions.count) { colors.ResizeInitialized(count, Color.white); startPositions.ResizeInitialized(count); endPositions.ResizeInitialized(count); positions.ResizeInitialized(count); prevPositions.ResizeInitialized(count); restPositions.ResizeInitialized(count); startOrientations.ResizeInitialized(count, Quaternion.identity); endOrientations.ResizeInitialized(count, Quaternion.identity); orientations.ResizeInitialized(count, Quaternion.identity); prevOrientations.ResizeInitialized(count, Quaternion.identity); restOrientations.ResizeInitialized(count, Quaternion.identity); renderablePositions.ResizeInitialized(count); renderableOrientations.ResizeInitialized(count, Quaternion.identity); velocities.ResizeInitialized(count); angularVelocities.ResizeInitialized(count); invMasses.ResizeInitialized(count); invRotationalMasses.ResizeInitialized(count); principalRadii.ResizeInitialized(count); collisionMaterials.ResizeInitialized(count); phases.ResizeInitialized(count); filters.ResizeInitialized(count); renderableRadii.ResizeInitialized(count); fluidInterface.ResizeInitialized(count); fluidMaterials.ResizeInitialized(count); anisotropies.ResizeInitialized(count); life.ResizeInitialized(count); fluidData.ResizeInitialized(count); userData.ResizeInitialized(count); externalForces.ResizeInitialized(count); externalTorques.ResizeInitialized(count); wind.ResizeInitialized(count); positionDeltas.ResizeInitialized(count); orientationDeltas.ResizeInitialized(count, new Quaternion(0, 0, 0, 0)); positionConstraintCounts.ResizeInitialized(count); orientationConstraintCounts.ResizeInitialized(count); normals.ResizeInitialized(count); } if (count >= m_ParticleToActor.Length) { Array.Resize(ref m_ParticleToActor, count * 2); } } private void UpdateFoamParticleCapacity() { if (maxFoamParticles != foamPositions.count) { foamPositions.ResizeUninitialized((int)maxFoamParticles); foamVelocities.ResizeUninitialized((int)maxFoamParticles); foamColors.ResizeUninitialized((int)maxFoamParticles); foamAttributes.ResizeUninitialized((int)maxFoamParticles); foamCount[3] = Mathf.Min(foamCount[3], (int)maxFoamParticles); implementation.MaxFoamParticleCountChanged(this); } } private void AllocateParticles(ObiNativeIntList particleIndices) { // If attempting to allocate more particles than we have: if (particleIndices.count > freeList.count) { int grow = particleIndices.count - freeList.count; // append new free indices: for (int i = 0; i < grow; ++i) freeList.Add(positions.count + i); // grow particle arrays: EnsureParticleArraysCapacity(positions.count + particleIndices.count); } // determine first particle in the free list to use: int first = freeList.count - particleIndices.count; // copy free indices to the input array: particleIndices.CopyFrom(freeList, first, 0, particleIndices.count); // shorten the free list: freeList.ResizeUninitialized(first); } private void FreeParticles(ObiNativeIntList particleIndices) { freeList.AddRange(particleIndices); } private void CollisionCallbacks() { if (OnCollision != null) { colliderContacts.WaitForReadback(); OnCollision.Invoke(this, colliderContacts); } if (OnParticleCollision != null) { particleContacts.WaitForReadback(); OnParticleCollision.Invoke(this, particleContacts); } if (OnAdvection != null) { foamPositions.WaitForReadback(); foamVelocities.WaitForReadback(); foamAttributes.WaitForReadback(); foamColors.WaitForReadback(); foamCount.WaitForReadback(); OnAdvection.Invoke(this); foamPositions.Upload(); foamVelocities.Upload(); foamAttributes.Upload(); foamColors.Upload(); foamCount.Upload(); } } public void StartSimulation(float stepDelta, int simulationSteps) { if (simulationSteps > 0) { // Complete previous simulation call, if any: CompleteSimulation(); simulatedTime = stepDelta * simulationSteps; // physics time that has been simulated by Unity this frame. Might be more than the time we actually simulate, due to maxStepsPerFrame. substepTime = stepDelta / substeps; // duration of a substep. // only update buffered synchronization before starting a new step. bufferedSynchronization = synchronization; // AddActor() calls are buffered, new actors should be inserted as this particular point in time: while (addBuffer.TryDequeue(out ObiActor actor)) InsertBufferedActor(actor); if (initialized && maxStepsPerFrame > 0) { simulationInFlight = true; int frameSubsteps = Mathf.Min(maxStepsPerFrame, simulationSteps) * substeps; // amount of substeps *actually* simulated this frame. float timeToSimulate = frameSubsteps * substepTime; // amount of time we need to simulate, might be less than simulatedTime. UpdateFoamParticleCapacity(); // Update collision materials/rigidbodies after adding new actors to make sure collision materials are up to date. // Also call it before SimulationStart, so that constraints referencing rigidbodies (such as Pin constraints in attachments) // use handle data that's up to date. using (m_UpdateColliderWorld.Auto()) { ObiColliderWorld.GetInstance().UpdateCollisionMaterials(); EnsureRigidbodyArraysCapacity(ObiColliderWorld.GetInstance().rigidbodyHandles.Count); } // We need SimulationStart to be called before PushConstraints for updating pin constraints. OnSimulationStart?.Invoke(this, timeToSimulate, substepTime); foreach (ObiActor actor in actors) actor.SimulationStart(timeToSimulate, substepTime); // Update the active particles array: PushActiveParticles(); // Update the simplices array: PushSimplices(); // Update deformable triangles/edges arrays: PushDeformableTriangles(); PushDeformableEdges(); // Update constraint batches: PushConstraints(); // Update parameters: parameters.gravity = gravitySpace == Space.World ? transform.InverseTransformVector(gravity) : gravity; parameters.ambientWind = windSpace == Space.World ? transform.InverseTransformVector(ambientWind) : ambientWind; implementation.SetParameters(parameters); // Notify render systems that a step has started: m_RenderSystems.Step(); // CPU -> GPU data transfer implementation.PushData(); // Update inertial reference frame: simulationHandle = UpdateTransformFrame(simulatedTime); // Calculate bounds: simulationHandle = implementation.UpdateBounds(simulationHandle, simulatedTime); // Perform collision detection: if (simulateWhenInvisible || isVisible) { simulationHandle = implementation.CollisionDetection(simulationHandle, simulatedTime); simulationHandle?.Complete(); // complete here, since several jobs need fluidParticles.Length. TODO: use deferred jobs. } // Perform queued queries. This ensures queries "see" the same state as collision callbacks, and ensures no // data races (queries performed while the simulation is running). FlushSpatialQueries(); // Divide each step into multiple substeps: float timeLeft = simulatedTime; for (int i = 0; i < frameSubsteps; ++i) { // Only update the solver if it is visible, or if we must simulate even when invisible. if ((simulateWhenInvisible || isVisible) && initialized) { simulationHandle = implementation.Substep(simulationHandle, stepDelta, substepTime, simulationSteps, timeLeft); } timeLeft -= substepTime; } timeSinceSimulationStart += timeToSimulate; // Request GPU data to be brought back to the CPU. RequestReadback(); } } } private void FlushSpatialQueries() { while (bufferedQueryShapes.count > 0) { // copy buffered queries to the lists used for performing the queries: queryShapes.ResizeUninitialized(bufferedQueryShapes.count); queryTransforms.ResizeUninitialized(bufferedQueryTransforms.count); queryShapes.CopyFrom(bufferedQueryShapes); queryTransforms.CopyFrom(bufferedQueryTransforms); bufferedQueryShapes.Clear(); bufferedQueryTransforms.Clear(); implementation.SpatialQuery(queryShapes, queryTransforms, queryResults); queryResults.Readback(); if (synchronousSpatialQueries) { // Wait for query results right now and trigger query results event: queryResults.WaitForReadback(); OnSpatialQueryResults?.Invoke(this, queryResults); } } } public void CompleteSimulation() { // if the solver is not yet initialized or there's no previous call to SimulationStart, return. if (!initialized || !simulationInFlight) return; // Make sure previous simulation call has completed. simulationHandle?.Complete(); // Update physics state for rendering, and wait for GPU readbacks to finish. implementation.FinishSimulation(); // Trigger simulation end callback, after GPU readbacks are completed but before query/collision callbacks. OnSimulationEnd?.Invoke(this, simulatedTime, substepTime); foreach (ObiActor actor in actors) actor.SimulationEnd(simulatedTime, substepTime); // Update rigidbody velocities with the simulation results: ObiColliderWorld.GetInstance().UpdateRigidbodyVelocities(this); // Trigger spatial query results: if (!synchronousSpatialQueries) { queryResults.WaitForReadback(); OnSpatialQueryResults?.Invoke(this, queryResults); } // Trigger collision callbacks now that GPU data (including rigidbody velocity deltas) is available. CollisionCallbacks(); simulationInFlight = false; } /// /// Performs physics state interpolation and updates rendering. /// /// Remaining time that could not be simulated during this frame (in seconds). This is used to interpolate physics state. public void Render(float unsimulatedTime) { if (!initialized) return; // Only perform interpolation if the solver is visible, or if we must simulate even when invisible. if (simulateWhenInvisible || isVisible) { using (m_StateInterpolationPerfMarker.Auto()) { // interpolate physics state: simulationHandle = implementation.ApplyInterpolation(simulationHandle, startPositions, startOrientations, Time.fixedDeltaTime, unsimulatedTime); simulationHandle?.Complete(); } } // test bounds against all cameras to update visibility. UpdateVisibility(); OnInterpolate?.Invoke(this, simulatedTime, substepTime); foreach (ObiActor actor in actors) actor.Interpolate(simulatedTime, substepTime); if (!Application.isPlaying) { // in-editor, actors update their positions/orientations in Interpolate when transformed, // so we must copy them to the GPU: positions.Upload(); orientations.Upload(); renderablePositions.Upload(); renderableOrientations.Upload(); } // Update render systems if dirty: if (dirtyRendering != 0) { m_RenderSystems.Setup(dirtyRendering); dirtyRendering = 0; } // Only render if visible: if (simulateWhenInvisible || isVisible) m_RenderSystems.Render(); } private void UpdateBounds() { // While in-editor, update active particles and simplices so that // solver bounds are correct. if (initialized) { PushActiveParticles(); PushSimplices(); simulationHandle = UpdateTransformFrame(0); simulationHandle = implementation.UpdateBounds(simulationHandle, 0); simulationHandle?.Complete(); } } private void RequestReadback() { if (!initialized) return; OnRequestReadback?.Invoke(this); foreach (ObiActor actor in actors) actor.RequestReadback(); implementation.RequestReadback(); // We must read the entire contacts buffer instead of the amount of contacts the CPU // has from last frame, since we need to get both the counter value and the contacts data on the same // frame. Alternative would be to read back amount of contacts from last frame, but that // means we could find invalid/uninitialized contacts if the amount of contacts decreases from one frame to the next. if (OnCollision != null) colliderContacts.Readback(); if (OnParticleCollision != null) particleContacts.Readback(); if (OnAdvection != null) { foamPositions.Readback(); foamVelocities.Readback(); foamAttributes.Readback(); foamColors.Readback(); foamCount.Readback(); } } /// /// Adds an actor to the solver. /// /// Attemps to add the actor to this solver returning whether this was successful or not. In case the actor was already added, or had no reference to a blueprint, this operation will return false. /// If this was the first actor added to the solver, will attempt to initialize the solver. /// While in play mode, if the actor is sucessfully added to the solver, will also call actor.LoadBlueprint(). /// An actor. /// /// Whether the actor was sucessfully added. /// public bool AddActor(ObiActor actor) { if (actor == null || actors == null || actor.sourceBlueprint == null || actor.sourceBlueprint.empty || actors.Contains(actor) || addBuffer.Contains(actor)) return false; // in-editor, we insert actors right away since the simulation is not running, // yet we need to perform rendering. if (!Application.isPlaying) InsertBufferedActor(actor); else addBuffer.Enqueue(actor); return true; } /// /// Attempts to remove an actor from this solver, and returns whether this was sucessful or not. /// /// Will only reurn true if the actor had been previously added successfully to this solver. /// If the actor is sucessfully removed from the solver, will also call actor.UnloadBlueprint(). Once the last actor is removed from the solver, /// this method will attempt to tear down the solver. /// An actor. /// /// Whether the actor was sucessfully removed. /// public bool RemoveActor(ObiActor actor) { if (actor == null) return false; // remove from add buffer: TODO: use list instead of queue. addBuffer = new Queue(addBuffer.Where(s => s != actor)); // Find actor index in our actors array: int index = actors.IndexOf(actor); // If we are in charge of this actor indeed, perform all steps necessary to release it. if (index >= 0) { actor.UnloadBlueprint(this); for (int i = 0; i < actor.solverIndices.count; ++i) particleToActor[actor.solverIndices[i]] = null; FreeParticles(actor.solverIndices); freeGroupIDs.Push(actor.groupID); actors.RemoveAt(index); actor.solverIndices.Dispose(); actor.solverIndices = null; for (int i = 0; i < actor.solverBatchOffsets.Length; ++i) actor.solverBatchOffsets[i].Clear(); // If this was the last actor in the solver, tear it down: if (actors.Count == 0) Teardown(); return true; } return false; } private void InsertBufferedActor(ObiActor actor) { if (actor == null) return; // If the solver is not initialized yet, do so: Initialize(); if (actor.solverIndices == null) actor.solverIndices = new ObiNativeIntList(); actor.solverIndices.ResizeUninitialized(actor.sourceBlueprint.particleCount); AllocateParticles(actor.solverIndices); for (int i = 0; i < actor.solverIndices.count; ++i) particleToActor[actor.solverIndices[i]] = new ParticleInActor(actor, i); actors.Add(actor); if (freeGroupIDs.Count == 0) freeGroupIDs.Push(actors.Count); actor.groupID = freeGroupIDs.Pop(); actor.LoadBlueprint(this); implementation.ParticleCountChanged(this); OnParticleCountChanged?.Invoke(this); } /// /// Updates solver parameters. /// /// Call this after modifying solver or constraint parameters. public void PushSolverParameters() { if (!initialized) return; implementation.SetParameters(parameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Distance, ref distanceConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Bending, ref bendingConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.ParticleCollision, ref particleCollisionConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.ParticleFriction, ref particleFrictionConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Collision, ref collisionConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Friction, ref frictionConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Density, ref densityConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Skin, ref skinConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Volume, ref volumeConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.ShapeMatching, ref shapeMatchingConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Tether, ref tetherConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Pin, ref pinConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Stitch, ref stitchConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.StretchShear, ref stretchShearConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.BendTwist, ref bendTwistConstraintParameters); implementation.SetConstraintGroupParameters(Oni.ConstraintType.Chain, ref chainConstraintParameters); if (OnUpdateParameters != null) OnUpdateParameters(this); } /// /// Returns the parameters used by a given constraint type. /// /// If you know the type of the constraints at runtime, /// this is the same as directly accessing the appropiate public Oni.ConstraintParameters struct in the solver. /// Type of the constraints whose parameters will be returned by this method. /// /// Parameters for the constraints of the specified type. /// public Oni.ConstraintParameters GetConstraintParameters(Oni.ConstraintType constraintType) { switch (constraintType) { case Oni.ConstraintType.Distance: return distanceConstraintParameters; case Oni.ConstraintType.Bending: return bendingConstraintParameters; case Oni.ConstraintType.ParticleCollision: return particleCollisionConstraintParameters; case Oni.ConstraintType.ParticleFriction: return particleFrictionConstraintParameters; case Oni.ConstraintType.Collision: return collisionConstraintParameters; case Oni.ConstraintType.Friction: return frictionConstraintParameters; case Oni.ConstraintType.Skin: return skinConstraintParameters; case Oni.ConstraintType.Volume: return volumeConstraintParameters; case Oni.ConstraintType.ShapeMatching: return shapeMatchingConstraintParameters; case Oni.ConstraintType.Tether: return tetherConstraintParameters; case Oni.ConstraintType.Pin: return pinConstraintParameters; case Oni.ConstraintType.Stitch: return stitchConstraintParameters; case Oni.ConstraintType.Density: return densityConstraintParameters; case Oni.ConstraintType.StretchShear: return stretchShearConstraintParameters; case Oni.ConstraintType.BendTwist: return bendTwistConstraintParameters; case Oni.ConstraintType.Chain: return chainConstraintParameters; default: return new Oni.ConstraintParameters(true, Oni.ConstraintParameters.EvaluationOrder.Sequential, 1); } } /// /// Returns the runtime representation of constraints of a given type being simulated by this solver. /// /// Type of the constraints that will be returned by this method. /// /// The runtime constraints of the type speficied. /// public IObiConstraints GetConstraintsByType(Oni.ConstraintType type) { int index = (int)type; if (m_Constraints != null && index >= 0 && index < m_Constraints.Length) return m_Constraints[index]; return null; } private void PushActiveParticles() { if (dirtyActiveParticles) { using (m_PushActiveParticles.Auto()) { activeParticles.Clear(); for (int i = 0; i < actors.Count; ++i) { if (actors[i].isActiveAndEnabled) activeParticles.AddRange(actors[i].solverIndices, actors[i].activeParticleCount); } implementation.SetActiveParticles(activeParticles); dirtyActiveParticles = false; } } } private void PushDeformableTriangles() { if (dirtyDeformableTriangles) { using (m_PushDeformableTriangles.Auto()) { deformableTriangles.Clear(); deformableUVs.Clear(); for (int i = 0; i < actors.Count; ++i) { ObiActor currentActor = actors[i]; if (currentActor.isActiveAndEnabled) { currentActor.ProvideDeformableTriangles(deformableTriangles, deformableUVs); } } implementation.SetDeformableTriangles(deformableTriangles, deformableUVs); dirtyDeformableTriangles = false; } } } private void PushDeformableEdges() { if (dirtyDeformableEdges) { using (m_PushDeformableEdges.Auto()) { deformableEdges.Clear(); for (int i = 0; i < actors.Count; ++i) { ObiActor currentActor = actors[i]; if (currentActor.isActiveAndEnabled) { currentActor.ProvideDeformableEdges(deformableEdges); } } implementation.SetDeformableEdges(deformableEdges); dirtyDeformableEdges = false; } } } private void PushSimplices() { if (dirtySimplices != Oni.SimplexType.None) { using (m_PushSimplices.Auto()) { simplices.Clear(); if ((dirtySimplices & Oni.SimplexType.Point) != 0) points.Clear(); if ((dirtySimplices & Oni.SimplexType.Edge) != 0) edges.Clear(); if ((dirtySimplices & Oni.SimplexType.Triangle) != 0) triangles.Clear(); for (int i = 0; i < actors.Count; ++i) { var currentActor = actors[i]; if (currentActor.isActiveAndEnabled && currentActor.isLoaded) { //simplex based contacts if (currentActor.surfaceCollisions) { if (currentActor.sharedBlueprint.points != null && (dirtySimplices & Oni.SimplexType.Point) != 0) for (int j = 0; j < currentActor.sharedBlueprint.points.Length; ++j) { int actorIndex = currentActor.sharedBlueprint.points[j]; if (actorIndex < currentActor.activeParticleCount) points.Add(currentActor.solverIndices[actorIndex]); } if (currentActor.sharedBlueprint.edges != null && (dirtySimplices & Oni.SimplexType.Edge) != 0) for (int j = 0; j < currentActor.sharedBlueprint.edges.Length / 2; ++j) { int actorIndex1 = currentActor.sharedBlueprint.edges[j * 2]; int actorIndex2 = currentActor.sharedBlueprint.edges[j * 2 + 1]; if (actorIndex1 < currentActor.activeParticleCount && actorIndex2 < currentActor.activeParticleCount) { edges.Add(currentActor.solverIndices[actorIndex1]); edges.Add(currentActor.solverIndices[actorIndex2]); } } if (currentActor.sharedBlueprint.triangles != null && (dirtySimplices & Oni.SimplexType.Triangle) != 0) for (int j = 0; j < currentActor.sharedBlueprint.triangles.Length / 3; ++j) { int actorIndex1 = currentActor.sharedBlueprint.triangles[j * 3]; int actorIndex2 = currentActor.sharedBlueprint.triangles[j * 3 + 1]; int actorIndex3 = currentActor.sharedBlueprint.triangles[j * 3 + 2]; if (actorIndex1 < currentActor.activeParticleCount && actorIndex2 < currentActor.activeParticleCount && actorIndex3 < currentActor.activeParticleCount) { triangles.Add(currentActor.solverIndices[actorIndex1]); triangles.Add(currentActor.solverIndices[actorIndex2]); triangles.Add(currentActor.solverIndices[actorIndex3]); } } } // particle based contacts else if ((dirtySimplices & Oni.SimplexType.Point) != 0) { // generate a point simplex out of each active particle: points.AddRange(currentActor.solverIndices, currentActor.activeParticleCount); } } } simplices.EnsureCapacity(points.count + edges.count + triangles.count); simplices.AddRange(triangles); simplices.AddRange(edges); simplices.AddRange(points); m_SimplexCounts = new SimplexCounts(points.count, edges.count / 2, triangles.count / 3); cellCoords.ResizeInitialized(m_SimplexCounts.simplexCount); implementation.SetSimplices(simplices, m_SimplexCounts); dirtySimplices = Oni.SimplexType.None; } } } private void PushConstraints() { if (dirtyConstraints != 0) { // Clear all dirty constraints: for (int i = 0; i < Oni.ConstraintTypeCount; ++i) if (m_Constraints[i] != null && ((1 << i) & dirtyConstraints) != 0) m_Constraints[i].Clear(); // Iterate over all actors, merging their batches together: for (int k = 0; k < actors.Count; ++k) { if (actors[k].isLoaded) { for (int i = 0; i < Oni.ConstraintTypeCount; ++i) if (m_Constraints[i] != null && ((1 << i) & dirtyConstraints) != 0) { var constraints = actors[k].GetConstraintsByType((Oni.ConstraintType)i); m_Constraints[i].Merge(actors[k], constraints); } } } // Readd the constraints to the solver: for (int i = 0; i < Oni.ConstraintTypeCount; ++i) if (m_Constraints[i] != null && ((1 << i) & dirtyConstraints) != 0) m_Constraints[i].AddToSolver(this); // Reset the dirty flag: dirtyConstraints = 0; } } /** * Updates solver bounds, then checks if they're visible from at least one camera. If so, sets isVisible to true, false otherwise. */ private void UpdateVisibility() { using (m_UpdateVisibilityPerfMarker.Auto()) { using (m_GetSolverBoundsPerfMarker.Auto()) { // get bounds in solver space: Vector3 min = Vector3.zero, max = Vector3.zero; implementation.GetBounds(ref min, ref max); m_Bounds.SetMinMax(min, max); } if (m_Bounds.AreValid()) { using (m_TestBoundsPerfMarker.Auto()) { // transform bounds to world space: m_BoundsWS = m_Bounds.Transform(transform.localToWorldMatrix); using (m_GetAllCamerasPerfMarker.Auto()) { Array.Resize(ref sceneCameras, Camera.allCamerasCount); Camera.GetAllCameras(sceneCameras); } foreach (Camera cam in sceneCameras) { GeometryUtility.CalculateFrustumPlanes(cam, planes); if (GeometryUtility.TestPlanesAABB(planes, m_BoundsWS)) { if (!isVisible) { isVisible = true; foreach (ObiActor actor in actors) actor.OnSolverVisibilityChanged(isVisible); } return; } } } } if (isVisible) { isVisible = false; foreach (ObiActor actor in actors) actor.OnSolverVisibilityChanged(isVisible); } } } private void InitializeTransformFrame() { Vector4 translation = transform.position; Vector4 scale = transform.lossyScale; Quaternion rotation = transform.rotation; implementation.InitializeFrame(translation, scale, rotation); } private IObiJobHandle UpdateTransformFrame(float dt) { Vector4 translation = transform.position; Vector4 scale = transform.lossyScale; Quaternion rotation = transform.rotation; implementation.UpdateFrame(translation, scale, rotation, dt); return implementation.ApplyFrame(worldLinearInertiaScale, worldAngularInertiaScale, dt); } public void RegisterRenderSystem(IRenderSystem renderSystem) { m_RenderSystems.RegisterRenderSystem(renderSystem); } public void UnregisterRenderSystem(IRenderSystem renderSystem) { m_RenderSystems.UnregisterRenderSystem(renderSystem); } public RenderSystem GetRenderSystem() where T : ObiRenderer { return m_RenderSystems.GetRenderSystem(); } public IRenderSystem GetRenderSystem(Oni.RenderingSystemType type) { return m_RenderSystems.GetRenderSystem(type); } /// /// Enqueues a generic spatial query to be performed during the next physics update. /// If called when the solver is yet uninitialized, /// the query will be ignored and this method will return -1. /// /// Query shape to test against all simplices in the solver. /// Transform to apply to the query shape. /// /// Index of the query in the queue. Use the queryIndex member of each query result to correlate each result to the query that spawned it. For instance: /// a query result with queryIndex 5, belongs to the query shape at index 5 in the queue. /// public int EnqueueSpatialQuery(QueryShape shape, AffineTransform transform) { // if the solver is not initialized, bail out. if (!initialized) return -1; int index = bufferedQueryShapes.count; bufferedQueryShapes.Add(shape); bufferedQueryTransforms.Add(transform); return index; } /// /// Enqueues multiple generic spatial query to be performed during the next physics update. /// If called when the solver is yet uninitialized, /// the query will be ignored and this method will return -1. /// /// Query shapes to test against all simplices in the solver. /// Transforms to apply to the query shapes. /// /// Index of the first query in the queue. Use the queryIndex member of each query result to correlate each result to the query that spawned it. For instance: /// a query result with queryIndex 5, belongs to the query shape at index 5 in the queue. /// public int EnqueueSpatialQueries(ObiNativeQueryShapeList shapes, ObiNativeAffineTransformList transforms) { // if the solver is not initialized or input is not ok, bail out. if (!initialized || shapes == null || transforms == null || shapes.count != transforms.count) return -1; int index = bufferedQueryShapes.count; bufferedQueryShapes.AddRange(shapes); bufferedQueryTransforms.AddRange(transforms); return index; } /// /// Enqueues a raycast to be performed during the next physics update. /// If called when the solver is yet uninitialized, /// the query will be ignored and this method will return -1. /// /// Ray to cast against all simplices in the solver. Expressed in world space. /// Filter (mask, category) used to filter out collisions against certain simplices. /// Ray length. /// /// Ray thickness. If the ray hits a simplex, hitInfo will contain a point on the simplex. /// If it merely passes near the simplex (within its thickness distance, but no actual hit), it will contain the point on the ray closest to the simplex surface. /// /// Index of the query in the queue. Use the queryIndex member of each query result to correlate each result to the query that spawned it. For instance: /// a query result with queryIndex 5, belongs to the query shape at index 5 in the queue. /// public int EnqueueRaycast(Ray ray, int filter, float maxDistance = 100, float rayThickness = 0) { // if the solver is not initialized or simulation is currently underway, bail out. if (!initialized) return -1; int index = bufferedQueryShapes.count; bufferedQueryShapes.Add(new QueryShape { type = QueryShape.QueryType.Ray, center = ray.origin, size = ray.direction * maxDistance, contactOffset = rayThickness, maxDistance = 0.0001f, filter = filter }); bufferedQueryTransforms.Add(new AffineTransform(Vector4.zero, Quaternion.identity, Vector4.one)); return index; } } }