337 lines
22 KiB
C#
337 lines
22 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace Obi
|
|
{
|
|
[AddComponentMenu("Physics/Obi/Obi Pinhole", 820)]
|
|
[RequireComponent(typeof(ObiRopeBase))]
|
|
[ExecuteInEditMode]
|
|
public class ObiPinhole : MonoBehaviour
|
|
{
|
|
|
|
[SerializeField] [HideInInspector] private ObiRopeBase m_Rope;
|
|
[SerializeField] [HideInInspector] private Transform m_Target;
|
|
|
|
[Range(0, 1)]
|
|
[SerializeField] [HideInInspector] private float m_Position = 0;
|
|
|
|
[SerializeField] [HideInInspector] private bool m_LimitRange = false;
|
|
[MinMax(0, 1)]
|
|
[SerializeField] [HideInInspector] private Vector2 m_Range = new Vector2(0, 1);
|
|
|
|
[Range(0, 1)]
|
|
[SerializeField] [HideInInspector] private float m_Friction = 0;
|
|
[SerializeField] [HideInInspector] private float m_MotorSpeed = 0;
|
|
[SerializeField] [HideInInspector] private float m_MotorForce = 0;
|
|
[SerializeField] [HideInInspector] private float m_Compliance = 0;
|
|
[SerializeField] [HideInInspector] private bool m_ClampAtEnds = true;
|
|
|
|
[SerializeField] [HideInInspector] private ObiPinholeConstraintsBatch.PinholeEdge currentEdge;
|
|
[SerializeField] [HideInInspector] public ObiPinholeConstraintsBatch.PinholeEdge firstEdge;
|
|
[SerializeField] [HideInInspector] public ObiPinholeConstraintsBatch.PinholeEdge lastEdge;
|
|
|
|
// private variables are serialized during script reloading, to keep their value. Must mark them explicitly as non-serialized.
|
|
[NonSerialized] private ObiPinholeConstraintsBatch pinBatch;
|
|
[NonSerialized] private ObiColliderBase attachedCollider;
|
|
[NonSerialized] private int attachedColliderHandleIndex;
|
|
|
|
[NonSerialized] private Vector3 m_PositionOffset;
|
|
[NonSerialized] private bool m_ParametersDirty = true;
|
|
[NonSerialized] private bool m_PositionDirty = false;
|
|
[NonSerialized] private bool m_RangeDirty = false;
|
|
|
|
/// <summary>
|
|
/// The rope this attachment is added to.
|
|
/// </summary>
|
|
public ObiActor rope
|
|
{
|
|
get { return m_Rope; }
|
|
}
|
|
|
|
public float edgeCoordinate
|
|
{
|
|
get { return currentEdge.coordinate; }
|
|
}
|
|
|
|
public int edgeIndex
|
|
{
|
|
get { return currentEdge.edgeIndex; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The target transform that the pinhole should be attached to.
|
|
/// </summary>
|
|
public Transform target
|
|
{
|
|
get { return m_Target; }
|
|
set
|
|
{
|
|
if (value != m_Target)
|
|
{
|
|
m_Target = value;
|
|
Bind();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Normalized coordinate of the point along the rope where the pinhole is positioned.
|
|
/// </summary>
|
|
public float position
|
|
{
|
|
get { return m_Position; }
|
|
set
|
|
{
|
|
if (!Mathf.Approximately(value, m_Position))
|
|
{
|
|
m_Position = value;
|
|
CalculateMu();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool limitRange
|
|
{
|
|
get { return m_LimitRange; }
|
|
set
|
|
{
|
|
if (m_LimitRange != value)
|
|
{
|
|
m_LimitRange = value;
|
|
CalculateRange();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Normalized coordinate of the point along the rope where the pinhole is positioned.
|
|
/// </summary>
|
|
public Vector2 range
|
|
{
|
|
get { return m_Range; }
|
|
set
|
|
{
|
|
m_Range = value;
|
|
CalculateRange();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether this pinhole is currently bound or not.
|
|
/// </summary>
|
|
public bool isBound
|
|
{
|
|
get { return m_Target != null && currentEdge.edgeIndex >= 0; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constraint compliance.
|
|
/// </summary>
|
|
/// High compliance values will increase the pinhole's elasticity.
|
|
public float compliance
|
|
{
|
|
get { return m_Compliance; }
|
|
set
|
|
{
|
|
if (!Mathf.Approximately(value, m_Compliance))
|
|
{
|
|
m_Compliance = value;
|
|
m_ParametersDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public float friction
|
|
{
|
|
get { return m_Friction; }
|
|
set
|
|
{
|
|
if (!Mathf.Approximately(value, m_Friction))
|
|
{
|
|
m_Friction = value;
|
|
m_ParametersDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public float motorSpeed
|
|
{
|
|
get { return m_MotorSpeed; }
|
|
set
|
|
{
|
|
if (!Mathf.Approximately(value, m_MotorSpeed))
|
|
{
|
|
m_MotorSpeed = value;
|
|
m_ParametersDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public float motorForce
|
|
{
|
|
get { return m_MotorForce; }
|
|
set
|
|
{
|
|
if (!Mathf.Approximately(value, m_MotorForce))
|
|
{
|
|
m_MotorForce = value;
|
|
m_ParametersDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool clampAtEnds
|
|
{
|
|
get { return m_ClampAtEnds; }
|
|
set
|
|
{
|
|
if (m_ClampAtEnds != value)
|
|
{
|
|
m_ClampAtEnds = value;
|
|
m_ParametersDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force threshold above which the pinhole should break.
|
|
/// </summary>
|
|
[Delayed] public float breakThreshold = float.PositiveInfinity;
|
|
|
|
public float relativeVelocity { get; private set; }
|
|
|
|
private void OnEnable()
|
|
{
|
|
m_Rope = GetComponent<ObiRopeBase>();
|
|
m_Rope.OnBlueprintLoaded += Actor_OnBlueprintLoaded;
|
|
m_Rope.OnSimulationStart += Actor_OnSimulationStart;
|
|
m_Rope.OnRequestReadback += Actor_OnRequestReadback;
|
|
|
|
if (m_Rope.solver != null)
|
|
Actor_OnBlueprintLoaded(m_Rope, m_Rope.sourceBlueprint);
|
|
|
|
EnablePinhole();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
DisablePinhole();
|
|
|
|
m_Rope.OnBlueprintLoaded -= Actor_OnBlueprintLoaded;
|
|
m_Rope.OnSimulationStart -= Actor_OnSimulationStart;
|
|
m_Rope.OnRequestReadback -= Actor_OnRequestReadback;
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
m_Rope = GetComponent<ObiRopeBase>();
|
|
m_ParametersDirty = true;
|
|
m_PositionDirty = true;
|
|
m_RangeDirty = true;
|
|
}
|
|
|
|
private void Actor_OnBlueprintLoaded(ObiActor act, ObiActorBlueprint blueprint)
|
|
{
|
|
Bind();
|
|
}
|
|
|
|
private void Actor_OnSimulationStart(ObiActor act, float stepTime, float substepTime)
|
|
{
|
|
// Attachments must be updated at the start of the step, before performing any simulation.
|
|
UpdatePinhole();
|
|
|
|
// if there's any broken constraint, flag pinhole constraints as dirty for remerging at the start of the next step.
|
|
BreakPinhole(substepTime);
|
|
}
|
|
|
|
private void Actor_OnRequestReadback(ObiActor actor)
|
|
{
|
|
if (enabled && m_Rope.isLoaded && isBound)
|
|
{
|
|
var solver = m_Rope.solver;
|
|
|
|
var actorConstraints = m_Rope.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
|
|
if (actorConstraints != null && pinBatch != null && actorConstraints.batchCount <= solverConstraints.batchCount)
|
|
{
|
|
int pinBatchIndex = actorConstraints.batches.IndexOf(pinBatch);
|
|
if (pinBatchIndex >= 0 && pinBatchIndex < rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole].Count)
|
|
{
|
|
var solverBatch = solverConstraints.batches[pinBatchIndex];
|
|
|
|
solverBatch.particleIndices.Readback();
|
|
solverBatch.edgeMus.Readback();
|
|
solverBatch.relativeVelocities.Readback();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ClampMuToRange()
|
|
{
|
|
if (m_LimitRange)
|
|
{
|
|
float maxCoord = lastEdge.GetRopeCoordinate(m_Rope);
|
|
float minCoord = firstEdge.GetRopeCoordinate(m_Rope);
|
|
|
|
if (m_Position > maxCoord)
|
|
{
|
|
m_Position = maxCoord;
|
|
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
|
|
m_PositionDirty = true;
|
|
}
|
|
else if (m_Position < minCoord)
|
|
{
|
|
m_Position = minCoord;
|
|
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
|
|
m_PositionDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CalculateMu()
|
|
{
|
|
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
|
|
ClampMuToRange();
|
|
|
|
m_PositionDirty = true;
|
|
}
|
|
|
|
public void CalculateRange()
|
|
{
|
|
if (m_LimitRange)
|
|
{
|
|
firstEdge.edgeIndex = m_Rope.GetEdgeAt(m_Range.x, out firstEdge.coordinate);
|
|
lastEdge.edgeIndex = m_Rope.GetEdgeAt(m_Range.y, out lastEdge.coordinate);
|
|
}
|
|
else
|
|
{
|
|
firstEdge.edgeIndex = m_Rope.GetEdgeAt(0, out firstEdge.coordinate);
|
|
lastEdge.edgeIndex = m_Rope.GetEdgeAt(1, out lastEdge.coordinate);
|
|
firstEdge.coordinate = -float.MaxValue;
|
|
lastEdge.coordinate = float.MaxValue;
|
|
}
|
|
|
|
ClampMuToRange();
|
|
|
|
m_RangeDirty = true;
|
|
}
|
|
|
|
public void Bind()
|
|
{
|
|
// Disable pinhole.
|
|
DisablePinhole();
|
|
|
|
if (m_Target != null && m_Rope.isLoaded)
|
|
{
|
|
Matrix4x4 bindMatrix = m_Target.worldToLocalMatrix * m_Rope.solver.transform.localToWorldMatrix;
|
|
|
|
var ropeBlueprint = m_Rope.sharedBlueprint as ObiRopeBlueprintBase;
|
|
if (ropeBlueprint != null && ropeBlueprint.deformableEdges != null)
|
|
{
|
|
currentEdge.edgeIndex = m_Rope.GetEdgeAt(m_Position, out currentEdge.coordinate);
|
|
|
|
if (currentEdge.edgeIndex >= 0)
|
|
{
|
|
CalculateRange();
|
|
m_RangeDirty = false;
|
|
m_PositionDirty = false;
|
|
|
|
int p1 = ropeBlueprint.deformableEdges[currentEdge.edgeIndex * 2];
|
|
int p2 = ropeBlueprint.deformableEdges[currentEdge.edgeIndex * 2+1];
|
|
Vector4 pos = Vector4.Lerp(m_Rope.solver.positions[m_Rope.solverIndices[p1]], m_Rope.solver.positions[m_Rope.solverIndices[p2]], currentEdge.coordinate);
|
|
m_PositionOffset = bindMatrix.MultiplyPoint3x4(pos);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentEdge.edgeIndex = -1;
|
|
}
|
|
|
|
// Re-enable pinhole.
|
|
EnablePinhole();
|
|
}
|
|
|
|
private void EnablePinhole()
|
|
{
|
|
|
|
if (enabled && m_Rope.isLoaded && isBound)
|
|
{
|
|
var pins = m_Rope.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiPinholeConstraintsData;
|
|
attachedCollider = m_Target.GetComponent<ObiColliderBase>();
|
|
|
|
if (pins != null && attachedCollider != null && pinBatch == null)
|
|
{
|
|
// create a new data batch with all our pin constraints:
|
|
pinBatch = new ObiPinholeConstraintsBatch(pins);
|
|
pinBatch.AddConstraint(currentEdge,
|
|
firstEdge,
|
|
lastEdge,
|
|
m_Rope,
|
|
attachedCollider,
|
|
m_PositionOffset,
|
|
m_Compliance,
|
|
m_Friction,
|
|
m_MotorSpeed,
|
|
m_MotorForce,
|
|
m_ClampAtEnds);
|
|
|
|
pinBatch.activeConstraintCount++;
|
|
|
|
// add the batch to the actor:
|
|
pins.AddBatch(pinBatch);
|
|
|
|
// store the attached collider's handle:
|
|
attachedColliderHandleIndex = -1;
|
|
if (attachedCollider.Handle != null)
|
|
attachedColliderHandleIndex = attachedCollider.Handle.index;
|
|
|
|
m_Rope.SetConstraintsDirty(Oni.ConstraintType.Pinhole);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private void DisablePinhole()
|
|
{
|
|
if (isBound)
|
|
{
|
|
if (pinBatch != null)
|
|
{
|
|
var pins = m_Rope.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
if (pins != null)
|
|
{
|
|
pins.RemoveBatch(pinBatch);
|
|
if (rope.isLoaded)
|
|
m_Rope.SetConstraintsDirty(Oni.ConstraintType.Pinhole);
|
|
}
|
|
|
|
attachedCollider = null;
|
|
pinBatch = null;
|
|
attachedColliderHandleIndex = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdatePinhole()
|
|
{
|
|
if (enabled && m_Rope.isLoaded && isBound)
|
|
{
|
|
UpdateEdgeCoordinate();
|
|
UpdateParameters();
|
|
|
|
// in case the handle has been updated/invalidated (for instance, when disabling the target) rebuild constraints:
|
|
if (attachedCollider != null &&
|
|
attachedCollider.Handle != null &&
|
|
attachedCollider.Handle.index != attachedColliderHandleIndex)
|
|
{
|
|
attachedColliderHandleIndex = attachedCollider.Handle.index;
|
|
m_Rope.SetConstraintsDirty(Oni.ConstraintType.Pinhole);
|
|
}
|
|
}
|
|
else if (!isBound && attachedColliderHandleIndex >= 0)
|
|
{
|
|
attachedColliderHandleIndex = -1;
|
|
m_Rope.SetConstraintsDirty(Oni.ConstraintType.Pinhole);
|
|
}
|
|
}
|
|
|
|
private void UpdateParameters()
|
|
{
|
|
if (enabled && m_Rope.isLoaded && isBound && m_ParametersDirty)
|
|
{
|
|
var solver = m_Rope.solver;
|
|
|
|
var actorConstraints = m_Rope.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
|
|
if (actorConstraints != null && pinBatch != null && actorConstraints.batchCount <= solverConstraints.batchCount)
|
|
{
|
|
int pinBatchIndex = actorConstraints.batches.IndexOf(pinBatch);
|
|
if (pinBatchIndex >= 0 && pinBatchIndex < rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole].Count)
|
|
{
|
|
int offset = rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole][pinBatchIndex];
|
|
var solverBatch = solverConstraints.batches[pinBatchIndex];
|
|
|
|
for (int i = 0; i < pinBatch.activeConstraintCount; i++)
|
|
{
|
|
solverBatch.parameters[(offset + i) * 5] = m_Compliance;
|
|
solverBatch.parameters[(offset + i) * 5+1] = m_Friction;
|
|
solverBatch.parameters[(offset + i) * 5+2] = m_MotorSpeed;
|
|
solverBatch.parameters[(offset + i) * 5+3] = m_MotorForce;
|
|
solverBatch.parameters[(offset + i) * 5+4] = m_ClampAtEnds ? 1 : 0;
|
|
}
|
|
solverBatch.parameters.Upload();
|
|
|
|
m_ParametersDirty = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateEdgeCoordinate()
|
|
{
|
|
if (enabled && m_Rope.isLoaded && isBound)
|
|
{
|
|
var solver = m_Rope.solver;
|
|
|
|
var actorConstraints = m_Rope.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
|
|
if (actorConstraints != null && pinBatch != null && actorConstraints.batchCount <= solverConstraints.batchCount)
|
|
{
|
|
int pinBatchIndex = actorConstraints.batches.IndexOf(pinBatch);
|
|
if (pinBatchIndex >= 0 && pinBatchIndex < rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole].Count)
|
|
{
|
|
int offset = rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole][pinBatchIndex];
|
|
var solverBatch = solverConstraints.batches[pinBatchIndex];
|
|
|
|
solverBatch.particleIndices.WaitForReadback();
|
|
solverBatch.edgeMus.WaitForReadback();
|
|
solverBatch.relativeVelocities.WaitForReadback();
|
|
|
|
if (m_RangeDirty)
|
|
{
|
|
// update edge index and coordinate, then upload them.
|
|
for (int i = 0; i < pinBatch.activeConstraintCount; i++)
|
|
{
|
|
solverBatch.edgeRanges[(offset + i) * 2] = m_Rope.deformableEdgesOffset + firstEdge.edgeIndex;
|
|
solverBatch.edgeRanges[(offset + i) * 2 + 1] = m_Rope.deformableEdgesOffset + lastEdge.edgeIndex;
|
|
solverBatch.edgeRangeMus[(offset + i) * 2] = firstEdge.coordinate;
|
|
solverBatch.edgeRangeMus[(offset + i) * 2 + 1] = lastEdge.coordinate;
|
|
}
|
|
solverBatch.edgeRanges.Upload();
|
|
solverBatch.edgeRangeMus.Upload();
|
|
|
|
m_RangeDirty = false;
|
|
}
|
|
|
|
if (m_PositionDirty)
|
|
{
|
|
// update edge index and coordinate, then upload them.
|
|
for (int i = 0; i < pinBatch.activeConstraintCount; i++)
|
|
{
|
|
solverBatch.particleIndices[offset + i] = m_Rope.deformableEdgesOffset + currentEdge.edgeIndex;
|
|
solverBatch.edgeMus[offset + i] = currentEdge.coordinate;
|
|
}
|
|
solverBatch.particleIndices.Upload();
|
|
solverBatch.edgeMus.Upload();
|
|
|
|
m_PositionDirty = false;
|
|
}
|
|
else
|
|
{
|
|
// read edge index and coordinate:
|
|
for (int i = 0; i < pinBatch.activeConstraintCount; i++)
|
|
{
|
|
currentEdge.coordinate = solverBatch.edgeMus[offset + i];
|
|
currentEdge.edgeIndex = solverBatch.particleIndices[offset + i] - m_Rope.deformableEdgesOffset;
|
|
m_Position = currentEdge.GetRopeCoordinate(m_Rope);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < pinBatch.activeConstraintCount; i++)
|
|
{
|
|
relativeVelocity = solverBatch.relativeVelocities[offset + i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void BreakPinhole(float substepTime)
|
|
{
|
|
if (enabled && m_Rope.isLoaded && isBound)
|
|
{
|
|
var solver = m_Rope.solver;
|
|
|
|
var actorConstraints = m_Rope.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pinhole) as ObiConstraints<ObiPinholeConstraintsBatch>;
|
|
|
|
bool dirty = false;
|
|
if (actorConstraints != null && pinBatch != null && actorConstraints.batchCount <= solverConstraints.batchCount)
|
|
{
|
|
int pinBatchIndex = actorConstraints.batches.IndexOf(pinBatch);
|
|
if (pinBatchIndex >= 0 && pinBatchIndex < rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole].Count)
|
|
{
|
|
int offset = rope.solverBatchOffsets[(int)Oni.ConstraintType.Pinhole][pinBatchIndex];
|
|
var solverBatch = solverConstraints.batches[pinBatchIndex];
|
|
|
|
float sqrTime = substepTime * substepTime;
|
|
for (int i = 0; i < pinBatch.activeConstraintCount; i++)
|
|
{
|
|
// In case the handle has been created/destroyed.
|
|
if (pinBatch.pinBodies[i] != attachedCollider.Handle)
|
|
{
|
|
pinBatch.pinBodies[i] = attachedCollider.Handle;
|
|
dirty = true;
|
|
}
|
|
|
|
// in case the constraint has been broken:
|
|
if (-solverBatch.lambdas[offset + i] / sqrTime > breakThreshold)
|
|
{
|
|
pinBatch.DeactivateConstraint(i);
|
|
dirty = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// constraints are recreated at the start of a step.
|
|
if (dirty)
|
|
m_Rope.SetConstraintsDirty(Oni.ConstraintType.Pinhole);
|
|
}
|
|
}
|
|
}
|
|
}
|