GQ_Communicate/GQ_TongXin/Assets/Obi/Scripts/Solver/ObiSolver.cs

674 lines
20 KiB
C#

/**
\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.
- Simulation can be warm-started in the editor, then all simulation state gets serialized with the object. This means
your prefabs can be stored at any point in the simulation, and they will resume it when instantiated.
- Custom editor tools.
*/
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Runtime.InteropServices;
using System.Linq;
namespace Obi
{
/**
* An ObiSolver component simulates particles and their interactions using the Oni unified physics library.
* Several kinds of constraint types and their parameters are exposed, and several Obi components can
* be used to feed particles and constraints to the solver.
*/
[ExecuteInEditMode]
[AddComponentMenu("Physics/Obi/Obi Solver")]
[DisallowMultipleComponent]
public sealed class ObiSolver : MonoBehaviour
{
public enum SimulationOrder{
FixedUpdate,
AfterFixedUpdate,
LateUpdate
}
public class ObiCollisionEventArgs : EventArgs{
public Oni.Contact[] contacts; /**< collision contacts.*/
public ObiCollisionEventArgs(Oni.Contact[] contacts){
this.contacts = contacts;
}
}
public class ObiFluidEventArgs : EventArgs{
public int[] indices; /**< fluid particle indices.*/
public Vector4[] vorticities;
public float[] densities;
public ObiFluidEventArgs(int[] indices,
Vector4[] vorticities,
float[] densities){
this.indices = indices;
this.vorticities = vorticities;
this.densities = densities;
}
}
public class ParticleInActor{
public ObiActor actor;
public int indexInActor;
public ParticleInActor(ObiActor actor, int indexInActor){
this.actor = actor;
this.indexInActor = indexInActor;
}
}
public const int MAX_NEIGHBOURS = 92;
public const int CONSTRAINT_GROUPS = 12;
public event EventHandler OnFrameBegin;
public event EventHandler OnStepBegin;
public event EventHandler OnFixedParticlesUpdated;
public event EventHandler OnStepEnd;
public event EventHandler OnBeforePositionInterpolation;
public event EventHandler OnBeforeActorsFrameEnd;
public event EventHandler OnFrameEnd;
public event EventHandler<ObiCollisionEventArgs> OnCollision;
public event EventHandler<ObiFluidEventArgs> OnFluidUpdated;
public int maxParticles = 5000;
[HideInInspector] [NonSerialized] public bool simulate = true;
[Tooltip("If enabled, will force the solver to keep simulating even when not visible from any camera.")]
public bool simulateWhenInvisible = true; /**< Whether to keep simulating the cloth when its not visible by any camera.*/
[Tooltip("If enabled, the solver object transform will be used as the frame of reference for all actors using this solver, instead of the world's frame.")]
public bool simulateInLocalSpace = false;
[Tooltip("Determines when will the solver update particles.")]
public SimulationOrder simulationOrder = SimulationOrder.FixedUpdate;
public LayerMask collisionLayers = 1;
public Oni.SolverParameters parameters = new Oni.SolverParameters(Oni.SolverParameters.Interpolation.None,
new Vector4(0,-9.81f,0,0));
[HideInInspector] [NonSerialized] public List<ObiActor> actors = new List<ObiActor>();
private int allocatedParticleCount = 0;
[HideInInspector] [NonSerialized] public ParticleInActor[] particleToActor;
[HideInInspector] [NonSerialized] public int[] materialIndices;
[HideInInspector] [NonSerialized] public int[] fluidMaterialIndices;
private int[] activeParticles;
private List<ObiEmitterMaterial> emitterMaterials = new List<ObiEmitterMaterial>();
[HideInInspector] [NonSerialized] public Vector4[] renderablePositions; /**< renderable particle positions.*/
// constraint groups:
[HideInInspector] public int[] constraintsOrder;
// constraint parameters:
public Oni.ConstraintParameters distanceConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Sequential,3);
public Oni.ConstraintParameters bendingConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,3);
public Oni.ConstraintParameters particleCollisionConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,3);
public Oni.ConstraintParameters collisionConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,3);
public Oni.ConstraintParameters skinConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Sequential,3);
public Oni.ConstraintParameters volumeConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,3);
public Oni.ConstraintParameters tetherConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,3);
public Oni.ConstraintParameters pinConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,3);
public Oni.ConstraintParameters stitchConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,2);
public Oni.ConstraintParameters densityConstraintParameters = new Oni.ConstraintParameters(true,Oni.ConstraintParameters.EvaluationOrder.Parallel,2);
private IntPtr oniSolver;
private ObiEmitterMaterial defaultFluidMaterial;
private UnityEngine.Bounds bounds = new UnityEngine.Bounds();
private Matrix4x4 lastTransform;
private bool initialized = false;
private bool isVisible = true;
private float smoothDelta = 0.02f;
private int renderablePositionsClients = 0; /** counter for the amount of actors that need renderable positions.*/
public IntPtr OniSolver
{
get{return oniSolver;}
}
public UnityEngine.Bounds Bounds
{
get{return bounds;}
}
public Matrix4x4 LastTransform
{
get{return lastTransform;}
}
public bool IsVisible
{
get{return isVisible;}
}
public int AllocParticleCount{
get{return allocatedParticleCount;}
}
public bool IsUpdating{
get{return (initialized && simulate && (simulateWhenInvisible || IsVisible));}
}
public void RequireRenderablePositions(){
renderablePositionsClients++;
}
public void RelinquishRenderablePositions(){
if (renderablePositionsClients > 0)
renderablePositionsClients--;
}
void Awake(){
lastTransform = transform.localToWorldMatrix;
if (Application.isPlaying) //only during game.
Initialize();
}
void Start(){
if (Application.isPlaying){
ObiColliderBase[] colliders = FindObjectsOfType<ObiColliderBase>();
foreach (ObiColliderBase c in colliders){
c.RegisterInSolver(this,true);
}
}
}
void OnDestroy(){
if (Application.isPlaying){ //only during game.
Teardown();
ObiColliderBase[] colliders = FindObjectsOfType<ObiColliderBase>();
foreach (ObiColliderBase c in colliders){
c.RemoveFromSolver(this);
}
}
}
void OnEnable(){
if (!Application.isPlaying) //only in editor.
Initialize();
StartCoroutine("RunLateFixedUpdate");
ObiArbiter.RegisterSolver(this);
}
void OnDisable(){
if (!Application.isPlaying) //only in editor.
Teardown();
StopCoroutine("RunLateFixedUpdate");
ObiArbiter.UnregisterSolver(this);
}
public void Initialize(){
// Tear everything down first:
Teardown();
try{
// Create a default material:
defaultFluidMaterial = ScriptableObject.CreateInstance<ObiEmitterMaterialFluid>();
defaultFluidMaterial.hideFlags = HideFlags.HideAndDontSave;
// Create the Oni solver:
oniSolver = Oni.CreateSolver(maxParticles,MAX_NEIGHBOURS);
actors = new List<ObiActor>();
activeParticles = new int[maxParticles];
particleToActor = new ParticleInActor[maxParticles];
materialIndices = new int[maxParticles];
fluidMaterialIndices = new int[maxParticles];
renderablePositions = new Vector4[maxParticles];
// Initialize materials:
UpdateEmitterMaterials();
// Initialize parameters:
UpdateParameters();
}catch (Exception exception){
Debug.LogException(exception);
}finally{
initialized = true;
};
}
private void Teardown(){
if (!initialized) return;
try{
while (actors.Count > 0){
actors[actors.Count-1].RemoveFromSolver(null);
}
Oni.DestroySolver(oniSolver);
oniSolver = IntPtr.Zero;
GameObject.DestroyImmediate(defaultFluidMaterial);
}catch (Exception exception){
Debug.LogException(exception);
}finally{
initialized = false;
}
}
/**
* Adds the actor to this solver. Will return whether the allocation was sucessful or not.
*/
public bool AddActor(ObiActor actor, int numParticles){
if (particleToActor == null || actor == null)
return false;
int[] allocated = new int[numParticles];
int allocatedCount = 0;
for (int i = 0; i < maxParticles && allocatedCount < numParticles; i++){
if (particleToActor[i] == null){
allocated[allocatedCount] = i;
allocatedCount++;
}
}
// could not allocate enough particles.
if (allocatedCount < numParticles){
return false;
}
allocatedParticleCount += numParticles;
// store per-particle actor reference:
for (int i = 0; i < numParticles; ++i)
particleToActor[allocated[i]] = new ParticleInActor(actor,i);
// set the actor particle indices.
actor.particleIndices = allocated;
// Add the actor to the actor list:
actors.Add(actor);
// Update active particles. Update materials, in case the actor has a new one.
UpdateActiveParticles();
UpdateEmitterMaterials();
return true;
}
/**
* Removes an actor from this solver. Returns the index that was occupied by the actor in the actor list, or -1 if it was not managed by this solver.
*/
public int RemoveActor(ObiActor actor){
if (particleToActor == null || actor == null)
return -1;
// Find actor index in our actors array:
int index = actors.IndexOf(actor);
// If we are in charce of this actor indeed, perform all steps necessary to release it.
if (index > -1){
allocatedParticleCount -= actor.particleIndices.Length;
for (int i = 0; i < actor.particleIndices.Length; ++i)
particleToActor[actor.particleIndices[i]] = null;
actors.RemoveAt(index);
// Update active particles. Update materials, in case the actor had one.
UpdateActiveParticles();
UpdateEmitterMaterials();
}
return index;
}
/**
* Updates solver parameters, sending them to the Oni library.
*/
public void UpdateParameters(){
Oni.SetSolverParameters(oniSolver,ref parameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Distance,ref distanceConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Bending,ref bendingConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.ParticleCollision,ref particleCollisionConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Collision,ref collisionConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Density,ref densityConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Skin,ref skinConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Volume,ref volumeConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Tether,ref tetherConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Pin,ref pinConstraintParameters);
Oni.SetConstraintGroupParameters(oniSolver,(int)Oni.ConstraintType.Stitch,ref stitchConstraintParameters);
// Lazy initialization of constraints order.
if (constraintsOrder == null || constraintsOrder.Length != CONSTRAINT_GROUPS)
constraintsOrder = Enumerable.Range(0, CONSTRAINT_GROUPS).ToArray();
Oni.SetConstraintsOrder(oniSolver,constraintsOrder);
}
/**
* Updates the active particles array.
*/
public void UpdateActiveParticles(){
int numActive = 0;
for (int i = 0; i < actors.Count; ++i){
ObiActor currentActor = actors[i];
if (currentActor.isActiveAndEnabled){
for (int j = 0; j < currentActor.particleIndices.Length; ++j){
if (currentActor.active[j]){
activeParticles[numActive] = currentActor.particleIndices[j];
numActive++;
}
}
}
}
Oni.SetActiveParticles(oniSolver,activeParticles,numActive);
}
public void UpdateEmitterMaterials(){
// reset the emitter material list:
emitterMaterials = new List<ObiEmitterMaterial>(){defaultFluidMaterial};
// Setup all materials used by particle actors:
foreach (ObiActor actor in actors){
ObiEmitter em = actor as ObiEmitter;
if (em == null) continue;
int materialIndex = 0;
if (em.EmitterMaterial != null){
materialIndex = emitterMaterials.IndexOf(em.EmitterMaterial);
// if the material has not been considered before:
if (materialIndex < 0){
materialIndex = emitterMaterials.Count;
emitterMaterials.Add(em.EmitterMaterial);
//keep an eye on material changes:
em.EmitterMaterial.OnChangesMade += emitterMaterial_OnChangesMade;
}
}
// Update material index for all actor particles:
for(int i = 0; i < actor.particleIndices.Length; i++){
fluidMaterialIndices[actor.particleIndices[i]] = materialIndex;
}
}
Oni.SetFluidMaterialIndices(oniSolver,fluidMaterialIndices,fluidMaterialIndices.Length,0);
Oni.FluidMaterial[] mArray = emitterMaterials.ConvertAll<Oni.FluidMaterial>(a => a.GetEquivalentOniMaterial(parameters.mode)).ToArray();
Oni.SetFluidMaterials(oniSolver,mArray,mArray.Length,0);
}
private void emitterMaterial_OnChangesMade (object sender, ObiEmitterMaterial.MaterialChangeEventArgs e)
{
ObiEmitterMaterial material = sender as ObiEmitterMaterial;
int index = emitterMaterials.IndexOf(material);
if (index >= 0){
Oni.SetFluidMaterials(oniSolver,new Oni.FluidMaterial[]{material.GetEquivalentOniMaterial(parameters.mode)},1,index);
}
}
public void AccumulateSimulationTime(float dt){
Oni.AddSimulationTime(oniSolver,dt);
}
public void ResetSimulationTime(){
Oni.ResetSimulationTime(oniSolver);
}
public void SimulateStep(float stepTime){
Oni.ClearDiffuseParticles(oniSolver);
if (OnStepBegin != null)
OnStepBegin(this,null);
foreach(ObiActor actor in actors)
actor.OnSolverStepBegin();
// Update Oni skeletal mesh skinning after updating animators:
Oni.UpdateSkeletalAnimation(oniSolver);
// Trigger event right after actors have fixed their particles in OnSolverStepBegin.
if (OnFixedParticlesUpdated != null)
OnFixedParticlesUpdated(this,null);
ObiArbiter.FrameStart();
// Update the solver (this is internally split in tasks so multiple solvers can be updated in parallel)
Oni.UpdateSolver(oniSolver, stepTime);
// Wait here for all other solvers to finish, if we are the last solver to call this.
ObiArbiter.WaitForAllSolvers();
}
public void EndFrame(float frameDelta){
foreach(ObiActor actor in actors)
actor.OnSolverPreInterpolation();
if (OnBeforePositionInterpolation != null)
OnBeforePositionInterpolation(this,null);
Oni.ApplyPositionInterpolation(oniSolver, frameDelta);
// if we need to get renderable positions back from the solver:
if (renderablePositionsClients > 0)
{
Oni.GetRenderableParticlePositions(oniSolver, renderablePositions, renderablePositions.Length,0);
// convert positions to world space if they are expressed in solver space:
if (simulateInLocalSpace){
Matrix4x4 l2wTransform = transform.localToWorldMatrix;
for (int i = 0; i < renderablePositions.Length; ++i){
renderablePositions[i] = l2wTransform.MultiplyPoint3x4(renderablePositions[i]);
}
}
}
// Trigger fluid update:
TriggerFluidUpdateEvents();
if (OnBeforeActorsFrameEnd != null)
OnBeforeActorsFrameEnd(this,null);
CheckVisibility();
foreach(ObiActor actor in actors)
actor.OnSolverFrameEnd();
}
private void TriggerFluidUpdateEvents(){
int numFluidParticles = Oni.GetConstraintCount(oniSolver,(int)Oni.ConstraintType.Density);
if (numFluidParticles > 0 && OnFluidUpdated != null){
int[] indices = new int[numFluidParticles];
Vector4[] vorticities = new Vector4[maxParticles];
float[] densities = new float[maxParticles];
Oni.GetActiveConstraintIndices(oniSolver,indices,numFluidParticles,(int)Oni.ConstraintType.Density);
Oni.GetParticleVorticities(oniSolver,vorticities,maxParticles,0);
Oni.GetParticleDensities(oniSolver,densities,maxParticles,0);
OnFluidUpdated(this,new ObiFluidEventArgs(indices,vorticities,densities));
}
}
private void TriggerCollisionEvents(){
int numCollisions = Oni.GetConstraintCount(oniSolver,(int)Oni.ConstraintType.Collision);
if (OnCollision != null){
Oni.Contact[] contacts = new Oni.Contact[numCollisions];
if (numCollisions > 0)
{
Oni.GetCollisionContacts(oniSolver,contacts,numCollisions);
}
OnCollision(this,new ObiCollisionEventArgs(contacts));
}
}
private bool AreBoundsValid(Bounds bounds){
return !(float.IsNaN(bounds.center.x) || float.IsInfinity(bounds.center.x) ||
float.IsNaN(bounds.center.y) || float.IsInfinity(bounds.center.y) ||
float.IsNaN(bounds.center.z) || float.IsInfinity(bounds.center.z));
}
/**
* Checks if any particle in the solver is visible from at least one camera. If so, sets isVisible to true, false otherwise.
*/
private void CheckVisibility(){
Vector3 min = Vector3.zero, max = Vector3.zero;
Oni.GetBounds(oniSolver,ref min, ref max);
bounds.SetMinMax(min,max);
isVisible = false;
if (AreBoundsValid(bounds)){
Bounds wsBounds = simulateInLocalSpace ? bounds.Transform(transform.localToWorldMatrix) : bounds;
foreach (Camera cam in Camera.allCameras){
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(cam);
if (GeometryUtility.TestPlanesAABB(planes, wsBounds)){
isVisible = true;
return;
}
}
}
}
void Update(){
if (!Application.isPlaying)
return;
if (OnFrameBegin != null)
OnFrameBegin(this,null);
foreach(ObiActor actor in actors)
actor.OnSolverFrameBegin();
if (IsUpdating && simulationOrder != SimulationOrder.LateUpdate){
AccumulateSimulationTime(Time.deltaTime);
}
}
IEnumerator RunLateFixedUpdate() {
while (true) {
yield return new WaitForFixedUpdate();
if (Application.isPlaying && IsUpdating && simulationOrder == SimulationOrder.AfterFixedUpdate)
SimulateStep(Time.fixedDeltaTime);
}
}
void FixedUpdate()
{
if (Application.isPlaying && IsUpdating && simulationOrder == SimulationOrder.FixedUpdate)
SimulateStep(Time.fixedDeltaTime);
}
public void AllSolversStepEnd()
{
// Trigger solver events:
TriggerCollisionEvents();
foreach(ObiActor actor in actors)
actor.OnSolverStepEnd();
if (OnStepEnd != null)
OnStepEnd(this,null);
lastTransform = transform.localToWorldMatrix;
}
private void LateUpdate(){
if (Application.isPlaying && IsUpdating && simulationOrder == SimulationOrder.LateUpdate){
smoothDelta = Mathf.Lerp(Time.deltaTime,smoothDelta,0.95f);
AccumulateSimulationTime(smoothDelta);
SimulateStep(smoothDelta);
}
if (!Application.isPlaying)
return;
EndFrame (simulationOrder == SimulationOrder.LateUpdate ? smoothDelta : Time.fixedDeltaTime);
if (OnFrameEnd != null)
OnFrameEnd(this,null);
}
}
}