702 lines
25 KiB
C#
702 lines
25 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Serialization;
|
|
using System.IO;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace Obi
|
|
{
|
|
|
|
public abstract class ObiActorBlueprint : ScriptableObject, IObiParticleCollection
|
|
{
|
|
public delegate void BlueprintCallback(ObiActorBlueprint blueprint);
|
|
public event BlueprintCallback OnBlueprintGenerate;
|
|
|
|
[HideInInspector] [SerializeField] protected uint m_Checksum;
|
|
[HideInInspector] [SerializeField] protected bool m_Empty = true;
|
|
[HideInInspector] [SerializeField] protected bool m_Edited = false; /**< Whether there's been any modifications to blueprint data since generating it. This is used to tell whether it can be re-generated without data loss.*/
|
|
[HideInInspector] [SerializeField] protected int m_ActiveParticleCount = 0;
|
|
[HideInInspector] [SerializeField] protected int m_InitialActiveParticleCount = 0;
|
|
[HideInInspector] [SerializeField] protected Bounds _bounds = new Bounds();
|
|
|
|
/**Particle components*/
|
|
[HideInInspector] public Vector3[] positions = null; /**< Particle positions.*/
|
|
[HideInInspector] public Vector4[] restPositions = null; /**< Particle rest positions, used to filter collisions.*/
|
|
|
|
[HideInInspector] public Quaternion[] orientations = null; /**< Particle orientations.*/
|
|
[HideInInspector] public Quaternion[] restOrientations = null; /**< Particle rest orientations.*/
|
|
|
|
[HideInInspector] public Vector3[] velocities = null; /**< Particle velocities.*/
|
|
[HideInInspector] public Vector3[] angularVelocities = null; /**< Particle angular velocities.*/
|
|
|
|
[HideInInspector] public float[] invMasses = null; /**< Particle inverse masses*/
|
|
[HideInInspector] public float[] invRotationalMasses = null;
|
|
|
|
[FormerlySerializedAs("phases")]
|
|
[HideInInspector] public int[] filters = null; /**< Particle filters*/
|
|
[HideInInspector] public Vector3[] principalRadii = null; /**< Particle ellipsoid principal radii. These are the ellipsoid radius in each axis.*/
|
|
[HideInInspector] public Color[] colors = null; /**< Particle colors (not used by all actors, can be null)*/
|
|
|
|
/** Simplices **/
|
|
[HideInInspector] public int[] points = null;
|
|
[HideInInspector] public int[] edges = null;
|
|
[HideInInspector] public int[] triangles = null;
|
|
|
|
/** Constraint components. Each constraint type contains a list of constraint batches.*/
|
|
[HideInInspector] public ObiDistanceConstraintsData distanceConstraintsData = null;
|
|
[HideInInspector] public ObiBendConstraintsData bendConstraintsData = null;
|
|
[HideInInspector] public ObiSkinConstraintsData skinConstraintsData = null;
|
|
[HideInInspector] public ObiTetherConstraintsData tetherConstraintsData = null;
|
|
[HideInInspector] public ObiStretchShearConstraintsData stretchShearConstraintsData = null;
|
|
[HideInInspector] public ObiBendTwistConstraintsData bendTwistConstraintsData = null;
|
|
[HideInInspector] public ObiShapeMatchingConstraintsData shapeMatchingConstraintsData = null;
|
|
[HideInInspector] public ObiAerodynamicConstraintsData aerodynamicConstraintsData = null;
|
|
[HideInInspector] public ObiChainConstraintsData chainConstraintsData = null;
|
|
[HideInInspector] public ObiVolumeConstraintsData volumeConstraintsData = null;
|
|
|
|
/** Particle groups.*/
|
|
[HideInInspector] public List<ObiParticleGroup> groups = new List<ObiParticleGroup>();
|
|
|
|
/**
|
|
* Checksum value generated from particle positions and orientations.
|
|
*/
|
|
public uint checksum
|
|
{
|
|
get { return m_Checksum; }
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of particles used by this blueprint.
|
|
*/
|
|
public int particleCount
|
|
{
|
|
get { return positions != null ? positions.Length : 0; }
|
|
}
|
|
|
|
public int activeParticleCount
|
|
{
|
|
get { return m_ActiveParticleCount; }
|
|
}
|
|
|
|
public Oni.SimplexType simplexTypes
|
|
{
|
|
get {
|
|
return Oni.SimplexType.Point | // points (single particles) are always available.
|
|
(edges != null ? Oni.SimplexType.Edge : 0) |
|
|
(triangles != null ? Oni.SimplexType.Triangle : 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether this group uses oriented particles.
|
|
*/
|
|
public bool usesOrientedParticles
|
|
{
|
|
get
|
|
{
|
|
return invRotationalMasses != null && invRotationalMasses.Length > 0 &&
|
|
orientations != null && orientations.Length > 0;
|
|
}
|
|
}
|
|
|
|
public virtual bool usesTethers
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
public bool IsParticleActive(int index)
|
|
{
|
|
return index < m_ActiveParticleCount;
|
|
}
|
|
|
|
protected virtual void SwapWithFirstInactiveParticle(int index)
|
|
{
|
|
positions.Swap(index, m_ActiveParticleCount);
|
|
restPositions.Swap(index, m_ActiveParticleCount);
|
|
orientations.Swap(index, m_ActiveParticleCount);
|
|
restOrientations.Swap(index, m_ActiveParticleCount);
|
|
velocities.Swap(index, m_ActiveParticleCount);
|
|
angularVelocities.Swap(index, m_ActiveParticleCount);
|
|
invMasses.Swap(index, m_ActiveParticleCount);
|
|
invRotationalMasses.Swap(index, m_ActiveParticleCount);
|
|
filters.Swap(index, m_ActiveParticleCount);
|
|
principalRadii.Swap(index, m_ActiveParticleCount);
|
|
colors.Swap(index, m_ActiveParticleCount);
|
|
|
|
m_Edited = true;
|
|
}
|
|
|
|
public bool edited
|
|
{
|
|
get { return m_Edited; }
|
|
set { m_Edited = value;}
|
|
}
|
|
|
|
/**
|
|
* Activates one particle. This operation preserves the relative order of all particles.
|
|
*/
|
|
public bool ActivateParticle(int index)
|
|
{
|
|
if (IsParticleActive(index))
|
|
return false;
|
|
|
|
SwapWithFirstInactiveParticle(index);
|
|
m_ActiveParticleCount++;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Deactivates one particle. 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 index)
|
|
{
|
|
if (!IsParticleActive(index))
|
|
return false;
|
|
|
|
m_ActiveParticleCount--;
|
|
SwapWithFirstInactiveParticle(index);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool empty
|
|
{
|
|
get { return m_Empty; }
|
|
}
|
|
|
|
public void RecalculateBounds()
|
|
{
|
|
if (positions.Length > 0)
|
|
{
|
|
_bounds = new Bounds(positions[0],Vector3.zero);
|
|
for (int i = 1; i < positions.Length; ++i)
|
|
_bounds.Encapsulate(positions[i]);
|
|
}
|
|
else
|
|
_bounds = new Bounds();
|
|
}
|
|
|
|
public Bounds bounds
|
|
{
|
|
get { return _bounds; }
|
|
}
|
|
|
|
protected void GenerateChecksum()
|
|
{
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
if (positions != null)
|
|
foreach (var p in positions) ms.Concatenate(p);
|
|
|
|
if (orientations != null)
|
|
foreach (var o in orientations) ms.Concatenate(o);
|
|
|
|
ms.Flush();
|
|
m_Checksum = ObiUtils.Adler32(ms.ToArray());
|
|
}
|
|
}
|
|
|
|
public IEnumerable<IObiConstraints> GetConstraints()
|
|
{
|
|
if (distanceConstraintsData != null && distanceConstraintsData.batchCount > 0)
|
|
yield return distanceConstraintsData;
|
|
if (bendConstraintsData != null && bendConstraintsData.batchCount > 0)
|
|
yield return bendConstraintsData;
|
|
if (skinConstraintsData != null && skinConstraintsData.batchCount > 0)
|
|
yield return skinConstraintsData;
|
|
if (tetherConstraintsData != null && tetherConstraintsData.batchCount > 0)
|
|
yield return tetherConstraintsData;
|
|
if (stretchShearConstraintsData != null && stretchShearConstraintsData.batchCount > 0)
|
|
yield return stretchShearConstraintsData;
|
|
if (bendTwistConstraintsData != null && bendTwistConstraintsData.batchCount > 0)
|
|
yield return bendTwistConstraintsData;
|
|
if (shapeMatchingConstraintsData != null && shapeMatchingConstraintsData.batchCount > 0)
|
|
yield return shapeMatchingConstraintsData;
|
|
if (aerodynamicConstraintsData != null && aerodynamicConstraintsData.batchCount > 0)
|
|
yield return aerodynamicConstraintsData;
|
|
if (chainConstraintsData != null && chainConstraintsData.batchCount > 0)
|
|
yield return chainConstraintsData;
|
|
if (volumeConstraintsData != null && volumeConstraintsData.batchCount > 0)
|
|
yield return volumeConstraintsData;
|
|
}
|
|
|
|
public IObiConstraints GetConstraintsByType(Oni.ConstraintType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case Oni.ConstraintType.Distance: return distanceConstraintsData;
|
|
case Oni.ConstraintType.Bending: return bendConstraintsData;
|
|
case Oni.ConstraintType.Skin: return skinConstraintsData;
|
|
case Oni.ConstraintType.Tether: return tetherConstraintsData;
|
|
case Oni.ConstraintType.BendTwist: return bendTwistConstraintsData;
|
|
case Oni.ConstraintType.StretchShear: return stretchShearConstraintsData;
|
|
case Oni.ConstraintType.ShapeMatching: return shapeMatchingConstraintsData;
|
|
case Oni.ConstraintType.Aerodynamics: return aerodynamicConstraintsData;
|
|
case Oni.ConstraintType.Chain: return chainConstraintsData;
|
|
case Oni.ConstraintType.Volume: return volumeConstraintsData;
|
|
default: return null;
|
|
}
|
|
}
|
|
|
|
public int GetParticleRuntimeIndex(int blueprintIndex)
|
|
{
|
|
return blueprintIndex;
|
|
}
|
|
|
|
public Vector3 GetParticlePosition(int index)
|
|
{
|
|
if (positions != null && index < positions.Length)
|
|
{
|
|
return positions[index];
|
|
}
|
|
return Vector3.zero;
|
|
}
|
|
|
|
public Quaternion GetParticleOrientation(int index)
|
|
{
|
|
if (orientations != null && index < orientations.Length)
|
|
{
|
|
return orientations[index];
|
|
}
|
|
return Quaternion.identity;
|
|
}
|
|
|
|
public Vector3 GetParticleRestPosition(int index)
|
|
{
|
|
if (restPositions != null && index < restPositions.Length)
|
|
{
|
|
return restPositions[index];
|
|
}
|
|
return Vector3.zero;
|
|
}
|
|
|
|
public Quaternion GetParticleRestOrientation(int index)
|
|
{
|
|
if (restOrientations != null && index < restOrientations.Length)
|
|
{
|
|
return restOrientations[index];
|
|
}
|
|
return Quaternion.identity;
|
|
}
|
|
|
|
public void GetParticleAnisotropy(int index, ref Vector4 b1, ref Vector4 b2, ref Vector4 b3)
|
|
{
|
|
if (orientations != null && index < orientations.Length)
|
|
{
|
|
|
|
Quaternion orientation = orientations[index];
|
|
|
|
b1 = orientation * Vector3.right;
|
|
b2 = orientation * Vector3.up;
|
|
b3 = orientation * Vector3.forward;
|
|
|
|
b1[3] = principalRadii[index][0];
|
|
b2[3] = principalRadii[index][1];
|
|
b3[3] = principalRadii[index][2];
|
|
|
|
}
|
|
else
|
|
{
|
|
b1[3] = b2[3] = b3[3] = principalRadii[index][0];
|
|
}
|
|
}
|
|
|
|
public float GetParticleMaxRadius(int index)
|
|
{
|
|
if (principalRadii != null && index < principalRadii.Length)
|
|
{
|
|
return principalRadii[index][0];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public Color GetParticleColor(int index)
|
|
{
|
|
if (colors != null && index < colors.Length)
|
|
{
|
|
return colors[index];
|
|
}
|
|
else
|
|
return Color.white;
|
|
}
|
|
|
|
public void GenerateImmediate()
|
|
{
|
|
var g = Generate();
|
|
while (g.MoveNext()){}
|
|
}
|
|
|
|
public IEnumerator Generate()
|
|
{
|
|
Clear();
|
|
|
|
IEnumerator g = Initialize();
|
|
|
|
while (g.MoveNext())
|
|
yield return g.Current;
|
|
|
|
RecalculateBounds();
|
|
|
|
m_Empty = false;
|
|
m_InitialActiveParticleCount = m_ActiveParticleCount;
|
|
|
|
foreach (IObiConstraints constraints in GetConstraints())
|
|
for (int i = 0; i < constraints.batchCount; ++i)
|
|
constraints.GetBatch(i).initialActiveConstraintCount = constraints.GetBatch(i).activeConstraintCount;
|
|
|
|
CommitBlueprintChanges();
|
|
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
EditorUtility.SetDirty(this);
|
|
}
|
|
#endif
|
|
|
|
OnBlueprintGenerate?.Invoke(this);
|
|
}
|
|
|
|
// Called at the end of blueprint generation. Also automatically called when exiting blueprint editor.
|
|
// This generates a checksum for the blueprint, and in some case extra data (such as default skinmaps for cloth and softbodies).
|
|
public virtual void CommitBlueprintChanges()
|
|
{
|
|
GenerateChecksum();
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
m_Empty = true;
|
|
edited = false;
|
|
|
|
m_ActiveParticleCount = 0;
|
|
positions = null;
|
|
restPositions = null;
|
|
orientations = null;
|
|
restOrientations = null;
|
|
velocities = null;
|
|
angularVelocities = null;
|
|
invMasses = null;
|
|
invRotationalMasses = null;
|
|
filters = null;
|
|
principalRadii = null;
|
|
colors = null;
|
|
|
|
points = null;
|
|
edges = null;
|
|
triangles = null;
|
|
|
|
distanceConstraintsData = null;
|
|
bendConstraintsData = null;
|
|
skinConstraintsData = null;
|
|
tetherConstraintsData = null;
|
|
bendTwistConstraintsData = null;
|
|
stretchShearConstraintsData = null;
|
|
shapeMatchingConstraintsData = null;
|
|
aerodynamicConstraintsData = null;
|
|
chainConstraintsData = null;
|
|
volumeConstraintsData = null;
|
|
|
|
}
|
|
|
|
public ObiParticleGroup InsertNewParticleGroup(string name, int index, bool saveImmediately = true)
|
|
{
|
|
if (index >= 0 && index <= groups.Count)
|
|
{
|
|
ObiParticleGroup group = ScriptableObject.CreateInstance<ObiParticleGroup>();
|
|
group.SetSourceBlueprint(this);
|
|
group.name = name;
|
|
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
AssetDatabase.AddObjectToAsset(group, this);
|
|
Undo.RegisterCreatedObjectUndo(group, "Insert particle group");
|
|
|
|
Undo.RecordObject(this, "Insert particle group");
|
|
groups.Insert(index, group);
|
|
|
|
if (EditorUtility.IsPersistent(this))
|
|
{
|
|
EditorUtility.SetDirty(this);
|
|
if (saveImmediately)
|
|
AssetDatabase.SaveAssetIfDirty(this);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
groups.Insert(index, group);
|
|
}
|
|
|
|
edited = true;
|
|
|
|
return group;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public ObiParticleGroup AppendNewParticleGroup(string name, bool saveImmediately = true)
|
|
{
|
|
return InsertNewParticleGroup(name, groups.Count, saveImmediately);
|
|
}
|
|
|
|
public bool RemoveParticleGroupAt(int index, bool saveImmediately = true)
|
|
{
|
|
if (index >= 0 && index < groups.Count)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
Undo.RecordObject(this, "Remove particle group");
|
|
|
|
var group = groups[index];
|
|
groups.RemoveAt(index);
|
|
|
|
if (group != null)
|
|
Undo.DestroyObjectImmediate(group);
|
|
|
|
if (EditorUtility.IsPersistent(this))
|
|
{
|
|
EditorUtility.SetDirty(this);
|
|
if (saveImmediately)
|
|
AssetDatabase.SaveAssetIfDirty(this);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
var group = groups[index];
|
|
groups.RemoveAt(index);
|
|
|
|
if (group != null)
|
|
DestroyImmediate(group, true);
|
|
}
|
|
|
|
edited = true;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool SetParticleGroupName(int index, string name, bool saveImmediately = true)
|
|
{
|
|
if (index >= 0 && index < groups.Count)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
Undo.RecordObject(this, "Set particle group name");
|
|
groups[index].name = name;
|
|
|
|
if (EditorUtility.IsPersistent(this))
|
|
{
|
|
EditorUtility.SetDirty(this);
|
|
if (saveImmediately)
|
|
AssetDatabase.SaveAssetIfDirty(this);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
groups[index].name = name;
|
|
}
|
|
|
|
edited = true;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void ClearParticleGroups(bool registerUndo = true, bool saveImmediately = true)
|
|
{
|
|
if (groups.Count == 0) return;
|
|
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
if (registerUndo)
|
|
{
|
|
Undo.RecordObject(this, "Clear particle groups");
|
|
for (int i = 0; i < groups.Count; ++i)
|
|
if (groups[i] != null)
|
|
Undo.DestroyObjectImmediate(groups[i]);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < groups.Count; ++i)
|
|
if (groups[i] != null)
|
|
DestroyImmediate(groups[i], true);
|
|
}
|
|
|
|
if (EditorUtility.IsPersistent(this))
|
|
{
|
|
EditorUtility.SetDirty(this);
|
|
if (saveImmediately)
|
|
AssetDatabase.SaveAssetIfDirty(this);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
for (int i = 0; i < groups.Count; ++i)
|
|
if (groups[i] != null)
|
|
DestroyImmediate(groups[i], true);
|
|
}
|
|
|
|
groups.Clear();
|
|
}
|
|
|
|
private bool IsParticleSharedInConstraint(int index, List<int> particles, bool[] selected)
|
|
{
|
|
bool containsCurrent = false;
|
|
bool containsUnselected = false;
|
|
|
|
for (int k = 0; k < particles.Count; ++k)
|
|
{
|
|
containsCurrent |= particles[k] == index;
|
|
containsUnselected |= !selected[particles[k]];
|
|
|
|
if (containsCurrent && containsUnselected)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool DoesParticleShareConstraints(IObiConstraints constraints, int index, List<int> particles, bool[] selected)
|
|
{
|
|
bool shared = false;
|
|
for (int i = 0; i < constraints.batchCount; ++i)
|
|
{
|
|
var batch = constraints.GetBatch(i);
|
|
for (int j = 0; j < batch.activeConstraintCount; ++j)
|
|
{
|
|
particles.Clear();
|
|
batch.GetParticlesInvolved(j, particles);
|
|
|
|
if (shared |= IsParticleSharedInConstraint(index, particles, selected))
|
|
break;
|
|
}
|
|
|
|
if (shared)
|
|
break;
|
|
}
|
|
return shared;
|
|
}
|
|
|
|
private void DeactivateConstraintsWithInactiveParticles(IObiConstraints constraints, List<int> particles)
|
|
{
|
|
for (int j = 0; j < constraints.batchCount; ++j)
|
|
{
|
|
var batch = constraints.GetBatch(j);
|
|
|
|
for (int i = batch.activeConstraintCount - 1; i >= 0; --i)
|
|
{
|
|
particles.Clear();
|
|
batch.GetParticlesInvolved(i, particles);
|
|
for (int k = 0; k < particles.Count; ++k)
|
|
{
|
|
if (!IsParticleActive(particles[k]))
|
|
{
|
|
batch.DeactivateConstraint(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
edited = true;
|
|
}
|
|
|
|
private void ParticlesSwappedInGroups(int index, int newIndex)
|
|
{
|
|
// Update groups:
|
|
foreach (ObiParticleGroup group in groups)
|
|
{
|
|
for (int i = 0; i < group.particleIndices.Count; ++i)
|
|
{
|
|
if (group.particleIndices[i] == newIndex)
|
|
group.particleIndices[i] = index;
|
|
else if (group.particleIndices[i] == index)
|
|
group.particleIndices[i] = newIndex;
|
|
}
|
|
}
|
|
|
|
edited = true;
|
|
}
|
|
|
|
public virtual void RemoveSelectedParticles(ref bool[] selected, bool optimize = true)
|
|
{
|
|
List<int> particles = new List<int>();
|
|
|
|
// iterate over all particles and get those selected ones that are only constrained to other selected ones.
|
|
for (int i = activeParticleCount - 1; i >= 0; --i)
|
|
{
|
|
// if the particle is not selected for optimization, skip it.
|
|
if (!selected[i])
|
|
continue;
|
|
|
|
// look if the particle shares distance or shape matching constraints with an unselected particle.
|
|
bool shared = false;
|
|
if (optimize)
|
|
{
|
|
shared |= DoesParticleShareConstraints(distanceConstraintsData, i, particles, selected);
|
|
shared |= DoesParticleShareConstraints(bendConstraintsData, i, particles, selected);
|
|
shared |= DoesParticleShareConstraints(shapeMatchingConstraintsData, i, particles, selected);
|
|
}
|
|
|
|
if (!shared)
|
|
{
|
|
if (DeactivateParticle(i))
|
|
{
|
|
selected.Swap(i, m_ActiveParticleCount);
|
|
|
|
// Update constraints:
|
|
foreach (IObiConstraints constraints in GetConstraints())
|
|
for (int j = 0; j < constraints.batchCount; ++j)
|
|
constraints.GetBatch(j).ParticlesSwapped(i, m_ActiveParticleCount);
|
|
|
|
// Update groups:
|
|
ParticlesSwappedInGroups(i, m_ActiveParticleCount);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// deactivate all constraints that reference inactive particles:
|
|
foreach (IObiConstraints constraints in GetConstraints())
|
|
DeactivateConstraintsWithInactiveParticles(constraints, particles);
|
|
|
|
CommitBlueprintChanges();
|
|
|
|
edited = true;
|
|
}
|
|
|
|
public void RestoreRemovedParticles()
|
|
{
|
|
m_ActiveParticleCount = m_InitialActiveParticleCount;
|
|
|
|
foreach (IObiConstraints constraints in GetConstraints())
|
|
for (int j = 0; j < constraints.batchCount; ++j)
|
|
constraints.GetBatch(j).activeConstraintCount = constraints.GetBatch(j).initialActiveConstraintCount;
|
|
|
|
CommitBlueprintChanges();
|
|
}
|
|
|
|
public virtual void GenerateTethers(bool[] selected) { }
|
|
public virtual void ClearTethers() { }
|
|
|
|
protected abstract IEnumerator Initialize();
|
|
|
|
}
|
|
} |