Files
2025-05-29 22:31:40 +03:00

1234 lines
47 KiB
C#

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Obi
{
/**
* Represents a group of related particles. ObiActor does not make
* any assumptions about the relationship between these particles, except that they get allocated
* and released together.
*/
[ExecuteInEditMode]
[DisallowMultipleComponent]
public abstract class ObiActor : MonoBehaviour, IObiParticleCollection
{
public class ObiActorSolverArgs : System.EventArgs
{
public ObiSolver solver { get; }
public ObiActorSolverArgs(ObiSolver solver)
{
this.solver = solver;
}
}
public delegate void ActorCallback(ObiActor actor);
public delegate void ActorStepCallback(ObiActor actor,float stepTime);
public delegate void ActorBlueprintCallback(ObiActor actor,ObiActorBlueprint blueprint);
/// <summary>
/// Called when the actor blueprint has been loaded into the solver.
/// </summary>
public event ActorBlueprintCallback OnBlueprintLoaded;
/// <summary>
/// Called when the actor blueprint has been unloaded from the solver.
/// </summary>
public event ActorBlueprintCallback OnBlueprintUnloaded;
/// <summary>
/// Called at the start of the solver's FixedUpdate (for Fixed and LateFixed updaters) or the solver's Update (for Late updaters)
/// </summary>
public event ActorCallback OnPrepareFrame;
/// <summary>
/// Called at the beginning of a time step, before dirty constraints and active particles have been updated.
/// </summary>
public event ActorStepCallback OnPrepareStep;
/// <summary>
/// Called at the beginning of a time step, after dirty constraints and active particles have been updated.
/// </summary>
public event ActorStepCallback OnBeginStep;
/// <summary>
/// Called at the beginning of each substep.
/// </summary>
public event ActorStepCallback OnSubstep;
/// <summary>
/// Called at the end of a timestep, after external forces have been reset and collision callbacks called.
/// </summary>
public event ActorStepCallback OnEndStep;
/// <summary>
/// Called at the end of each frame.
/// </summary>
public event ActorCallback OnInterpolate;
[HideInInspector] protected int m_ActiveParticleCount = 0;
/// <summary>
/// Index of each one of the actor's particles in the solver.
/// </summary>
[HideInInspector] public int[] solverIndices;
/// <summary>
/// For each of the actor's constraint types, offset of every batch in the solver.
/// </summary>
[HideInInspector] public List<int>[] solverBatchOffsets;
protected ObiSolver m_Solver;
protected bool m_Loaded = false;
private ObiActorBlueprint state;
private ObiActorBlueprint m_BlueprintInstance;
private ObiPinConstraintsData m_PinConstraints;
[SerializeField][HideInInspector] protected ObiCollisionMaterial m_CollisionMaterial;
[SerializeField][HideInInspector] protected bool m_SurfaceCollisions = false;
/// <summary>
/// The solver in charge of simulating this actor.
/// </summary>
/// This is the first ObiSlver component found up the actor's hierarchy.
public ObiSolver solver
{
get { return m_Solver; }
}
/// <summary>
/// True if the actor blueprint has been loaded into a solver.
/// If true, it guarantees actor.solver, actor.solverIndices and actor.solverBatchOffsets won't be null.
/// </summary>
public bool isLoaded
{
get { return m_Loaded; }
}
/// <summary>
/// The collision material being used by this actor.
/// </summary>
public ObiCollisionMaterial collisionMaterial
{
get
{
return m_CollisionMaterial;
}
set
{
if (m_CollisionMaterial != value)
{
m_CollisionMaterial = value;
UpdateCollisionMaterials();
}
}
}
/// <summary>
/// Whether to use simplices (triangles, edges) for contact generation.
/// </summary>
public virtual bool surfaceCollisions
{
get
{
return m_SurfaceCollisions;
}
set
{
if (value != m_SurfaceCollisions)
{
m_SurfaceCollisions = value;
if (m_Solver != null)
m_Solver.dirtySimplices = true;
}
}
}
/// <summary>
/// Amount of particles allocated by this actor.
/// </summary>
public int particleCount
{
get
{
return sourceBlueprint != null ? sourceBlueprint.particleCount : 0;
}
}
/// <summary>
/// Amount of particles in use by this actor.
/// </summary>
/// This will always be equal to or smaller than <see cref="particleCount"/>.
public int activeParticleCount
{
get
{
return m_ActiveParticleCount;
}
}
/// <summary>
/// Whether this actors makes use of particle orientations or not.
/// </summary>
public bool usesOrientedParticles
{
get
{
return sourceBlueprint != null &&
sourceBlueprint.invRotationalMasses != null && sourceBlueprint.invRotationalMasses.Length > 0 &&
sourceBlueprint.orientations != null && sourceBlueprint.orientations.Length > 0 &&
sourceBlueprint.restOrientations != null && sourceBlueprint.restOrientations.Length > 0;
}
}
/// <summary>
/// If true, it means particles may not be completely spherical, but ellipsoidal.
/// </summary>
public virtual bool usesAnisotropicParticles
{
get
{
return false;
}
}
/// <summary>
/// If true, it means external forces aren't applied to the particles directly.
/// </summary>
/// For instance, cloth uses aerodynamic constraints to do so, and fluid uses drag.
public virtual bool usesCustomExternalForces
{
get { return false; }
}
/// <summary>
/// Matrix that transforms from the actor's local space to the solver's local space.
/// </summary>
/// If there's no solver present, this is the same as the actor's local to world matrix.
public Matrix4x4 actorLocalToSolverMatrix
{
get
{
if (m_Solver != null)
return m_Solver.transform.worldToLocalMatrix * transform.localToWorldMatrix;
else
return transform.localToWorldMatrix;
}
}
/// <summary>
/// Matrix that transforms from the solver's local space to the actor's local space.
/// </summary>
/// If there's no solver present, this is the same as the actor's world to local matrix.
/// This is always the same as the inverse of <see cref="actorLocalToSolverMatrix"/>.
public Matrix4x4 actorSolverToLocalMatrix
{
get
{
if (m_Solver != null)
return transform.worldToLocalMatrix * m_Solver.transform.localToWorldMatrix;
else
return transform.worldToLocalMatrix;
}
}
/// <summary>
/// Reference to the blueprint asset used by this actor.
/// </summary>
public abstract ObiActorBlueprint sourceBlueprint
{
get;
}
/// <summary>
/// Reference to the blueprint in use by this actor.
/// </summary>
/// If you haven't called <see cref="blueprint"/> before, this will be the same as <see cref="sourceBlueprint"/>.
/// If you have called <see cref="blueprint"/> before, it will be the same as <see cref="blueprint"/>.
public ObiActorBlueprint sharedBlueprint
{
get
{
if (m_BlueprintInstance != null)
return m_BlueprintInstance;
return sourceBlueprint;
}
}
/// <summary>
/// Returns a unique instance of this actor's <see cref="sourceBlueprint"/>.
/// </summary>
/// This is mostly used when the actor needs to change some blueprint data at runtime,
/// and you don't want to change the blueprint asset as this would affect all other actors using it. Tearable cloth and ropes
/// make use of this.
public ObiActorBlueprint blueprint
{
get
{
if (m_BlueprintInstance == null && sourceBlueprint != null)
m_BlueprintInstance = Instantiate(sourceBlueprint);
return m_BlueprintInstance;
}
}
protected virtual void Awake()
{
#if UNITY_EDITOR
// Check if this script's GameObject is in a PrefabStage
#if UNITY_2021_2_OR_NEWER
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(gameObject);
#else
var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(gameObject);
#endif
if (prefabStage != null)
{
// Only create a solver if there's not one up our hierarchy.
if (GetComponentInParent<ObiSolver>() == null)
{
// Add our own environment root and move it to the PrefabStage scene
var newParent = new GameObject("ObiSolver (Environment)", typeof(ObiSolver), typeof(ObiLateFixedUpdater));
newParent.GetComponent<ObiLateFixedUpdater>().solvers.Add(newParent.GetComponent<ObiSolver>());
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(newParent, gameObject.scene);
transform.root.parent = newParent.transform;
}
}
#endif
}
protected virtual void OnDestroy()
{
if (m_BlueprintInstance != null)
DestroyImmediate(m_BlueprintInstance);
}
protected virtual void OnEnable()
{
solverBatchOffsets = new List<int>[Oni.ConstraintTypeCount];
for (int i = 0; i < solverBatchOffsets.Length; ++i)
solverBatchOffsets[i] = new List<int>();
m_PinConstraints = new ObiPinConstraintsData();
// when an actor is enabled, grabs the first solver up its hierarchy,
// initializes it (if not initialized) and gets added to it.
m_Solver = GetComponentInParent<ObiSolver>();
AddToSolver();
}
protected virtual void OnDisable()
{
RemoveFromSolver();
}
protected virtual void OnValidate()
{
}
private void OnTransformParentChanged()
{
if (isActiveAndEnabled)
SetSolver(GetComponentInParent<ObiSolver>());
}
/// <summary>
/// Adds this actor to its solver, if any. Automatically called by <see cref="ObiSolver"/>.
/// </summary>
public void AddToSolver()
{
if (m_Solver != null)
{
if (!m_Solver.AddActor(this))
m_Solver = null;
else if (sourceBlueprint != null)
sourceBlueprint.OnBlueprintGenerate += OnBlueprintRegenerate;
}
}
/// <summary>
/// Remove this actor from its solver, if any. Automatically called by <see cref="ObiSolver"/>.
/// </summary>
public void RemoveFromSolver()
{
if (m_Solver != null)
{
m_Solver.RemoveActor(this);
if (sourceBlueprint != null)
sourceBlueprint.OnBlueprintGenerate -= OnBlueprintRegenerate;
}
}
/// <summary>
/// Forcibly changed the solver in charge of this actor
/// </summary>
/// <param name="newSolver"> The solver we want to put in charge of this actor.</param>
/// First it removes the actor from its current solver, then changes the actor's current solver and then readds it to this new solver.
protected void SetSolver(ObiSolver newSolver)
{
// In case the first solver up our hierarchy is not the one we're currently in, change solver.
if (newSolver != m_Solver)
{
RemoveFromSolver();
m_Solver = newSolver;
AddToSolver();
}
}
protected virtual void OnBlueprintRegenerate(ObiActorBlueprint blueprint)
{
// Reload:
RemoveFromSolver();
AddToSolver();
}
protected void UpdateCollisionMaterials()
{
if (m_Solver != null && solverIndices != null)
{
int index = m_CollisionMaterial != null ? m_CollisionMaterial.handle.index : -1;
for (int i = 0; i < solverIndices.Length; i++)
solver.collisionMaterials[solverIndices[i]] = index;
}
}
/// <summary>
/// Copies all data (position, velocity, phase, etc) from one particle to another one.
/// </summary>
/// <param name="actorSourceIndex"> Index in the actor arrays of the particle we will copy data from.</param>
/// <param name="actorDestIndex"> Index in the actor arrays of the particle we will copy data to.</param>
/// <returns>
/// Whether the indices passed are within actor bounds.
/// </returns>
/// Extend this method to implement copying your own custom particle data in custom actors.
public virtual bool CopyParticle(int actorSourceIndex, int actorDestIndex)
{
if (!isLoaded ||
actorSourceIndex < 0 || actorSourceIndex >= solverIndices.Length ||
actorDestIndex < 0 || actorDestIndex >= solverIndices.Length)
return false;
int sourceIndex = solverIndices[actorSourceIndex];
int destIndex = solverIndices[actorDestIndex];
// Copy solver data:
m_Solver.prevPositions[destIndex] = m_Solver.prevPositions[sourceIndex];
m_Solver.renderablePositions[destIndex] = m_Solver.renderablePositions[sourceIndex];
m_Solver.startPositions[destIndex] = m_Solver.positions[destIndex] = m_Solver.positions[sourceIndex];
m_Solver.startOrientations[destIndex] = m_Solver.orientations[destIndex] = m_Solver.orientations[sourceIndex];
m_Solver.restPositions[destIndex] = m_Solver.restPositions[sourceIndex];
m_Solver.restOrientations[destIndex] = m_Solver.restOrientations[sourceIndex];
m_Solver.velocities[destIndex] = m_Solver.velocities[sourceIndex];
m_Solver.angularVelocities[destIndex] = m_Solver.velocities[sourceIndex];
m_Solver.invMasses[destIndex] = m_Solver.invMasses[sourceIndex];
m_Solver.invRotationalMasses[destIndex] = m_Solver.invRotationalMasses[sourceIndex];
m_Solver.principalRadii[destIndex] = m_Solver.principalRadii[sourceIndex];
m_Solver.phases[destIndex] = m_Solver.phases[sourceIndex];
m_Solver.filters[destIndex] = m_Solver.filters[sourceIndex];
m_Solver.colors[destIndex] = m_Solver.colors[sourceIndex];
return true;
}
/// <summary>
/// Teleports one actor particle to a certain position in solver space.
/// </summary>
/// <param name="actorIndex"> Index in the actor arrays of the particle we will teeleport.</param>
/// <param name="position"> Position to teleport the particle to, expressed in solver space.</param>
public void TeleportParticle(int actorIndex, Vector3 position)
{
if (!isLoaded || actorIndex < 0 || actorIndex >= solverIndices.Length)
return;
int solverIndex = solverIndices[actorIndex];
Vector4 delta = (Vector4)position - m_Solver.positions[solverIndex];
m_Solver.positions[solverIndex] += delta;
m_Solver.prevPositions[solverIndex] += delta;
m_Solver.renderablePositions[solverIndex] += delta;
m_Solver.startPositions[solverIndex] += delta;
}
/// <summary>
/// Teleports the entire actor to a new location / orientation.
/// </summary>
/// <param name="position"> World space position to teleport the actor to.</param>
/// <param name="rotation"> World space rotation to teleport the actor to.</param>
public virtual void Teleport(Vector3 position, Quaternion rotation)
{
if (!isLoaded)
return;
// Subtract current transform position/rotation, then add new world space position/rotation.
// Lastly, set the transform to the new position/rotation.
Matrix4x4 offset = solver.transform.worldToLocalMatrix *
Matrix4x4.TRS(position, Quaternion.identity, Vector3.one) *
Matrix4x4.TRS(Vector3.zero, rotation, Vector3.one) *
Matrix4x4.TRS(Vector3.zero, Quaternion.Inverse(transform.rotation), Vector3.one) *
Matrix4x4.TRS(-transform.position, Quaternion.identity, Vector3.one) *
solver.transform.localToWorldMatrix;
Quaternion rotOffset = offset.rotation;
for (int i = 0; i < solverIndices.Length; i++)
{
int solverIndex = solverIndices[i];
m_Solver.positions[solverIndex] =
m_Solver.prevPositions[solverIndex] =
m_Solver.renderablePositions[solverIndex] =
m_Solver.startPositions[solverIndex] = offset.MultiplyPoint3x4(m_Solver.positions[solverIndex]);
m_Solver.orientations[solverIndex] =
m_Solver.prevOrientations[solverIndex] =
m_Solver.renderableOrientations[solverIndex] =
m_Solver.startOrientations[solverIndex] = rotOffset * m_Solver.orientations[solverIndex];
m_Solver.velocities[solverIndex] = Vector4.zero;
m_Solver.angularVelocities[solverIndex] = Vector4.zero;
}
transform.position = position;
transform.rotation = rotation;
}
protected virtual void SwapWithFirstInactiveParticle(int actorIndex)
{
// update solver indices:
m_Solver.particleToActor[solverIndices[actorIndex]].indexInActor = m_ActiveParticleCount;
m_Solver.particleToActor[solverIndices[m_ActiveParticleCount]].indexInActor = actorIndex;
solverIndices.Swap(actorIndex, m_ActiveParticleCount);
}
/// <summary>
/// Activates one particle.
/// </summary>
/// <param name="actorIndex"> Index in the actor arrays of the particle we will activate.</param>
/// <returns>
/// True if the particle was inactive. False if the particle was already active.
/// </returns>
/// This operation preserves the relative order of all particles.
public bool ActivateParticle(int actorIndex)
{
if (IsParticleActive(actorIndex))
return false;
SwapWithFirstInactiveParticle(actorIndex);
m_ActiveParticleCount++;
m_Solver.dirtyActiveParticles = true;
return true;
}
/// <summary>
/// Deactivates one particle.
/// </summary>
/// <param name="actorIndex"> Index in the actor arrays of the particle we will deactivate.</param>
/// <returns>
/// True if the particle was active. False if the particle was already inactive.
/// </returns>
/// This operation does not preserve the relative order of other particles, because the last active particle will
/// swap positions with the particle being deactivated.
public bool DeactivateParticle(int actorIndex)
{
if (!IsParticleActive(actorIndex))
return false;
m_ActiveParticleCount--;
SwapWithFirstInactiveParticle(actorIndex);
m_Solver.dirtyActiveParticles = true;
return true;
}
/// <summary>
/// Returns whether a given particle is active.
/// </summary>
/// <param name="actorIndex"> Index in the actor arrays of the particle.</param>
/// <returns>
/// True if the particle is active. False if the particle is inactive.
/// </returns>
public bool IsParticleActive(int actorIndex)
{
return actorIndex < m_ActiveParticleCount;
}
/// <summary>
/// Updates particle phases in the solver at runtime, including or removing the self-collision flag.
/// </summary>
public virtual void SetSelfCollisions(bool selfCollisions)
{
if (m_Solver != null && Application.isPlaying && isLoaded)
{
for (int i = 0; i < particleCount; i++)
{
if (selfCollisions)
m_Solver.phases[solverIndices[i]] |= (int)ObiUtils.ParticleFlags.SelfCollide;
else
m_Solver.phases[solverIndices[i]] &= ~(int)ObiUtils.ParticleFlags.SelfCollide;
}
}
}
/// <summary>
/// Updates particle phases in the solver at runtime, including or removing the one-sided flag.
/// </summary>
public virtual void SetOneSided(bool oneSided)
{
if (m_Solver != null && Application.isPlaying && isLoaded)
{
for (int i = 0; i < particleCount; i++)
{
if (oneSided)
m_Solver.phases[solverIndices[i]] |= (int)ObiUtils.ParticleFlags.OneSided;
else
m_Solver.phases[solverIndices[i]] &= ~(int)ObiUtils.ParticleFlags.OneSided;
}
}
}
/// <summary>
/// Marks simplices dirty.
/// </summary>
public void SetSimplicesDirty()
{
if (m_Solver != null)
m_Solver.dirtySimplices = true;
}
/// <summary>
/// Marks a given constraint type as dirty.
/// </summary>
/// <param name="constraintType"> Type of the constraints that need re-creation.</param>
/// This will cause the solver to perform a constraint re-creation at the start of the next step. Needed when the constraint data in an actor changes at runtime,
/// as a result of changing topology (torn cloth or ropes), or changes in internal constraint parameters such as compliance values. This is a relatively expensive operation,
/// so it's best to amortize as many constraint modification operations as possible in a single step.
public void SetConstraintsDirty(Oni.ConstraintType constraintType)
{
if (m_Solver != null)
m_Solver.dirtyConstraints |= (1 << (int)constraintType);
}
/// <summary>
/// Returns the data representation of constraints of a given type being simulated by this solver.
/// </summary>
/// <param name="type"> Type of the constraints that will be returned by this method.</param>
/// <returns>
/// The runtime constraints of the type speficied. Most constraints are stored in the blueprint, with a couple notable exceptions: pin and stitch constraints
/// are always created at runtime, so they're not stored in the blueprint but in the actor itself.
/// </returns>
public IObiConstraints GetConstraintsByType(Oni.ConstraintType type)
{
// pin constraints are a special case, because they're not stored in a blueprint. They are created at runtime at stored in the actor itself.
if (type == Oni.ConstraintType.Pin)
return m_PinConstraints;
if (sharedBlueprint != null)
return sharedBlueprint.GetConstraintsByType(type);
return null;
}
/// <summary>
/// Call when some particle properties have been modified and need updating.
/// </summary>
/// Does not do anything by default. Call when manually modifying particle properties in the solver, should the actor need to do some book keeping.
/// For softbodies, updates their rest state.
public virtual void UpdateParticleProperties()
{
}
/// <summary>
/// Returns the index of this particle in the solver arrays.
/// </summary>
/// <param name="actorIndex"> Index of the particle in the actor arrays.</param>
/// <returns>
/// The index of a given particle in the solver arrays.
/// </returns>
/// At runtime when the blueprint is loaded, this is the same as calling actor.solverIndices[solverIndex].
/// If the blueprint is not loaded it will return the same index passed to it: actorIndex.
/// Note that this function does not perform any range checking.
public int GetParticleRuntimeIndex(int actorIndex)
{
if (isLoaded)
return solverIndices[actorIndex];
return actorIndex;
}
/// <summary>
/// Given a solver particle index, returns the position of that particle in world space.
/// </summary>
/// <param name="solverIndex"> Index of the particle in the solver arrays.</param>
/// <returns>
/// The position of a given particle in world space.
/// </returns>
public Vector3 GetParticlePosition(int solverIndex)
{
if (isLoaded)
return m_Solver.transform.TransformPoint(m_Solver.renderablePositions[solverIndex]);
return Vector3.zero;
}
/// <summary>
/// Given a solver particle index, returns the orientation of that particle in world space.
/// </summary>
/// <param name="solverIndex"> Index of the particle in the solver arrays.</param>
/// <returns>
/// The orientation of a given particle in world space.
/// </returns>
public Quaternion GetParticleOrientation(int solverIndex)
{
if (isLoaded)
return m_Solver.transform.rotation * m_Solver.renderableOrientations[solverIndex];
return Quaternion.identity;
}
/**
*
*/
/// <summary>
/// Given a solver particle index, returns the anisotropic frame of that particle in world space.
/// </summary>
/// <param name="solverIndex"> Index of the particle in the solver arrays.</param>
/// <param name="solverIndex"> First basis vector of the frame. Contains particle radius along this axis in the 4th position.</param>
/// <param name="solverIndex"> Second basis vector of the frame. Contains particle radius along this axis in the 4th position..</param>
/// <param name="solverIndex"> Third basis vector of the frame. Contains particle radius along this axis in the 4th position.</param>
public void GetParticleAnisotropy(int solverIndex, ref Vector4 b1, ref Vector4 b2, ref Vector4 b3)
{
if (isLoaded && usesAnisotropicParticles)
{
int baseIndex = solverIndex * 3;
b1 = m_Solver.transform.TransformDirection(m_Solver.anisotropies[baseIndex]);
b2 = m_Solver.transform.TransformDirection(m_Solver.anisotropies[baseIndex + 1]);
b3 = m_Solver.transform.TransformDirection(m_Solver.anisotropies[baseIndex + 2]);
b1[3] = m_Solver.maxScale * m_Solver.anisotropies[baseIndex][3];
b2[3] = m_Solver.maxScale * m_Solver.anisotropies[baseIndex + 1][3];
b3[3] = m_Solver.maxScale * m_Solver.anisotropies[baseIndex + 2][3];
}
else
{
b1[3] = b2[3] = b3[3] = m_Solver.maxScale * m_Solver.principalRadii[solverIndex][0];
}
}
/// <summary>
/// Given a solver particle index, returns the maximum world space radius of that particle, in any axis.
/// </summary>
/// <param name="solverIndex"> Index of the particle in the solver arrays.</param>
/// <returns>
/// The maximum radius of a given particle in world space.
/// </returns>
public float GetParticleMaxRadius(int solverIndex)
{
if (isLoaded)
return m_Solver.maxScale * m_Solver.principalRadii[solverIndex][0];
return 0;
}
/// <summary>
/// Given a solver particle index, returns the color of that particle.
/// </summary>
/// <param name="solverIndex"> Index of the particle in the solver arrays.</param>
/// <returns>
/// The color of the particle.
/// </returns>
public Color GetParticleColor(int solverIndex)
{
if (isLoaded)
return m_Solver.colors[solverIndex];
return Color.white;
}
/// <summary>
/// Sets a given category value for all particles in the actor.
/// </summary>
/// <param name="newCategory"> Category value.</param>
public void SetFilterCategory(int newCategory)
{
newCategory = Mathf.Clamp(newCategory, ObiUtils.MinCategory, ObiUtils.MaxCategory);
for (int i = 0; i < particleCount; ++i)
{
int solverIndex = solverIndices[i];
var mask = ObiUtils.GetMaskFromFilter(solver.filters[solverIndex]);
solver.filters[solverIndex] = ObiUtils.MakeFilter(mask, newCategory);
}
}
/// <summary>
/// Sets a given mask value for all particles in the actor.
/// </summary>
/// <param name="newMask"> Mask value.</param>
public void SetFilterMask(int newMask)
{
newMask = Mathf.Clamp(newMask, ObiUtils.CollideWithNothing, ObiUtils.CollideWithEverything);
for (int i = 0; i < particleCount; ++i)
{
int solverIndex = solverIndices[i];
var category = ObiUtils.GetCategoryFromFilter(solver.filters[solverIndex]);
solver.filters[solverIndex] = ObiUtils.MakeFilter(newMask, category);
}
}
/// <summary>
/// Sets the inverse mass of each particle so that the total actor mass matches the one passed by parameter.
/// </summary>
/// <param name="mass"> The actor's total mass.</param>
public void SetMass(float mass)
{
if (Application.isPlaying && isLoaded && activeParticleCount > 0)
{
float invMass = 1.0f / (mass / activeParticleCount);
for (int i = 0; i < activeParticleCount; ++i)
{
int solverIndex = solverIndices[i];
m_Solver.invMasses[solverIndex] = invMass;
m_Solver.invRotationalMasses[solverIndex] = invMass;
}
}
}
/// <summary>
/// Returns the actor's mass (sum of all particle masses), and the position of its center of mass.
/// </summary>
/// <param name="com"> The actor's center of mass, expressed in solver space.</param>
/// Particles with infinite mass (invMass = 0) are ignored.
public float GetMass(out Vector3 com)
{
float actorMass = 0;
com = Vector3.zero;
if (Application.isPlaying && isLoaded && activeParticleCount > 0)
{
Vector4 com4 = Vector4.zero;
for (int i = 0; i < activeParticleCount; ++i)
{
if (m_Solver.invMasses[solverIndices[i]] > 0)
{
float mass = 1.0f / m_Solver.invMasses[solverIndices[i]];
actorMass += mass;
com4 += m_Solver.positions[solverIndices[i]] * mass;
}
}
com = com4;
if (actorMass > float.Epsilon)
com /= actorMass;
}
return actorMass;
}
/// <summary>
///Adds an external force to all particles in the actor.
/// </summary>
/// <param name="force"> Value expressed in solver space.</param>
/// <param name="forceMode"> Type of "force" applied.</param>
public void AddForce(Vector3 force, ForceMode forceMode)
{
Vector3 com;
float mass = GetMass(out com);
if (!float.IsInfinity(mass))
{
Vector4 bodyForce = force;
switch (forceMode)
{
case ForceMode.Force:
{
bodyForce /= mass;
for (int i = 0; i < solverIndices.Length; ++i)
m_Solver.externalForces[solverIndices[i]] += bodyForce / m_Solver.invMasses[solverIndices[i]];
}
break;
case ForceMode.Acceleration:
{
for (int i = 0; i < solverIndices.Length; ++i)
m_Solver.externalForces[solverIndices[i]] += bodyForce / m_Solver.invMasses[solverIndices[i]];
}
break;
case ForceMode.Impulse:
{
bodyForce /= mass;
for (int i = 0; i < solverIndices.Length; ++i)
m_Solver.externalForces[solverIndices[i]] += bodyForce / m_Solver.invMasses[solverIndices[i]] / Time.fixedDeltaTime;
}
break;
case ForceMode.VelocityChange:
{
for (int i = 0; i < solverIndices.Length; ++i)
m_Solver.externalForces[solverIndices[i]] += bodyForce / m_Solver.invMasses[solverIndices[i]] / Time.fixedDeltaTime;
}
break;
}
}
}
/// <summary>
/// Adds a torque to the actor.
/// </summary>
/// <param name="force"> Value expressed in solver space.</param>
/// <param name="forceMode"> Type of "torque" applied.</param>
public void AddTorque(Vector3 force, ForceMode forceMode)
{
Vector3 com;
float mass = GetMass(out com);
if (!float.IsInfinity(mass))
{
Vector3 bodyForce = force;
switch (forceMode)
{
case ForceMode.Force:
{
bodyForce /= mass;
for (int i = 0; i < solverIndices.Length; ++i)
{
Vector3 v = Vector3.Cross(bodyForce / m_Solver.invMasses[solverIndices[i]], (Vector3)m_Solver.positions[solverIndices[i]] - com);
m_Solver.externalForces[solverIndices[i]] += new Vector4(v.x, v.y, v.z, 0);
}
}
break;
case ForceMode.Acceleration:
{
for (int i = 0; i < solverIndices.Length; ++i)
{
Vector3 v = Vector3.Cross(bodyForce / m_Solver.invMasses[solverIndices[i]], (Vector3)m_Solver.positions[solverIndices[i]] - com);
m_Solver.externalForces[solverIndices[i]] += new Vector4(v.x, v.y, v.z, 0);
}
}
break;
case ForceMode.Impulse:
{
bodyForce /= mass;
for (int i = 0; i < solverIndices.Length; ++i)
{
Vector3 v = Vector3.Cross(bodyForce / m_Solver.invMasses[solverIndices[i]] / Time.fixedDeltaTime, (Vector3)m_Solver.positions[solverIndices[i]] - com);
m_Solver.externalForces[solverIndices[i]] += new Vector4(v.x, v.y, v.z, 0);
}
}
break;
case ForceMode.VelocityChange:
{
for (int i = 0; i < solverIndices.Length; ++i)
{
Vector3 v = Vector3.Cross(bodyForce / m_Solver.invMasses[solverIndices[i]] / Time.fixedDeltaTime, (Vector3)m_Solver.positions[solverIndices[i]] - com);
m_Solver.externalForces[solverIndices[i]] += new Vector4(v.x, v.y, v.z, 0);
}
}
break;
}
}
}
#region Blueprints
private void LoadBlueprintParticles(ObiActorBlueprint bp, int groupID)
{
Matrix4x4 l2sTransform = actorLocalToSolverMatrix;
Quaternion l2sRotation = l2sTransform.rotation;
for (int i = 0; i < solverIndices.Length; i++)
{
int k = solverIndices[i];
if (bp.positions != null && i < bp.positions.Length)
{
m_Solver.startPositions[k] = m_Solver.prevPositions[k] = m_Solver.positions[k] = l2sTransform.MultiplyPoint3x4(bp.positions[i]);
m_Solver.renderablePositions[k] = l2sTransform.MultiplyPoint3x4(bp.positions[i]);
}
if (bp.orientations != null && i < bp.orientations.Length)
{
m_Solver.startOrientations[k] = m_Solver.prevOrientations[k] = m_Solver.orientations[k] = l2sRotation * bp.orientations[i];
m_Solver.renderableOrientations[k] = l2sRotation * bp.orientations[i];
}
if (bp.restPositions != null && i < bp.restPositions.Length)
m_Solver.restPositions[k] = bp.restPositions[i];
if (bp.restOrientations != null && i < bp.restOrientations.Length)
m_Solver.restOrientations[k] = bp.restOrientations[i];
if (bp.velocities != null && i < bp.velocities.Length)
m_Solver.velocities[k] = l2sTransform.MultiplyVector(bp.velocities[i]);
if (bp.angularVelocities != null && i < bp.angularVelocities.Length)
m_Solver.angularVelocities[k] = l2sTransform.MultiplyVector(bp.angularVelocities[i]);
if (bp.invMasses != null && i < bp.invMasses.Length)
m_Solver.invMasses[k] = bp.invMasses[i];
if (bp.invRotationalMasses != null && i < bp.invRotationalMasses.Length)
m_Solver.invRotationalMasses[k] = bp.invRotationalMasses[i];
if (bp.principalRadii != null && i < bp.principalRadii.Length)
m_Solver.principalRadii[k] = bp.principalRadii[i];
if (bp.filters != null && i < bp.filters.Length)
m_Solver.filters[k] = bp.filters[i];
if (bp.colors != null && i < bp.colors.Length)
m_Solver.colors[k] = bp.colors[i];
m_Solver.phases[k] = ObiUtils.MakePhase(groupID, 0);
}
m_ActiveParticleCount = sourceBlueprint.activeParticleCount;
m_Solver.dirtyActiveParticles = true;
m_Solver.dirtySimplices = true;
// Push collision materials:
UpdateCollisionMaterials();
}
private void UnloadBlueprintParticles()
{
// Update active particles.
m_ActiveParticleCount = 0;
m_Solver.dirtyActiveParticles = true;
m_Solver.dirtySimplices = true;
}
/// <summary>
/// Resets the position and velocity of all particles, to the values stored in the blueprint.
/// </summary>
/// Note however
/// that this does not affect constraints, so if you've torn a cloth/rope or resized a rope, calling ResetParticles won't restore
/// the initial topology of the actor.
public void ResetParticles()
{
if (isLoaded)
{
Matrix4x4 l2sTransform = actorLocalToSolverMatrix;
Quaternion l2sRotation = l2sTransform.rotation;
for (int i = 0; i < particleCount; ++i)
{
int solverIndex = solverIndices[i];
solver.renderablePositions[solverIndex] = solver.positions[solverIndex] = l2sTransform.MultiplyPoint3x4(sourceBlueprint.positions[i]);
solver.velocities[solverIndex] = l2sTransform.MultiplyVector(sourceBlueprint.velocities[i]);
if (usesOrientedParticles)
{
solver.renderableOrientations[solverIndex] = solver.orientations[solverIndex] = l2sRotation * sourceBlueprint.orientations[i];
solver.angularVelocities[solverIndex] = l2sTransform.MultiplyVector(sourceBlueprint.angularVelocities[i]);
}
}
}
}
#endregion
#region State
/// <summary>
/// Resets the position and velocity of all particles, to the values stored in the blueprint.
/// </summary>
/// <param name="bp"> The blueprint that we want to fill with current particle data.</param>
/// Note that this will not resize the blueprint's data arrays, and that it does not perform range checking. For this reason,
/// you must supply a blueprint large enough to store all particles' data.
public void SaveStateToBlueprint(ObiActorBlueprint bp)
{
if (bp == null)
return;
Matrix4x4 l2sTransform = actorLocalToSolverMatrix.inverse;
Quaternion l2sRotation = l2sTransform.rotation;
for (int i = 0; i < solverIndices.Length; i++)
{
int k = solverIndices[i];
if (m_Solver.positions != null && k < m_Solver.positions.count)
bp.positions[i] = l2sTransform.MultiplyPoint3x4(m_Solver.positions[k]);
if (m_Solver.velocities != null && k < m_Solver.velocities.count)
bp.velocities[i] = l2sTransform.MultiplyVector(m_Solver.velocities[k]);
}
}
protected void StoreState()
{
DestroyImmediate(state);
state = Instantiate<ObiActorBlueprint>(sourceBlueprint);
SaveStateToBlueprint(state);
}
public void ClearState()
{
DestroyImmediate(state);
}
#endregion
#region Solver callbacks
/// <summary>
/// Loads this actor's blueprint into a given solver. Automatically called by <see cref="ObiSolver"/>.
/// </summary>
public virtual void LoadBlueprint(ObiSolver solver)
{
var bp = sharedBlueprint;
// in case we have temporary state, load that instead of the original blueprint.
if (Application.isPlaying)
{
bp = state != null ? state : sourceBlueprint;
}
m_Loaded = true;
LoadBlueprintParticles(bp, solver.actors.Count);
solver.dirtyConstraints |= ~0;
if (OnBlueprintLoaded != null)
OnBlueprintLoaded(this, null);
}
/// <summary>
/// Unloads this actor's blueprint from a given solver. Automatically called by <see cref="ObiSolver"/>.
/// </summary>
public virtual void UnloadBlueprint(ObiSolver solver)
{
// instantiate blueprint and store current state in the instance:
if (Application.isPlaying)
{
StoreState();
}
m_Loaded = false;
// unload the blueprint.
solver.dirtyConstraints |= ~0;
UnloadBlueprintParticles();
if (OnBlueprintUnloaded != null)
OnBlueprintUnloaded(this, null);
}
public virtual void PrepareFrame()
{
if (OnPrepareFrame != null)
OnPrepareFrame(this);
}
public virtual void PrepareStep(float stepTime)
{
if (OnPrepareStep != null)
OnPrepareStep(this, stepTime);
}
public virtual void BeginStep(float stepTime)
{
if (OnBeginStep != null)
OnBeginStep(this,stepTime);
}
public virtual void Substep(float substepTime)
{
if (OnSubstep != null)
OnSubstep(this,substepTime);
}
public virtual void EndStep(float substepTime)
{
if (OnEndStep != null)
OnEndStep(this,substepTime);
}
public virtual void Interpolate()
{
// Update particle renderable positions/orientations in the solver:
if (!Application.isPlaying && isLoaded)
{
Matrix4x4 l2sTransform = actorLocalToSolverMatrix;
Quaternion l2sRotation = l2sTransform.rotation;
for (int i = 0; i < solverIndices.Length; i++)
{
int k = solverIndices[i];
if (sourceBlueprint.positions != null && i < sourceBlueprint.positions.Length)
{
m_Solver.renderablePositions[k] = l2sTransform.MultiplyPoint3x4(sourceBlueprint.positions[i]);
}
if (sourceBlueprint.orientations != null && i < sourceBlueprint.orientations.Length)
{
m_Solver.renderableOrientations[k] = l2sRotation * sourceBlueprint.orientations[i];
}
}
}
if (OnInterpolate != null)
OnInterpolate(this);
}
public virtual void OnSolverVisibilityChanged(bool visible)
{
}
#endregion
}
}