1392 lines
54 KiB
C#
1392 lines
54 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;
|
|
}
|
|
}
|
|
|
|
private struct BufferedForces
|
|
{
|
|
public bool dirty;
|
|
|
|
public Vector4 force;
|
|
public Vector4 acceleration;
|
|
public Vector4 impulse;
|
|
public Vector4 velChange;
|
|
|
|
public Vector4 angularForce;
|
|
public Vector4 angularAcceleration;
|
|
public Vector4 angularImpulse;
|
|
public Vector4 angularVelChange;
|
|
|
|
public void Clear()
|
|
{
|
|
force = Vector4.zero;
|
|
acceleration = Vector4.zero;
|
|
impulse = Vector4.zero;
|
|
velChange = Vector4.zero;
|
|
angularForce = Vector4.zero;
|
|
angularAcceleration = Vector4.zero;
|
|
angularImpulse = Vector4.zero;
|
|
angularVelChange = Vector4.zero;
|
|
}
|
|
}
|
|
|
|
public delegate void ActorCallback(ObiActor actor);
|
|
public delegate void ActorStepCallback(ObiActor actor, float simulatedTime, float substepTime);
|
|
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 when the blueprint currently in use has been re-generated. This will always be preceded by a call to
|
|
/// OnBlueprintUnloaded and OnBlueprintLoaded.
|
|
/// </summary>
|
|
public event ActorBlueprintCallback OnBlueprintRegenerated;
|
|
|
|
/// <summary>
|
|
/// Called before simulation starts.
|
|
/// </summary>
|
|
public event ActorStepCallback OnSimulationStart;
|
|
|
|
/// <summary>
|
|
/// Called after CPU->GPU data transfers, before collision detection starts.
|
|
/// </summary>
|
|
public event ActorStepCallback OnCollisionDetectionStart;
|
|
|
|
/// <summary>
|
|
/// Called before performing substepping.
|
|
/// </summary>
|
|
public event ActorStepCallback OnSubstepsStart;
|
|
|
|
/// <summary>
|
|
/// Called after simulation ends.
|
|
/// </summary>
|
|
public event ActorStepCallback OnSimulationEnd;
|
|
|
|
/// <summary>
|
|
/// You can use this callback to issue GPU->CPU readbacks.
|
|
/// </summary>
|
|
public event ActorCallback OnRequestReadback;
|
|
|
|
/// <summary>
|
|
/// Called at the end of each frame, after interpolation but before rendering.
|
|
/// </summary>
|
|
public event ActorStepCallback OnInterpolate;
|
|
|
|
[HideInInspector] protected ObiNativeIntList m_ActiveParticleCount;
|
|
|
|
/// <summary>
|
|
/// Index of each one of the actor's particles in the solver.
|
|
/// </summary>
|
|
[HideInInspector] public ObiNativeIntList solverIndices;
|
|
|
|
/// <summary>
|
|
/// For each of the actor's constraint types, offset of every batch in the solver.
|
|
/// </summary>
|
|
[HideInInspector] public List<int>[] solverBatchOffsets;
|
|
|
|
public int deformableEdgesOffset { protected set; get; } /**< index of the first deformable edge in the solver that belongs to this rope.*/
|
|
|
|
protected ObiSolver m_Solver;
|
|
protected bool m_Loaded = false;
|
|
public int groupID = 0;
|
|
|
|
private ObiActorBlueprint m_State;
|
|
private ObiActorBlueprint m_BlueprintInstance;
|
|
private ObiPinConstraintsData m_PinConstraints;
|
|
private ObiPinholeConstraintsData m_PinholeConstraints;
|
|
private BufferedForces bufferedForces = new BufferedForces();
|
|
[SerializeField] [HideInInspector] protected ObiCollisionMaterial m_CollisionMaterial;
|
|
[SerializeField] [HideInInspector] protected bool m_SurfaceCollisions = false;
|
|
[SerializeField] [HideInInspector] [Min(ObiUtils.epsilon)] protected float m_MassScale = 1;
|
|
|
|
/// <summary>
|
|
/// The solver in charge of simulating this actor.
|
|
/// </summary>
|
|
/// This is the first ObiSolver 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_Solver != null && 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 |= simplexTypes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scale applied to this actor's particle masses.
|
|
/// </summary>
|
|
public float massScale
|
|
{
|
|
get
|
|
{
|
|
return m_MassScale;
|
|
}
|
|
set
|
|
{
|
|
if (Mathf.Abs(m_MassScale - value) > ObiUtils.epsilon)
|
|
SetMassScale(value);
|
|
}
|
|
}
|
|
|
|
/// <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 != null ? m_ActiveParticleCount[0]:0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buffer of size 1 that contains the amount of active particles in use by this actor.
|
|
/// Useful to modify the amount of active particles from a compute shader.
|
|
/// </summary>
|
|
/// This will always be equal to or smaller than <see cref="particleCount"/>.
|
|
public ObiNativeIntList activeParticleCountBuffer
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
public Oni.SimplexType simplexTypes
|
|
{
|
|
get
|
|
{
|
|
return (sourceBlueprint != null) ? sourceBlueprint.simplexTypes : Oni.SimplexType.Point;
|
|
}
|
|
}
|
|
|
|
/// <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()
|
|
{
|
|
m_ActiveParticleCount = new ObiNativeIntList();
|
|
m_ActiveParticleCount.Add(0);
|
|
|
|
#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));
|
|
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(newParent, gameObject.scene);
|
|
transform.root.parent = newParent.transform;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
protected virtual void OnDestroy()
|
|
{
|
|
m_ActiveParticleCount.Dispose();
|
|
m_ActiveParticleCount = null;
|
|
|
|
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();
|
|
m_PinholeConstraints = new ObiPinholeConstraintsData();
|
|
|
|
// 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()
|
|
{
|
|
UpdateCollisionMaterials();
|
|
}
|
|
|
|
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 (sourceBlueprint != null)
|
|
sourceBlueprint.OnBlueprintGenerate += OnBlueprintRegenerate;
|
|
|
|
m_Solver.AddActor(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove this actor from its solver, if any. Automatically called by <see cref="ObiSolver"/>.
|
|
/// </summary>
|
|
public void RemoveFromSolver()
|
|
{
|
|
if (m_Solver != null)
|
|
{
|
|
if (sourceBlueprint != null)
|
|
sourceBlueprint.OnBlueprintGenerate -= OnBlueprintRegenerate;
|
|
|
|
m_Solver.RemoveActor(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forcibly changes 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the mass of all particles in the actor to their blueprint values, multiplied by a scale factor.
|
|
/// </summary>
|
|
/// <param name="scale"> new mass scale.
|
|
protected void SetMassScale(float scale)
|
|
{
|
|
if (Application.isPlaying && isLoaded && particleCount > 0)
|
|
{
|
|
scale = Mathf.Max(ObiUtils.epsilon, scale);
|
|
|
|
for (int i = 0; i < particleCount; ++i)
|
|
{
|
|
int solverIndex = solverIndices[i];
|
|
|
|
if (m_Solver.invMasses[solverIndex] > 0)
|
|
m_Solver.invMasses[solverIndex] = sharedBlueprint.invMasses[i] / scale;
|
|
|
|
if (m_Solver.invRotationalMasses[solverIndex] > 0 && sharedBlueprint.invRotationalMasses != null && i < sharedBlueprint.invRotationalMasses.Length)
|
|
m_Solver.invRotationalMasses[solverIndex] = sharedBlueprint.invRotationalMasses[i] / scale;
|
|
}
|
|
|
|
UpdateParticleProperties();
|
|
}
|
|
}
|
|
|
|
protected virtual void OnBlueprintRegenerate(ObiActorBlueprint blueprint)
|
|
{
|
|
// Reload by removing the current blueprint from the solver,
|
|
// destroying the current blueprint instance if any,
|
|
// then adding the shared blueprint again.
|
|
|
|
RemoveFromSolver();
|
|
|
|
if (m_BlueprintInstance != null)
|
|
DestroyImmediate(m_BlueprintInstance);
|
|
|
|
AddToSolver();
|
|
|
|
OnBlueprintRegenerated?.Invoke(this, blueprint);
|
|
}
|
|
|
|
protected void UpdateCollisionMaterials()
|
|
{
|
|
if (isLoaded)
|
|
{
|
|
int index = m_CollisionMaterial != null ? m_CollisionMaterial.handle.index : -1;
|
|
for (int i = 0; i < solverIndices.count; i++)
|
|
{
|
|
if (solverIndices[i] < solver.collisionMaterials.count)
|
|
solver.collisionMaterials[solverIndices[i]] = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void ProvideDeformableTriangles(ObiNativeIntList deformableTriangles, ObiNativeVector2List deformableUVs)
|
|
{
|
|
|
|
}
|
|
|
|
public virtual void ProvideDeformableEdges(ObiNativeIntList deformableEdges)
|
|
{
|
|
|
|
}
|
|
|
|
public virtual int GetDeformableEdgeCount()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/// <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.count ||
|
|
actorDestIndex < 0 || actorDestIndex >= solverIndices.count)
|
|
return false;
|
|
|
|
int sourceIndex = solverIndices[actorSourceIndex];
|
|
int destIndex = solverIndices[actorDestIndex];
|
|
|
|
// Copy solver data:
|
|
m_Solver.prevPositions[destIndex] = m_Solver.prevPositions[sourceIndex];
|
|
m_Solver.restPositions[destIndex] = m_Solver.restPositions[sourceIndex];
|
|
m_Solver.endPositions[destIndex] = m_Solver.startPositions[destIndex] = m_Solver.positions[destIndex] = m_Solver.positions[sourceIndex];
|
|
|
|
m_Solver.prevOrientations[destIndex] = m_Solver.prevOrientations[sourceIndex];
|
|
m_Solver.restOrientations[destIndex] = m_Solver.restOrientations[sourceIndex];
|
|
m_Solver.endOrientations[destIndex] = m_Solver.startOrientations[destIndex] = m_Solver.orientations[destIndex] = m_Solver.orientations[sourceIndex];
|
|
|
|
m_Solver.velocities[destIndex] = m_Solver.velocities[sourceIndex];
|
|
m_Solver.angularVelocities[destIndex] = m_Solver.angularVelocities[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.count)
|
|
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.endPositions[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 Matrix4x4 Teleport(Vector3 position, Quaternion rotation)
|
|
{
|
|
if (!isLoaded)
|
|
return Matrix4x4.identity;
|
|
|
|
// 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.count; i++)
|
|
{
|
|
int solverIndex = solverIndices[i];
|
|
|
|
m_Solver.positions[solverIndex] =
|
|
m_Solver.prevPositions[solverIndex] =
|
|
m_Solver.endPositions[solverIndex] =
|
|
m_Solver.startPositions[solverIndex] = offset.MultiplyPoint3x4(m_Solver.positions[solverIndex]);
|
|
|
|
m_Solver.orientations[solverIndex] =
|
|
m_Solver.prevOrientations[solverIndex] =
|
|
m_Solver.endOrientations[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;
|
|
|
|
return offset;
|
|
}
|
|
|
|
protected virtual void SwapWithFirstInactiveParticle(int actorIndex)
|
|
{
|
|
// update solver indices:
|
|
m_Solver.particleToActor[solverIndices[actorIndex]].indexInActor = activeParticleCount;
|
|
m_Solver.particleToActor[solverIndices[activeParticleCount]].indexInActor = actorIndex;
|
|
solverIndices.Swap(actorIndex, activeParticleCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Activates one particle.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// True if a particle could be activated. False if there are no particles to activate.
|
|
/// </returns>
|
|
/// This operation preserves the relative order of all particles.
|
|
public virtual bool ActivateParticle()
|
|
{
|
|
if (activeParticleCount >= particleCount)
|
|
return false;
|
|
|
|
// set active particle radius W to 1.
|
|
var radii = m_Solver.principalRadii[solverIndices[activeParticleCount]];
|
|
radii.w = 1;
|
|
m_Solver.principalRadii[solverIndices[activeParticleCount]] = radii;
|
|
|
|
m_ActiveParticleCount[0]++;
|
|
m_Solver.dirtyActiveParticles = true;
|
|
m_Solver.dirtySimplices |= simplexTypes;
|
|
|
|
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 virtual bool DeactivateParticle(int actorIndex)
|
|
{
|
|
if (!IsParticleActive(actorIndex))
|
|
return false;
|
|
|
|
m_ActiveParticleCount[0]--;
|
|
|
|
// set inactive particle W to zero, this allows renderers to ignore it.
|
|
var radii = m_Solver.principalRadii[solverIndices[actorIndex]];
|
|
radii.w = 0;
|
|
m_Solver.principalRadii[solverIndices[actorIndex]] = radii;
|
|
|
|
SwapWithFirstInactiveParticle(actorIndex);
|
|
m_Solver.dirtyActiveParticles = true;
|
|
m_Solver.dirtySimplices |= simplexTypes;
|
|
|
|
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 virtual bool IsParticleActive(int actorIndex)
|
|
{
|
|
return actorIndex < 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 self-collision flag.
|
|
/// </summary>
|
|
public virtual void SetDirectionalCollisions(bool dfCollisions)
|
|
{
|
|
if (m_Solver != null && Application.isPlaying && isLoaded)
|
|
{
|
|
for (int i = 0; i < particleCount; i++)
|
|
{
|
|
var norm = m_Solver.normals[solverIndices[i]];
|
|
|
|
if (dfCollisions)
|
|
norm.w = sharedBlueprint.restNormals[i].w;
|
|
else
|
|
norm.w = 0;
|
|
|
|
m_Solver.normals[solverIndices[i]] = norm;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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 |= simplexTypes;
|
|
}
|
|
|
|
/// <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>
|
|
/// Marks rendering dirty.
|
|
/// </summary>
|
|
public void SetRenderingDirty(Oni.RenderingSystemType rendererType)
|
|
{
|
|
if (m_Solver != null)
|
|
m_Solver.dirtyRendering |= (int)rendererType;
|
|
}
|
|
|
|
/// <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 (type == Oni.ConstraintType.Pinhole)
|
|
return m_PinholeConstraints;
|
|
|
|
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 rest position of that particle.
|
|
/// </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 GetParticleRestPosition(int solverIndex)
|
|
{
|
|
if (isLoaded)
|
|
return m_Solver.restPositions[solverIndex];
|
|
return Vector3.zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a solver particle index, returns the rest orientation of that particle.
|
|
/// </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 GetParticleRestOrientation(int solverIndex)
|
|
{
|
|
if (isLoaded)
|
|
return m_Solver.restOrientations[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)
|
|
{
|
|
b1 = m_Solver.transform.TransformDirection(m_Solver.renderableOrientations[solverIndex] * Vector3.right);
|
|
b2 = m_Solver.transform.TransformDirection(m_Solver.renderableOrientations[solverIndex] * Vector3.up);
|
|
b3 = m_Solver.transform.TransformDirection(m_Solver.renderableOrientations[solverIndex] * Vector3.forward);
|
|
|
|
b1[3] = m_Solver.maxScale * m_Solver.renderableRadii[solverIndex][0];
|
|
b2[3] = m_Solver.maxScale * m_Solver.renderableRadii[solverIndex][1];
|
|
b3[3] = m_Solver.maxScale * m_Solver.renderableRadii[solverIndex][2];
|
|
}
|
|
else
|
|
{
|
|
b1[3] = b2[3] = b3[3] = m_Solver.maxScale * m_Solver.renderableRadii[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;
|
|
}
|
|
|
|
UpdateParticleProperties();
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
if (force.sqrMagnitude > Mathf.Epsilon)
|
|
bufferedForces.dirty = true;
|
|
|
|
switch (forceMode)
|
|
{
|
|
case ForceMode.Force:
|
|
bufferedForces.force += (Vector4)force;
|
|
break;
|
|
case ForceMode.Acceleration:
|
|
bufferedForces.acceleration += (Vector4)force;
|
|
break;
|
|
case ForceMode.Impulse:
|
|
bufferedForces.impulse += (Vector4)force;
|
|
break;
|
|
case ForceMode.VelocityChange:
|
|
bufferedForces.velChange += (Vector4)force;
|
|
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)
|
|
{
|
|
if (force.sqrMagnitude > Mathf.Epsilon)
|
|
bufferedForces.dirty = true;
|
|
|
|
switch (forceMode)
|
|
{
|
|
case ForceMode.Force:
|
|
bufferedForces.angularForce += (Vector4)force;
|
|
break;
|
|
case ForceMode.Acceleration:
|
|
bufferedForces.angularAcceleration += (Vector4)force;
|
|
break;
|
|
case ForceMode.Impulse:
|
|
bufferedForces.angularImpulse += (Vector4)force;
|
|
break;
|
|
case ForceMode.VelocityChange:
|
|
bufferedForces.angularVelChange += (Vector4)force;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#region Blueprints
|
|
|
|
private void LoadBlueprintParticles(ObiActorBlueprint bp)
|
|
{
|
|
|
|
Matrix4x4 l2sTransform = actorLocalToSolverMatrix;
|
|
Quaternion l2sRotation = l2sTransform.rotation;
|
|
|
|
for (int i = 0; i < solverIndices.count; i++)
|
|
{
|
|
int k = solverIndices[i];
|
|
|
|
if (bp.positions != null && i < bp.positions.Length)
|
|
{
|
|
m_Solver.endPositions[k] = 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.endOrientations[k] = m_Solver.startOrientations[k] = m_Solver.prevOrientations[k] = m_Solver.orientations[k] = l2sRotation * bp.orientations[i];
|
|
m_Solver.renderableOrientations[k] = l2sRotation * bp.orientations[i];
|
|
}
|
|
|
|
// for softbodies, xyz values store SDF normal in particle's local space: needs to be transformed using particle orientation during simulation.
|
|
// w value stores sparse SDF if < 0.
|
|
if (bp.restNormals != null && i < bp.restNormals.Length)
|
|
m_Solver.normals[k] = bp.restNormals[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] / m_MassScale;
|
|
|
|
if (bp.invRotationalMasses != null && i < bp.invRotationalMasses.Length)
|
|
m_Solver.invRotationalMasses[k] = bp.invRotationalMasses[i] / m_MassScale;
|
|
|
|
if (bp.principalRadii != null && i < bp.principalRadii.Length)
|
|
{
|
|
Vector4 radii = bp.principalRadii[i];
|
|
radii.w = i < sourceBlueprint.activeParticleCount ? 1 : 0;
|
|
m_Solver.principalRadii[k] = radii;
|
|
}
|
|
else
|
|
{
|
|
// need inactive emitter particles to zero as their flag.
|
|
m_Solver.principalRadii[k] = Vector4.zero;
|
|
}
|
|
|
|
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[0] = sourceBlueprint.activeParticleCount;
|
|
m_Solver.dirtyActiveParticles = true;
|
|
m_Solver.dirtyDeformableTriangles = true;
|
|
m_Solver.dirtyDeformableEdges = true;
|
|
m_Solver.dirtySimplices |= simplexTypes;
|
|
m_Solver.dirtyConstraints |= ~0;
|
|
|
|
// Push collision materials:
|
|
UpdateCollisionMaterials();
|
|
|
|
}
|
|
|
|
private void UnloadBlueprintParticles()
|
|
{
|
|
// Update active particles.
|
|
m_ActiveParticleCount[0] = 0;
|
|
m_Solver.dirtyActiveParticles = true;
|
|
m_Solver.dirtyDeformableTriangles = true;
|
|
m_Solver.dirtyDeformableEdges = true;
|
|
m_Solver.dirtySimplices |= simplexTypes;
|
|
m_Solver.dirtyConstraints |= ~0;
|
|
}
|
|
|
|
/// <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.startPositions[solverIndex] = solver.endPositions[solverIndex] = solver.positions[solverIndex] = l2sTransform.MultiplyPoint3x4(sourceBlueprint.positions[i]);
|
|
solver.velocities[solverIndex] = l2sTransform.MultiplyVector(sourceBlueprint.velocities[i]);
|
|
|
|
if (usesOrientedParticles)
|
|
{
|
|
solver.startOrientations[solverIndex] = solver.endOrientations[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 bool SaveStateToBlueprint(ObiActorBlueprint bp)
|
|
{
|
|
if (bp == null || !m_Loaded)
|
|
return false;
|
|
|
|
Matrix4x4 l2sTransform = actorLocalToSolverMatrix.inverse;
|
|
Quaternion l2sRotation = l2sTransform.rotation;
|
|
|
|
// blueprint might have been regenerated, and reduced its size:
|
|
for (int i = 0; i < solverIndices.count; i++)
|
|
{
|
|
int k = solverIndices[i];
|
|
|
|
if (bp.positions != null && m_Solver.positions != null && k < m_Solver.positions.count && i < bp.positions.Length)
|
|
bp.positions[i] = l2sTransform.MultiplyPoint3x4(m_Solver.positions[k]);
|
|
|
|
if (bp.velocities != null && m_Solver.velocities != null && k < m_Solver.velocities.count && i < bp.velocities.Length)
|
|
bp.velocities[i] = l2sTransform.MultiplyVector(m_Solver.velocities[k]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected void StoreState()
|
|
{
|
|
DestroyImmediate(m_State);
|
|
m_State = Instantiate(sourceBlueprint);
|
|
SaveStateToBlueprint(m_State);
|
|
}
|
|
|
|
public void ClearState()
|
|
{
|
|
DestroyImmediate(m_State);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Solver callbacks
|
|
|
|
/// <summary>
|
|
/// Loads this actor's blueprint into the current solver. Automatically called by <see cref="ObiSolver"/>.
|
|
/// </summary>
|
|
internal virtual void LoadBlueprint()
|
|
{
|
|
var bp = sharedBlueprint;
|
|
|
|
// in case we have temporary state, load that instead of the original blueprint.
|
|
if (Application.isPlaying)
|
|
{
|
|
bp = m_State != null ? m_State : sourceBlueprint;
|
|
}
|
|
|
|
m_Loaded = true;
|
|
|
|
LoadBlueprintParticles(bp);
|
|
|
|
OnBlueprintLoaded?.Invoke(this, bp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unloads this actor's blueprint from a given solver. Automatically called by <see cref="ObiSolver"/>.
|
|
/// </summary>
|
|
internal virtual void UnloadBlueprint()
|
|
{
|
|
// instantiate blueprint and store current state in the instance:
|
|
if (Application.isPlaying)
|
|
StoreState();
|
|
|
|
m_Loaded = false;
|
|
|
|
// unload the blueprint.
|
|
UnloadBlueprintParticles();
|
|
|
|
OnBlueprintUnloaded?.Invoke(this, sharedBlueprint);
|
|
}
|
|
|
|
public virtual void SimulationStart(float timeToSimulate, float substepTime)
|
|
{
|
|
OnSimulationStart?.Invoke(this, timeToSimulate, substepTime);
|
|
|
|
// Apply any buffered forces/torques:
|
|
if (bufferedForces.dirty)
|
|
{
|
|
float mass = GetMass(out Vector3 com);
|
|
|
|
if (!float.IsInfinity(mass))
|
|
{
|
|
Vector4 accum;
|
|
foreach (var p in solverIndices)
|
|
{
|
|
accum = bufferedForces.force / m_Solver.invMasses[p] / mass;
|
|
accum += bufferedForces.acceleration / m_Solver.invMasses[p];
|
|
accum += bufferedForces.impulse / m_Solver.invMasses[p] / mass / timeToSimulate;
|
|
accum += bufferedForces.velChange / m_Solver.invMasses[p] / timeToSimulate;
|
|
m_Solver.externalForces[p] += accum;
|
|
|
|
accum = bufferedForces.angularForce / m_Solver.invMasses[p] / mass;
|
|
accum += bufferedForces.angularAcceleration / m_Solver.invMasses[p];
|
|
accum += bufferedForces.angularImpulse / m_Solver.invMasses[p] / mass / timeToSimulate;
|
|
accum += bufferedForces.angularVelChange / m_Solver.invMasses[p] / timeToSimulate;
|
|
m_Solver.externalForces[p] += (Vector4)Vector3.Cross(accum, (Vector3)m_Solver.positions[p] - com);
|
|
}
|
|
}
|
|
|
|
bufferedForces.Clear();
|
|
}
|
|
}
|
|
|
|
public virtual void CollisionDetectionStart(float simulatedTime, float substepTime)
|
|
{
|
|
OnCollisionDetectionStart?.Invoke(this, simulatedTime, substepTime);
|
|
}
|
|
|
|
public virtual void SubstepsStart(float simulatedTime, float substepTime)
|
|
{
|
|
OnSubstepsStart?.Invoke(this, simulatedTime, substepTime);
|
|
}
|
|
|
|
public virtual void SimulationEnd(float simulatedTime, float substepTime)
|
|
{
|
|
OnSimulationEnd?.Invoke(this, simulatedTime, substepTime);
|
|
}
|
|
|
|
public virtual void RequestReadback()
|
|
{
|
|
OnRequestReadback?.Invoke(this);
|
|
}
|
|
|
|
public virtual void Interpolate(float simulatedTime, float substepTime)
|
|
{
|
|
// Update particle positions/orientations in the solver:
|
|
if (!Application.isPlaying && isLoaded)
|
|
{
|
|
Matrix4x4 l2sTransform = actorLocalToSolverMatrix;
|
|
Quaternion l2sRotation = l2sTransform.rotation;
|
|
|
|
for (int i = 0; i < solverIndices.count; i++)
|
|
{
|
|
int k = solverIndices[i];
|
|
|
|
if (sourceBlueprint.positions != null && i < sourceBlueprint.positions.Length)
|
|
{
|
|
m_Solver.renderablePositions[k] = m_Solver.positions[k] = m_Solver.startPositions[k] = m_Solver.endPositions[k] = l2sTransform.MultiplyPoint3x4(sourceBlueprint.positions[i]);
|
|
}
|
|
|
|
if (sourceBlueprint.orientations != null && i < sourceBlueprint.orientations.Length)
|
|
{
|
|
m_Solver.renderableOrientations[k] = m_Solver.orientations[k] = m_Solver.startOrientations[k] = m_Solver.endOrientations[k] = l2sRotation * sourceBlueprint.orientations[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
OnInterpolate?.Invoke(this, simulatedTime, substepTime);
|
|
}
|
|
|
|
public virtual void OnSolverVisibilityChanged(bool visible)
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
}
|
|
|