#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS) using UnityEngine; using Unity.Jobs; using Unity.Mathematics; using Unity.Collections; namespace Obi { public class BurstSolverImpl : ISolverImpl { ObiSolver m_Solver; public ObiSolver abstraction { get { return m_Solver; } } public int particleCount { get { return m_Solver.positions.count; } } public int activeParticleCount { get { return abstraction.activeParticles.count; } } public BurstInertialFrame inertialFrame { get { return m_InertialFrame; } } public BurstAffineTransform solverToWorld { get { return m_InertialFrame.frame; } } public BurstAffineTransform worldToSolver { get { return m_InertialFrame.frame.Inverse(); } } private const int maxBatches = 17; private ConstraintBatcher collisionConstraintBatcher; private ConstraintBatcher fluidConstraintBatcher; // Per-type constraints array: private IBurstConstraintsImpl[] constraints; // Per-type iteration padding array: private int[] padding = new int[Oni.ConstraintTypeCount]; // Pool job handles to avoid runtime alloc: private JobHandlePool jobHandlePool; // particle contact generation: public ParticleGrid particleGrid; public NativeArray particleContacts; public NativeArray particleBatchData; // fluid interaction generation: public NativeArray fluidInteractions; public NativeArray fluidBatchData; // collider contact generation: private BurstColliderWorld colliderGrid; public NativeArray colliderContacts; // misc data: public NativeArray activeParticles; private NativeList deformableTriangles; public NativeArray simplices; public SimplexCounts simplexCounts; private BurstInertialFrame m_InertialFrame; // local to world inertial frame. private int scheduledJobCounter = 0; // cached particle data arrays (just wrappers over raw unmanaged data held by the abstract solver) public NativeArray positions; public NativeArray restPositions; public NativeArray prevPositions; public NativeArray renderablePositions; public NativeArray orientations; public NativeArray restOrientations; public NativeArray prevOrientations; public NativeArray renderableOrientations; public NativeArray velocities; public NativeArray angularVelocities; public NativeArray invMasses; public NativeArray invRotationalMasses; public NativeArray invInertiaTensors; public NativeArray externalForces; public NativeArray externalTorques; public NativeArray wind; public NativeArray positionDeltas; public NativeArray orientationDeltas; public NativeArray positionConstraintCounts; public NativeArray orientationConstraintCounts; public NativeArray collisionMaterials; public NativeArray phases; public NativeArray filters; public NativeArray anisotropies; public NativeArray principalRadii; public NativeArray normals; public NativeArray vorticities; public NativeArray fluidData; public NativeArray userData; public NativeArray smoothingRadii; public NativeArray buoyancies; public NativeArray restDensities; public NativeArray viscosities; public NativeArray surfaceTension; public NativeArray vortConfinement; public NativeArray athmosphericDrag; public NativeArray athmosphericPressure; public NativeArray diffusion; public NativeArray cellCoords; public NativeArray simplexBounds; private ConstraintSorter contactSorter; public BurstSolverImpl(ObiSolver solver) { this.m_Solver = solver; jobHandlePool = new JobHandlePool(4); contactSorter = new ConstraintSorter(); // Initialize collision world: GetOrCreateColliderWorld(); colliderGrid.IncreaseReferenceCount(); deformableTriangles = new NativeList(64, Allocator.Persistent); // Initialize contact generation acceleration structure: particleGrid = new ParticleGrid(); // Initialize constraint batcher: collisionConstraintBatcher = new ConstraintBatcher(maxBatches); fluidConstraintBatcher = new ConstraintBatcher(maxBatches); // Initialize constraint arrays: constraints = new IBurstConstraintsImpl[Oni.ConstraintTypeCount]; constraints[(int)Oni.ConstraintType.Tether] = new BurstTetherConstraints(this); constraints[(int)Oni.ConstraintType.Volume] = new BurstVolumeConstraints(this); constraints[(int)Oni.ConstraintType.Chain] = new BurstChainConstraints(this); constraints[(int)Oni.ConstraintType.Bending] = new BurstBendConstraints(this); constraints[(int)Oni.ConstraintType.Distance] = new BurstDistanceConstraints(this); constraints[(int)Oni.ConstraintType.ShapeMatching] = new BurstShapeMatchingConstraints(this); constraints[(int)Oni.ConstraintType.BendTwist] = new BurstBendTwistConstraints(this); constraints[(int)Oni.ConstraintType.StretchShear] = new BurstStretchShearConstraints(this); constraints[(int)Oni.ConstraintType.Pin] = new BurstPinConstraints(this); constraints[(int)Oni.ConstraintType.ParticleCollision] = new BurstParticleCollisionConstraints(this); constraints[(int)Oni.ConstraintType.Density] = new BurstDensityConstraints(this); constraints[(int)Oni.ConstraintType.Collision] = new BurstColliderCollisionConstraints(this); constraints[(int)Oni.ConstraintType.Skin] = new BurstSkinConstraints(this); constraints[(int)Oni.ConstraintType.Aerodynamics] = new BurstAerodynamicConstraints(this); constraints[(int)Oni.ConstraintType.Stitch] = new BurstStitchConstraints(this); constraints[(int)Oni.ConstraintType.ParticleFriction] = new BurstParticleFrictionConstraints(this); constraints[(int)Oni.ConstraintType.Friction] = new BurstColliderFrictionConstraints(this); var c = constraints[(int)Oni.ConstraintType.Collision] as BurstColliderCollisionConstraints; c.CreateConstraintsBatch(); var f = constraints[(int)Oni.ConstraintType.Friction] as BurstColliderFrictionConstraints; f.CreateConstraintsBatch(); } public void Destroy() { for (int i = 0; i < constraints.Length; ++i) if (constraints[i] != null) constraints[i].Dispose(); // Get rid of particle and collider grids: particleGrid.Dispose(); if (colliderGrid != null) colliderGrid.DecreaseReferenceCount(); collisionConstraintBatcher.Dispose(); fluidConstraintBatcher.Dispose(); if (deformableTriangles.IsCreated) deformableTriangles.Dispose(); if (simplexBounds.IsCreated) simplexBounds.Dispose(); if (particleContacts.IsCreated) particleContacts.Dispose(); if (particleBatchData.IsCreated) particleBatchData.Dispose(); if (fluidInteractions.IsCreated) fluidInteractions.Dispose(); if (fluidBatchData.IsCreated) fluidBatchData.Dispose(); if (colliderContacts.IsCreated) colliderContacts.Dispose(); } public void ReleaseJobHandles() { jobHandlePool.ReleaseAll(); } // Utility function to count scheduled jobs. Call it once per job. // Will JobHandle.ScheduleBatchedJobs once there's a good bunch of scheduled jobs. public void ScheduleBatchedJobsIfNeeded() { if (scheduledJobCounter++ > 16) { scheduledJobCounter = 0; JobHandle.ScheduleBatchedJobs(); } } private void GetOrCreateColliderWorld() { colliderGrid = GameObject.FindObjectOfType(); if (colliderGrid == null) { var world = new GameObject("BurstCollisionWorld", typeof(BurstColliderWorld)); colliderGrid = world.GetComponent(); } } public void InitializeFrame(Vector4 translation, Vector4 scale, Quaternion rotation) { m_InertialFrame = new BurstInertialFrame(translation, scale, rotation); } public void UpdateFrame(Vector4 translation, Vector4 scale, Quaternion rotation, float deltaTime) { m_InertialFrame.Update(translation, scale, rotation, deltaTime); } public void ApplyFrame(float worldLinearInertiaScale, float worldAngularInertiaScale, float deltaTime) { // inverse linear part: float4x4 linear = float4x4.TRS(float3.zero, inertialFrame.frame.rotation, math.rcp(inertialFrame.frame.scale.xyz)); float4x4 linearInv = math.transpose(linear); // non-inertial frame accelerations: float4 angularVel = math.mul(linearInv, math.mul(float4x4.Scale(inertialFrame.angularVelocity.xyz), linear)).diagonal(); float4 eulerAccel = math.mul(linearInv, math.mul(float4x4.Scale(inertialFrame.angularAcceleration.xyz), linear)).diagonal(); float4 inertialAccel = math.mul(linearInv, inertialFrame.acceleration); var applyInertialForces = new ApplyInertialForcesJob() { activeParticles = activeParticles, positions = positions, velocities = velocities, invMasses = invMasses, angularVel = angularVel, inertialAccel = inertialAccel, eulerAccel = eulerAccel, worldLinearInertiaScale = worldLinearInertiaScale, worldAngularInertiaScale = worldAngularInertiaScale, deltaTime = deltaTime, }; applyInertialForces.Schedule(activeParticleCount, 64).Complete(); } public int GetDeformableTriangleCount() { return deformableTriangles.Length / 3; } public void SetDeformableTriangles(int[] indices, int num, int destOffset) { if (destOffset + num >= deformableTriangles.Length / 3) deformableTriangles.ResizeUninitialized((destOffset + num) * 3); for (int i = 0; i < num * 3; ++i) deformableTriangles[i + destOffset * 3] = indices[i]; } public int RemoveDeformableTriangles(int num, int sourceOffset) { if (deformableTriangles.IsCreated) { int amount = deformableTriangles.Length / 3; if (num < 0) { deformableTriangles.Clear(); return amount; } int set = ClampArrayAccess(amount, num, sourceOffset); int end = sourceOffset + set; // TODO: replace by built in method in 0.9.0 deformableTriangles.RemoveRangeBurst(sourceOffset * 3, (end - sourceOffset) * 3 ); return set; } return 0; } public void SetSimplices(ObiNativeIntList simplices, SimplexCounts counts) { this.simplices = simplices.AsNativeArray(); this.simplexCounts = counts; if (simplexBounds.IsCreated) simplexBounds.Dispose(); simplexBounds = new NativeArray(counts.simplexCount, Allocator.Persistent); cellCoords = abstraction.cellCoords.AsNativeArray(); } public void SetActiveParticles(ObiNativeIntList indices) { activeParticles = indices.AsNativeArray(); } int ClampArrayAccess(int size, int num, int offset) { return math.min(num, math.max(0, size - offset)); } public JobHandle RecalculateInertiaTensors(JobHandle inputDeps) { var updateInertiaTensors = new UpdateInertiaTensorsJob() { activeParticles = activeParticles, inverseMasses = abstraction.invMasses.AsNativeArray(), inverseRotationalMasses = abstraction.invRotationalMasses.AsNativeArray(), principalRadii = abstraction.principalRadii.AsNativeArray(), inverseInertiaTensors = abstraction.invInertiaTensors.AsNativeArray(), }; return updateInertiaTensors.Schedule(activeParticleCount, 128, inputDeps); } public void GetBounds(ref Vector3 min, ref Vector3 max) { int chunkSize = 4; NativeArray bounds = new NativeArray(activeParticleCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); var particleBoundsJob = new ParticleToBoundsJob() { activeParticles = activeParticles, positions = positions, radii = principalRadii, bounds = bounds }; JobHandle reduction = particleBoundsJob.Schedule(activeParticles.Length, 64); // parallel reduction: int chunks = bounds.Length; int stride = 1; while (chunks > 1) { var reductionJob = new BoundsReductionJob() { bounds = bounds, stride = stride, size = chunkSize, }; reduction = reductionJob.Schedule(chunks, 1, reduction); chunks = (int)math.ceil(chunks / (float)chunkSize); stride *= chunkSize; } reduction.Complete(); // the parallel reduction leaves the final bounds in the first entry: if (bounds.Length > 0) { min = bounds[0].min.xyz; max = bounds[0].max.xyz; } bounds.Dispose(); } public void ResetForces() { abstraction.externalForces.WipeToZero(); abstraction.externalTorques.WipeToZero(); abstraction.wind.WipeToZero(); // We're at the end of a whole step (not a substep), so dispose of contact buffers: if (particleContacts.IsCreated) particleContacts.Dispose(); if (particleBatchData.IsCreated) particleBatchData.Dispose(); if (colliderContacts.IsCreated) colliderContacts.Dispose(); } public int GetConstraintCount(Oni.ConstraintType type) { if ((int)type > 0 && (int)type < constraints.Length) return constraints[(int)type].GetConstraintCount(); return 0; } public void GetCollisionContacts(Oni.Contact[] contacts, int count) { NativeArray.Copy(colliderContacts.Reinterpret(),0, contacts,0,count); } public void GetParticleCollisionContacts(Oni.Contact[] contacts, int count) { NativeArray.Copy(particleContacts.Reinterpret(),0, contacts,0,count); } public void SetParameters(Oni.SolverParameters parameters) { // No need to implement. This backend grabs parameters from the abstraction when it needs them. } public void SetConstraintGroupParameters(Oni.ConstraintType type, ref Oni.ConstraintParameters parameters) { // No need to implement. This backend grabs parameters from the abstraction when it needs them. } public void ParticleCountChanged(ObiSolver solver) { positions = abstraction.positions.AsNativeArray(); restPositions = abstraction.restPositions.AsNativeArray(); prevPositions = abstraction.prevPositions.AsNativeArray(); renderablePositions = abstraction.renderablePositions.AsNativeArray(); orientations = abstraction.orientations.AsNativeArray(); restOrientations = abstraction.restOrientations.AsNativeArray(); prevOrientations = abstraction.prevOrientations.AsNativeArray(); renderableOrientations = abstraction.renderableOrientations.AsNativeArray(); velocities = abstraction.velocities.AsNativeArray(); angularVelocities = abstraction.angularVelocities.AsNativeArray(); invMasses = abstraction.invMasses.AsNativeArray(); invRotationalMasses = abstraction.invRotationalMasses.AsNativeArray(); invInertiaTensors = abstraction.invInertiaTensors.AsNativeArray(); externalForces = abstraction.externalForces.AsNativeArray(); externalTorques = abstraction.externalTorques.AsNativeArray(); wind = abstraction.wind.AsNativeArray(); positionDeltas = abstraction.positionDeltas.AsNativeArray(); orientationDeltas = abstraction.orientationDeltas.AsNativeArray(); positionConstraintCounts = abstraction.positionConstraintCounts.AsNativeArray(); orientationConstraintCounts = abstraction.orientationConstraintCounts.AsNativeArray(); collisionMaterials = abstraction.collisionMaterials.AsNativeArray(); phases = abstraction.phases.AsNativeArray(); filters = abstraction.filters.AsNativeArray(); anisotropies = abstraction.anisotropies.AsNativeArray(); principalRadii = abstraction.principalRadii.AsNativeArray(); normals = abstraction.normals.AsNativeArray(); vorticities = abstraction.vorticities.AsNativeArray(); fluidData = abstraction.fluidData.AsNativeArray(); userData = abstraction.userData.AsNativeArray(); smoothingRadii = abstraction.smoothingRadii.AsNativeArray(); buoyancies = abstraction.buoyancies.AsNativeArray(); restDensities = abstraction.restDensities.AsNativeArray(); viscosities = abstraction.viscosities.AsNativeArray(); surfaceTension = abstraction.surfaceTension.AsNativeArray(); vortConfinement = abstraction.vortConfinement.AsNativeArray(); athmosphericDrag = abstraction.atmosphericDrag.AsNativeArray(); athmosphericPressure = abstraction.atmosphericPressure.AsNativeArray(); diffusion = abstraction.diffusion.AsNativeArray(); } public void SetRigidbodyArrays(ObiSolver solver) { // No need to implement. This backend grabs arrays from the abstraction when it needs them. } public IConstraintsBatchImpl CreateConstraintsBatch(Oni.ConstraintType type) { return constraints[(int)type].CreateConstraintsBatch(); } public void DestroyConstraintsBatch(IConstraintsBatchImpl batch) { if (batch != null) constraints[(int)batch.constraintType].RemoveBatch(batch); } public IObiJobHandle CollisionDetection(float stepTime) { var fluidHandle = FindFluidParticles(); var inertiaUpdate = RecalculateInertiaTensors(fluidHandle); return jobHandlePool.Borrow().SetHandle(GenerateContacts(inertiaUpdate, stepTime)); } protected JobHandle FindFluidParticles() { var d = constraints[(int)Oni.ConstraintType.Density] as BurstDensityConstraints; // Update positions: var findFluidJob = new FindFluidParticlesJob() { activeParticles = activeParticles, phases = m_Solver.phases.AsNativeArray(), fluidParticles = d.fluidParticles, }; return findFluidJob.Schedule(); } protected JobHandle UpdateSimplexBounds(JobHandle inputDeps, float deltaTime) { var buildAabbs = new BuildSimplexAabbs { radii = principalRadii, fluidRadii = smoothingRadii, positions = positions, velocities = velocities, simplices = simplices, simplexCounts = simplexCounts, simplexBounds = simplexBounds, particleMaterialIndices = abstraction.collisionMaterials.AsNativeArray(), collisionMaterials = ObiColliderWorld.GetInstance().collisionMaterials.AsNativeArray(), collisionMargin = abstraction.parameters.collisionMargin, continuousCollisionDetection = abstraction.parameters.continuousCollisionDetection, dt = deltaTime, }; return buildAabbs.Schedule(simplexCounts.simplexCount, 32, inputDeps); } protected JobHandle GenerateContacts(JobHandle inputDeps, float deltaTime) { // Dispose of previous fluid interactions. // We need fluid data during interpolation, for anisotropic fluid particles. For this reason, // we can't dispose of these arrays in ResetForces() at the end of each full step. They must use persistent allocation. if (fluidInteractions.IsCreated) fluidInteractions.Dispose(); if (fluidBatchData.IsCreated) fluidBatchData.Dispose(); // get constraint parameters for constraint types that depend on broadphases: var collisionParameters = m_Solver.GetConstraintParameters(Oni.ConstraintType.Collision); var particleCollisionParameters = m_Solver.GetConstraintParameters(Oni.ConstraintType.ParticleCollision); var densityParameters = m_Solver.GetConstraintParameters(Oni.ConstraintType.Density); // if no enabled constraints that require broadphase info, skip it entirely. if (collisionParameters.enabled || particleCollisionParameters.enabled || densityParameters.enabled) { // update the bounding box of each simplex: inputDeps = UpdateSimplexBounds(inputDeps, deltaTime); // generate particle-particle and particle-collider interactions in parallel: JobHandle generateParticleInteractionsHandle = inputDeps, generateContactsHandle = inputDeps; // particle-particle interactions (contacts, fluids) if (particleCollisionParameters.enabled || densityParameters.enabled) { particleGrid.Update(this, deltaTime, inputDeps); generateParticleInteractionsHandle = particleGrid.GenerateContacts(this, deltaTime); } // particle-collider interactions (contacts) if (collisionParameters.enabled) { generateContactsHandle = colliderGrid.GenerateContacts(this, deltaTime, inputDeps); } JobHandle.CombineDependencies(generateParticleInteractionsHandle, generateContactsHandle).Complete(); // allocate arrays for interactions and batch data: particleContacts = new NativeArray(particleGrid.particleContactQueue.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); particleBatchData = new NativeArray(maxBatches, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); fluidInteractions = new NativeArray(particleGrid.fluidInteractionQueue.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); fluidBatchData = new NativeArray(maxBatches, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); colliderContacts = new NativeArray(colliderGrid.colliderContactQueue.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); // dequeue contacts/interactions into temporary arrays: var rawParticleContacts = new NativeArray(particleGrid.particleContactQueue.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); var sortedParticleContacts = new NativeArray(particleGrid.particleContactQueue.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); var rawFluidInteractions = new NativeArray(particleGrid.fluidInteractionQueue.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); DequeueIntoArrayJob dequeueParticleContacts = new DequeueIntoArrayJob() { InputQueue = particleGrid.particleContactQueue, OutputArray = rawParticleContacts }; DequeueIntoArrayJob dequeueFluidInteractions = new DequeueIntoArrayJob() { InputQueue = particleGrid.fluidInteractionQueue, OutputArray = rawFluidInteractions }; DequeueIntoArrayJob dequeueColliderContacts = new DequeueIntoArrayJob() { InputQueue = colliderGrid.colliderContactQueue, OutputArray = colliderContacts }; var dequeueHandle = JobHandle.CombineDependencies(dequeueParticleContacts.Schedule(), dequeueFluidInteractions.Schedule(), dequeueColliderContacts.Schedule()); // Sort contacts for jitter-free gauss-seidel (sequential) solving: dequeueHandle = contactSorter.SortConstraints(simplexCounts.simplexCount, rawParticleContacts, ref sortedParticleContacts, dequeueHandle); ContactProvider contactProvider = new ContactProvider() { contacts = sortedParticleContacts, sortedContacts = particleContacts, simplices = simplices, simplexCounts = simplexCounts }; FluidInteractionProvider fluidProvider = new FluidInteractionProvider() { interactions = rawFluidInteractions, sortedInteractions = fluidInteractions, }; // batch particle contacts: var activeParticleBatchCount = new NativeArray(1, Allocator.TempJob); var particleBatchHandle = collisionConstraintBatcher.BatchConstraints(ref contactProvider, particleCount, ref particleBatchData, ref activeParticleBatchCount, dequeueHandle); // batch fluid interactions: var activeFluidBatchCount = new NativeArray(1, Allocator.TempJob); var fluidBatchHandle = fluidConstraintBatcher.BatchConstraints(ref fluidProvider, particleCount, ref fluidBatchData, ref activeFluidBatchCount, dequeueHandle); JobHandle.CombineDependencies(particleBatchHandle, fluidBatchHandle).Complete(); // Generate particle contact/friction batches: var pc = constraints[(int)Oni.ConstraintType.ParticleCollision] as BurstParticleCollisionConstraints; var pf = constraints[(int)Oni.ConstraintType.ParticleFriction] as BurstParticleFrictionConstraints; for (int i = 0; i < pc.batches.Count; ++i) pc.batches[i].enabled = false; for (int i = 0; i < pf.batches.Count; ++i) pf.batches[i].enabled = false; for (int i = 0; i < activeParticleBatchCount[0]; ++i) { // create extra batches if not enough: if (i == pc.batches.Count) { pc.CreateConstraintsBatch(); pf.CreateConstraintsBatch(); } pc.batches[i].enabled = true; pf.batches[i].enabled = true; (pc.batches[i] as BurstParticleCollisionConstraintsBatch).batchData = particleBatchData[i]; (pf.batches[i] as BurstParticleFrictionConstraintsBatch ).batchData = particleBatchData[i]; } // Generate fluid interaction batches: var dc = constraints[(int)Oni.ConstraintType.Density] as BurstDensityConstraints; for (int i = 0; i < dc.batches.Count; ++i) dc.batches[i].enabled = false; for (int i = 0; i < activeFluidBatchCount[0]; ++i) { // create extra batches if not enough: if (i == dc.batches.Count) dc.CreateConstraintsBatch(); dc.batches[i].enabled = true; (dc.batches[i] as BurstDensityConstraintsBatch).batchData = fluidBatchData[i]; } // dispose of temporary buffers: rawParticleContacts.Dispose(); rawFluidInteractions.Dispose(); sortedParticleContacts.Dispose(); activeParticleBatchCount.Dispose(); activeFluidBatchCount.Dispose(); } return inputDeps; } public IObiJobHandle Substep(float stepTime, float substepTime, int substeps) { // Apply aerodynamics JobHandle aerodynamicsHandle = constraints[(int)Oni.ConstraintType.Aerodynamics].Project(new JobHandle(), stepTime, substepTime, substeps); // Predict positions: var predictPositions = new PredictPositionsJob() { activeParticles = activeParticles, phases = phases, buoyancies = buoyancies, externalForces = externalForces, inverseMasses = invMasses, positions = positions, previousPositions = prevPositions, velocities = velocities, externalTorques = externalTorques, inverseRotationalMasses = invRotationalMasses, orientations = orientations, previousOrientations = prevOrientations, angularVelocities = angularVelocities, gravity = new float4(m_Solver.parameters.gravity, 0), deltaTime = substepTime, is2D = abstraction.parameters.mode == Oni.SolverParameters.Mode.Mode2D }; JobHandle predictPositionsHandle = predictPositions.Schedule(activeParticles.Length, 128, aerodynamicsHandle); // Project position constraints: JobHandle projectionHandle = ApplyConstraints(predictPositionsHandle, stepTime, substepTime, substeps); // Update velocities: var updateVelocitiesJob = new UpdateVelocitiesJob() { activeParticles = activeParticles, inverseMasses = invMasses, previousPositions = prevPositions, positions = positions, velocities = velocities, inverseRotationalMasses = invRotationalMasses, previousOrientations = prevOrientations, orientations = orientations, angularVelocities = angularVelocities, deltaTime = substepTime, is2D = abstraction.parameters.mode == Oni.SolverParameters.Mode.Mode2D }; JobHandle updateVelocitiesHandle = updateVelocitiesJob.Schedule(activeParticles.Length, 128, projectionHandle); // velocity constraints: JobHandle velocityCorrectionsHandle = ApplyVelocityCorrections(updateVelocitiesHandle, substepTime); // Update positions: var updatePositionsJob = new UpdatePositionsJob() { activeParticles = activeParticles, positions = positions, previousPositions = prevPositions, velocities = velocities, orientations = orientations, previousOrientations = prevOrientations, angularVelocities = angularVelocities, velocityScale = math.pow(1 - math.clamp(m_Solver.parameters.damping, 0, 1), substepTime), sleepThreshold = m_Solver.parameters.sleepThreshold }; JobHandle updatePositionsHandle = updatePositionsJob.Schedule(activeParticles.Length, 128, velocityCorrectionsHandle); return jobHandlePool.Borrow().SetHandle(updatePositionsHandle); } private JobHandle ApplyVelocityCorrections(JobHandle inputDeps, float deltaTime) { var densityParameters = m_Solver.GetConstraintParameters(Oni.ConstraintType.Density); if (densityParameters.enabled) { var d = constraints[(int)Oni.ConstraintType.Density] as BurstDensityConstraints; if (d != null) { return d.ApplyVelocityCorrections(inputDeps, deltaTime); } } return inputDeps; } private JobHandle ApplyConstraints(JobHandle inputDeps, float stepTime, float substepTime, int substeps) { // calculate max amount of iterations required, and initialize constraints.. int maxIterations = 0; for (int i = 0; i < Oni.ConstraintTypeCount; ++i) { var parameters = m_Solver.GetConstraintParameters((Oni.ConstraintType)i); if (parameters.enabled) { maxIterations = math.max(maxIterations, parameters.iterations); inputDeps = constraints[i].Initialize(inputDeps, substepTime); } } // calculate iteration paddings: for (int i = 0; i < Oni.ConstraintTypeCount; ++i) { var parameters = m_Solver.GetConstraintParameters((Oni.ConstraintType)i); if (parameters.enabled && parameters.iterations > 0) padding[i] = (int)math.ceil(maxIterations / (float)parameters.iterations); else padding[i] = maxIterations; } // perform projection iterations: for (int i = 1; i < maxIterations; ++i) { for (int j = 0; j < Oni.ConstraintTypeCount; ++j) { if (j != (int)Oni.ConstraintType.Aerodynamics) { var parameters = m_Solver.GetConstraintParameters((Oni.ConstraintType)j); if (parameters.enabled && i % padding[j] == 0) inputDeps = constraints[j].Project(inputDeps, stepTime, substepTime, substeps); } } } // final iteration, all groups together: for (int i = 0; i < Oni.ConstraintTypeCount; ++i) { if (i != (int)Oni.ConstraintType.Aerodynamics) { var parameters = m_Solver.GetConstraintParameters((Oni.ConstraintType)i); if (parameters.enabled && parameters.iterations > 0) inputDeps = constraints[i].Project(inputDeps, stepTime, substepTime, substeps); } } // Despite friction constraints being applied after collision (since coulomb friction depends on normal impulse) // we perform a collision iteration right at the end to ensure the final state meets the Signorini-Fichera conditions. var param = m_Solver.GetConstraintParameters(Oni.ConstraintType.ParticleCollision); if (param.enabled && param.iterations > 0) inputDeps = constraints[(int)Oni.ConstraintType.ParticleCollision].Project(inputDeps, stepTime, substepTime, substeps); param = m_Solver.GetConstraintParameters(Oni.ConstraintType.Collision); if (param.enabled && param.iterations > 0) inputDeps = constraints[(int)Oni.ConstraintType.Collision].Project(inputDeps, stepTime, substepTime, substeps); return inputDeps; } public void ApplyInterpolation(ObiNativeVector4List startPositions, ObiNativeQuaternionList startOrientations, float stepTime, float unsimulatedTime) { // Interpolate particle positions and orientations. var interpolate = new InterpolationJob() { positions = positions, startPositions = startPositions.AsNativeArray(), renderablePositions = renderablePositions, orientations = orientations, startOrientations = startOrientations.AsNativeArray(), renderableOrientations = renderableOrientations, deltaTime = stepTime, unsimulatedTime = unsimulatedTime, interpolationMode = m_Solver.parameters.interpolation }; JobHandle jobHandle = interpolate.Schedule(m_Solver.positions.count, 128); // Update deformable triangle normals var updateNormals = new UpdateNormalsJob() { renderPositions = renderablePositions, deformableTriangles = deformableTriangles, normals = normals }; jobHandle = updateNormals.Schedule(jobHandle); // fluid laplacian/anisotropy: var d = constraints[(int)Oni.ConstraintType.Density] as BurstDensityConstraints; if (d != null) jobHandle = d.CalculateAnisotropyLaplacianSmoothing(jobHandle); // update axis: var updatePrincipalAxis = new UpdatePrincipalAxisJob() { activeParticles = activeParticles, renderableOrientations = renderableOrientations, phases = phases, principalRadii = principalRadii, principalAxis = anisotropies, }; jobHandle = updatePrincipalAxis.Schedule(activeParticles.Length, 128, jobHandle); jobHandle.Complete(); } public void InterpolateDiffuseProperties(ObiNativeVector4List properties, ObiNativeVector4List diffusePositions, ObiNativeVector4List diffuseProperties, ObiNativeIntList neighbourCount, int diffuseCount) { particleGrid.InterpolateDiffuseProperties(this, properties.AsNativeArray(), diffusePositions.AsNativeArray(), diffuseProperties.AsNativeArray(), neighbourCount.AsNativeArray(), diffuseCount).Complete(); } public void SpatialQuery(ObiNativeQueryShapeList shapes, ObiNativeAffineTransformList transforms, ObiNativeQueryResultList results) { var resultsQueue = new NativeQueue(Allocator.Persistent); particleGrid.SpatialQuery(this, shapes.AsNativeArray(), transforms.AsNativeArray(), resultsQueue).Complete(); int count = resultsQueue.Count; results.ResizeUninitialized(count); var dequeueQueryResults = new DequeueIntoArrayJob() { InputQueue = resultsQueue, OutputArray = results.AsNativeArray() }; var dequeueHandle = dequeueQueryResults.Schedule(); var distanceJob = new CalculateQueryDistances() { prevPositions = prevPositions, prevOrientations = prevOrientations, radii = principalRadii, simplices = simplices, simplexCounts = simplexCounts, queryResults = results.AsNativeArray() }; distanceJob.Schedule(count, 16, dequeueHandle).Complete(); resultsQueue.Dispose(); } public int GetParticleGridSize() { return particleGrid.grid.usedCells.Length; } public void GetParticleGrid(ObiNativeAabbList cells) { particleGrid.GetCells(cells); } } } #endif